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

     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  	"strings"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  	"golang.org/x/tools/go/ast/inspector"
    18  	"golang.org/x/tools/internal/typeparams"
    19  )
    20  
    21  const Doc = `check for unkeyed composite literals
    22  
    23  This analyzer reports a diagnostic for composite literals of struct
    24  types imported from another package that do not use the field-keyed
    25  syntax. Such literals are fragile because the addition of a new field
    26  (even if unexported) to the struct will cause compilation to fail.
    27  
    28  As an example,
    29  
    30  	err = &net.DNSConfigError{err}
    31  
    32  should be replaced by:
    33  
    34  	err = &net.DNSConfigError{Err: err}
    35  `
    36  
    37  var Analyzer = &analysis.Analyzer{
    38  	Name:             "composites",
    39  	Doc:              Doc,
    40  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite",
    41  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    42  	RunDespiteErrors: true,
    43  	Run:              run,
    44  }
    45  
    46  var whitelist = true
    47  
    48  func init() {
    49  	Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only")
    50  }
    51  
    52  // runUnkeyedLiteral checks if a composite literal is a struct literal with
    53  // unkeyed fields.
    54  func run(pass *analysis.Pass) (interface{}, error) {
    55  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    56  
    57  	nodeFilter := []ast.Node{
    58  		(*ast.CompositeLit)(nil),
    59  	}
    60  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    61  		cl := n.(*ast.CompositeLit)
    62  
    63  		typ := pass.TypesInfo.Types[cl].Type
    64  		if typ == nil {
    65  			// cannot determine composite literals' type, skip it
    66  			return
    67  		}
    68  		typeName := typ.String()
    69  		if whitelist && unkeyedLiteral[typeName] {
    70  			// skip whitelisted types
    71  			return
    72  		}
    73  		var structuralTypes []types.Type
    74  		switch typ := typ.(type) {
    75  		case *types.TypeParam:
    76  			terms, err := typeparams.StructuralTerms(typ)
    77  			if err != nil {
    78  				return // invalid type
    79  			}
    80  			for _, term := range terms {
    81  				structuralTypes = append(structuralTypes, term.Type())
    82  			}
    83  		default:
    84  			structuralTypes = append(structuralTypes, typ)
    85  		}
    86  		for _, typ := range structuralTypes {
    87  			under := deref(typ.Underlying())
    88  			strct, ok := under.(*types.Struct)
    89  			if !ok {
    90  				// skip non-struct composite literals
    91  				continue
    92  			}
    93  			if isLocalType(pass, typ) {
    94  				// allow unkeyed locally defined composite literal
    95  				continue
    96  			}
    97  
    98  			// check if the struct contains an unkeyed field
    99  			allKeyValue := true
   100  			var suggestedFixAvailable = len(cl.Elts) == strct.NumFields()
   101  			var missingKeys []analysis.TextEdit
   102  			for i, e := range cl.Elts {
   103  				if _, ok := e.(*ast.KeyValueExpr); !ok {
   104  					allKeyValue = false
   105  					if i >= strct.NumFields() {
   106  						break
   107  					}
   108  					field := strct.Field(i)
   109  					if !field.Exported() {
   110  						// Adding unexported field names for structs not defined
   111  						// locally will not work.
   112  						suggestedFixAvailable = false
   113  						break
   114  					}
   115  					missingKeys = append(missingKeys, analysis.TextEdit{
   116  						Pos:     e.Pos(),
   117  						End:     e.Pos(),
   118  						NewText: []byte(fmt.Sprintf("%s: ", field.Name())),
   119  					})
   120  				}
   121  			}
   122  			if allKeyValue {
   123  				// all the struct fields are keyed
   124  				continue
   125  			}
   126  
   127  			diag := analysis.Diagnostic{
   128  				Pos:     cl.Pos(),
   129  				End:     cl.End(),
   130  				Message: fmt.Sprintf("%s struct literal uses unkeyed fields", typeName),
   131  			}
   132  			if suggestedFixAvailable {
   133  				diag.SuggestedFixes = []analysis.SuggestedFix{{
   134  					Message:   "Add field names to struct literal",
   135  					TextEdits: missingKeys,
   136  				}}
   137  			}
   138  			pass.Report(diag)
   139  			return
   140  		}
   141  	})
   142  	return nil, nil
   143  }
   144  
   145  func deref(typ types.Type) types.Type {
   146  	for {
   147  		ptr, ok := typ.(*types.Pointer)
   148  		if !ok {
   149  			break
   150  		}
   151  		typ = ptr.Elem().Underlying()
   152  	}
   153  	return typ
   154  }
   155  
   156  func isLocalType(pass *analysis.Pass, typ types.Type) bool {
   157  	switch x := typ.(type) {
   158  	case *types.Struct:
   159  		// struct literals are local types
   160  		return true
   161  	case *types.Pointer:
   162  		return isLocalType(pass, x.Elem())
   163  	case *types.Named:
   164  		// names in package foo are local to foo_test too
   165  		return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
   166  	case *types.TypeParam:
   167  		return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
   168  	}
   169  	return false
   170  }
   171  

View as plain text