1
2
3
4
5
6
7 package composite
8
9 import (
10 "go/ast"
11 "go/types"
12 "strings"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 "golang.org/x/tools/go/ast/inspector"
17 "golang.org/x/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
51
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
64 return
65 }
66 typeName := typ.String()
67 if whitelist && unkeyedLiteral[typeName] {
68
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
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
88 continue
89 }
90 if isLocalType(pass, typ) {
91
92 continue
93 }
94
95
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
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
130 return true
131 case *types.Pointer:
132 return isLocalType(pass, x.Elem())
133 case *types.Named:
134
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 }
141
View as plain text