golang.org/x/tools@v0.21.0/go/analysis/passes/composite/composite.go (about) 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/aliases" 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 // runUnkeyedLiteral checks if a composite literal is a struct literal with 54 // unkeyed fields. 55 func run(pass *analysis.Pass) (interface{}, error) { 56 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 57 58 nodeFilter := []ast.Node{ 59 (*ast.CompositeLit)(nil), 60 } 61 inspect.Preorder(nodeFilter, func(n ast.Node) { 62 cl := n.(*ast.CompositeLit) 63 64 typ := pass.TypesInfo.Types[cl].Type 65 if typ == nil { 66 // cannot determine composite literals' type, skip it 67 return 68 } 69 typeName := typ.String() 70 if whitelist && unkeyedLiteral[typeName] { 71 // skip whitelisted types 72 return 73 } 74 var structuralTypes []types.Type 75 switch typ := aliases.Unalias(typ).(type) { 76 case *types.TypeParam: 77 terms, err := typeparams.StructuralTerms(typ) 78 if err != nil { 79 return // invalid type 80 } 81 for _, term := range terms { 82 structuralTypes = append(structuralTypes, term.Type()) 83 } 84 default: 85 structuralTypes = append(structuralTypes, typ) 86 } 87 88 for _, typ := range structuralTypes { 89 strct, ok := typeparams.Deref(typ).Underlying().(*types.Struct) 90 if !ok { 91 // skip non-struct composite literals 92 continue 93 } 94 if isLocalType(pass, typ) { 95 // allow unkeyed locally defined composite literal 96 continue 97 } 98 99 // check if the struct contains an unkeyed field 100 allKeyValue := true 101 var suggestedFixAvailable = len(cl.Elts) == strct.NumFields() 102 var missingKeys []analysis.TextEdit 103 for i, e := range cl.Elts { 104 if _, ok := e.(*ast.KeyValueExpr); !ok { 105 allKeyValue = false 106 if i >= strct.NumFields() { 107 break 108 } 109 field := strct.Field(i) 110 if !field.Exported() { 111 // Adding unexported field names for structs not defined 112 // locally will not work. 113 suggestedFixAvailable = false 114 break 115 } 116 missingKeys = append(missingKeys, analysis.TextEdit{ 117 Pos: e.Pos(), 118 End: e.Pos(), 119 NewText: []byte(fmt.Sprintf("%s: ", field.Name())), 120 }) 121 } 122 } 123 if allKeyValue { 124 // all the struct fields are keyed 125 continue 126 } 127 128 diag := analysis.Diagnostic{ 129 Pos: cl.Pos(), 130 End: cl.End(), 131 Message: fmt.Sprintf("%s struct literal uses unkeyed fields", typeName), 132 } 133 if suggestedFixAvailable { 134 diag.SuggestedFixes = []analysis.SuggestedFix{{ 135 Message: "Add field names to struct literal", 136 TextEdits: missingKeys, 137 }} 138 } 139 pass.Report(diag) 140 return 141 } 142 }) 143 return nil, nil 144 } 145 146 // isLocalType reports whether typ belongs to the same package as pass. 147 // TODO(adonovan): local means "internal to a function"; rename to isSamePackageType. 148 func isLocalType(pass *analysis.Pass, typ types.Type) bool { 149 switch x := aliases.Unalias(typ).(type) { 150 case *types.Struct: 151 // struct literals are local types 152 return true 153 case *types.Pointer: 154 return isLocalType(pass, x.Elem()) 155 case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already) 156 // names in package foo are local to foo_test too 157 return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test") 158 } 159 return false 160 }