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