...

Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go

Documentation: cmd/vendor/golang.org/x/tools/go/analysis/passes/composite

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package composite defines an Analyzer that checks for unkeyed
     6  // composite literals.
     7  package composite
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"go/types"
    13  	"slices"
    14  	"strings"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/ast/inspector"
    19  	"golang.org/x/tools/internal/typeparams"
    20  )
    21  
    22  const Doc = `check for unkeyed composite literals
    23  
    24  This analyzer reports a diagnostic for composite literals of struct
    25  types imported from another package that do not use the field-keyed
    26  syntax. Such literals are fragile because the addition of a new field
    27  (even if unexported) to the struct will cause compilation to fail.
    28  
    29  As an example,
    30  
    31  	err = &net.DNSConfigError{err}
    32  
    33  should be replaced by:
    34  
    35  	err = &net.DNSConfigError{Err: err}
    36  `
    37  
    38  var Analyzer = &analysis.Analyzer{
    39  	Name:             "composites",
    40  	Doc:              Doc,
    41  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite",
    42  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    43  	RunDespiteErrors: true,
    44  	Run:              run,
    45  }
    46  
    47  var whitelist = true
    48  
    49  func init() {
    50  	Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only")
    51  }
    52  
    53  func run(pass *analysis.Pass) (any, error) {
    54  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    55  
    56  	for curLit := range inspect.Root().Preorder((*ast.CompositeLit)(nil)) {
    57  		complit := curLit.Node().(*ast.CompositeLit)
    58  
    59  		// Skip empty or partly/fully keyed literals.
    60  		if len(complit.Elts) == 0 ||
    61  			slices.ContainsFunc(complit.Elts, func(e ast.Expr) bool { return is[*ast.KeyValueExpr](e) }) {
    62  			continue
    63  		}
    64  
    65  		// Find struct type.
    66  		// (For a type parameter, choose an arbitrary term.)
    67  		typ := pass.TypesInfo.Types[complit].Type
    68  		if typ == nil {
    69  			continue // no type info
    70  		}
    71  		terms, err := typeparams.NormalTerms(typ)
    72  		if err != nil || len(terms) == 0 {
    73  			continue // invalid or empty type
    74  		}
    75  		t := terms[0].Type()
    76  		strct, ok := typeparams.Deref(t).Underlying().(*types.Struct)
    77  		if !ok {
    78  			continue // not a struct literal
    79  		}
    80  		if isSamePackageType(pass, t) {
    81  			continue // allow unkeyed literals for structs in same package
    82  		}
    83  
    84  		// Allow whitelisted types.
    85  		typeName := typ.String()
    86  		if whitelist && unkeyedLiteral[typeName] {
    87  			continue
    88  		}
    89  
    90  		// If there is one value per field,
    91  		// offer to fill in the field names.
    92  		var fixes []analysis.SuggestedFix
    93  		if len(complit.Elts) == strct.NumFields() {
    94  			var edits []analysis.TextEdit
    95  			for i, elt := range complit.Elts {
    96  				field := strct.Field(i)
    97  				// We cannot fill in the name of an
    98  				// exported field from another package.
    99  				if !field.Exported() {
   100  					edits = nil
   101  					break
   102  				}
   103  				edits = append(edits, analysis.TextEdit{
   104  					Pos:     elt.Pos(),
   105  					End:     elt.Pos(),
   106  					NewText: fmt.Appendf(nil, "%s: ", field.Name()),
   107  				})
   108  			}
   109  			if edits != nil {
   110  				fixes = []analysis.SuggestedFix{{
   111  					Message:   "Add field names to struct literal",
   112  					TextEdits: edits,
   113  				}}
   114  			}
   115  		}
   116  
   117  		pass.Report(analysis.Diagnostic{
   118  			Pos:            complit.Pos(),
   119  			End:            complit.End(),
   120  			Message:        fmt.Sprintf("%s struct literal uses unkeyed fields", typeName),
   121  			SuggestedFixes: fixes,
   122  		})
   123  	}
   124  	return nil, nil
   125  }
   126  
   127  // isSamePackageType reports whether typ belongs to the same package as pass.
   128  func isSamePackageType(pass *analysis.Pass, typ types.Type) bool {
   129  	switch x := types.Unalias(typ).(type) {
   130  	case *types.Struct:
   131  		// struct literals are local types
   132  		return true
   133  	case *types.Pointer:
   134  		return isSamePackageType(pass, x.Elem())
   135  	case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already)
   136  		// names in package foo are local to foo_test too
   137  		return x.Obj().Pkg() != nil &&
   138  			strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
   139  	}
   140  	return false
   141  }
   142  
   143  func is[T any](x any) bool {
   144  	_, ok := x.(T)
   145  	return ok
   146  }
   147  

View as plain text