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