Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / analysis / fillstruct / fillstruct.go
1 // Copyright 2020 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 fillstruct defines an Analyzer that automatically
6 // fills in a struct declaration with zero value elements for each field.
7 package fillstruct
8
9 import (
10         "bytes"
11         "fmt"
12         "go/ast"
13         "go/format"
14         "go/token"
15         "go/types"
16         "unicode"
17
18         "golang.org/x/tools/go/analysis"
19         "golang.org/x/tools/go/analysis/passes/inspect"
20         "golang.org/x/tools/go/ast/astutil"
21         "golang.org/x/tools/go/ast/inspector"
22         "golang.org/x/tools/internal/analysisinternal"
23         "golang.org/x/tools/internal/span"
24 )
25
26 const Doc = `note incomplete struct initializations
27
28 This analyzer provides diagnostics for any struct literals that do not have
29 any fields initialized. Because the suggested fix for this analysis is
30 expensive to compute, callers should compute it separately, using the
31 SuggestedFix function below.
32 `
33
34 var Analyzer = &analysis.Analyzer{
35         Name:             "fillstruct",
36         Doc:              Doc,
37         Requires:         []*analysis.Analyzer{inspect.Analyzer},
38         Run:              run,
39         RunDespiteErrors: true,
40 }
41
42 func run(pass *analysis.Pass) (interface{}, error) {
43         inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
44         nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
45         inspect.Preorder(nodeFilter, func(n ast.Node) {
46                 info := pass.TypesInfo
47                 if info == nil {
48                         return
49                 }
50                 expr := n.(*ast.CompositeLit)
51
52                 // TODO: Handle partially-filled structs as well.
53                 if len(expr.Elts) != 0 {
54                         return
55                 }
56
57                 var file *ast.File
58                 for _, f := range pass.Files {
59                         if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
60                                 file = f
61                                 break
62                         }
63                 }
64                 if file == nil {
65                         return
66                 }
67
68                 typ := info.TypeOf(expr)
69                 if typ == nil {
70                         return
71                 }
72
73                 // Find reference to the type declaration of the struct being initialized.
74                 for {
75                         p, ok := typ.Underlying().(*types.Pointer)
76                         if !ok {
77                                 break
78                         }
79                         typ = p.Elem()
80                 }
81                 typ = typ.Underlying()
82
83                 obj, ok := typ.(*types.Struct)
84                 if !ok {
85                         return
86                 }
87                 fieldCount := obj.NumFields()
88
89                 // Skip any struct that is already populated or that has no fields.
90                 if fieldCount == 0 || fieldCount == len(expr.Elts) {
91                         return
92                 }
93                 var fillable bool
94                 for i := 0; i < fieldCount; i++ {
95                         field := obj.Field(i)
96
97                         // Ignore fields that are not accessible in the current package.
98                         if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
99                                 continue
100                         }
101                         fillable = true
102                 }
103                 if !fillable {
104                         return
105                 }
106                 var name string
107                 switch typ := expr.Type.(type) {
108                 case *ast.Ident:
109                         name = typ.Name
110                 case *ast.SelectorExpr:
111                         name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name)
112                 default:
113                         name = "anonymous struct"
114                 }
115                 pass.Report(analysis.Diagnostic{
116                         Message: fmt.Sprintf("Fill %s", name),
117                         Pos:     expr.Pos(),
118                         End:     expr.End(),
119                 })
120         })
121         return nil, nil
122 }
123
124 func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
125         pos := rng.Start // don't use the end
126
127         // TODO(rstambler): Using ast.Inspect would probably be more efficient than
128         // calling PathEnclosingInterval. Switch this approach.
129         path, _ := astutil.PathEnclosingInterval(file, pos, pos)
130         if len(path) == 0 {
131                 return nil, fmt.Errorf("no enclosing ast.Node")
132         }
133         var expr *ast.CompositeLit
134         for _, n := range path {
135                 if node, ok := n.(*ast.CompositeLit); ok {
136                         expr = node
137                         break
138                 }
139         }
140         if info == nil {
141                 return nil, fmt.Errorf("nil types.Info")
142         }
143         typ := info.TypeOf(expr)
144         if typ == nil {
145                 return nil, fmt.Errorf("no composite literal")
146         }
147
148         // Find reference to the type declaration of the struct being initialized.
149         for {
150                 p, ok := typ.Underlying().(*types.Pointer)
151                 if !ok {
152                         break
153                 }
154                 typ = p.Elem()
155         }
156         typ = typ.Underlying()
157
158         obj, ok := typ.(*types.Struct)
159         if !ok {
160                 return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ)
161         }
162         fieldCount := obj.NumFields()
163
164         // Use a new fileset to build up a token.File for the new composite
165         // literal. We need one line for foo{, one line for }, and one line for
166         // each field we're going to set. format.Node only cares about line
167         // numbers, so we don't need to set columns, and each line can be
168         // 1 byte long.
169         fakeFset := token.NewFileSet()
170         tok := fakeFset.AddFile("", -1, fieldCount+2)
171
172         line := 2 // account for 1-based lines and the left brace
173         var elts []ast.Expr
174         var fieldTyps []types.Type
175         for i := 0; i < fieldCount; i++ {
176                 field := obj.Field(i)
177                 // Ignore fields that are not accessible in the current package.
178                 if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
179                         fieldTyps = append(fieldTyps, nil)
180                         continue
181                 }
182                 fieldTyps = append(fieldTyps, field.Type())
183         }
184         matches := analysisinternal.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg)
185         for i, fieldTyp := range fieldTyps {
186                 if fieldTyp == nil {
187                         continue
188                 }
189                 idents, ok := matches[fieldTyp]
190                 if !ok {
191                         return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
192                 }
193
194                 // Find the identifer whose name is most similar to the name of the field's key.
195                 // If we do not find any identifer that matches the pattern, generate a new value.
196                 // NOTE: We currently match on the name of the field key rather than the field type.
197                 value := analysisinternal.FindBestMatch(obj.Field(i).Name(), idents)
198                 if value == nil {
199                         value = populateValue(fset, file, pkg, fieldTyp)
200                 }
201                 if value == nil {
202                         return nil, nil
203                 }
204
205                 tok.AddLine(line - 1) // add 1 byte per line
206                 if line > tok.LineCount() {
207                         panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
208                 }
209                 pos := tok.LineStart(line)
210
211                 kv := &ast.KeyValueExpr{
212                         Key: &ast.Ident{
213                                 NamePos: pos,
214                                 Name:    obj.Field(i).Name(),
215                         },
216                         Colon: pos,
217                         Value: value,
218                 }
219                 elts = append(elts, kv)
220                 line++
221         }
222
223         // If all of the struct's fields are unexported, we have nothing to do.
224         if len(elts) == 0 {
225                 return nil, fmt.Errorf("no elements to fill")
226         }
227
228         // Add the final line for the right brace. Offset is the number of
229         // bytes already added plus 1.
230         tok.AddLine(len(elts) + 1)
231         line = len(elts) + 2
232         if line > tok.LineCount() {
233                 panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
234         }
235
236         cl := &ast.CompositeLit{
237                 Type:   expr.Type,
238                 Lbrace: tok.LineStart(1),
239                 Elts:   elts,
240                 Rbrace: tok.LineStart(line),
241         }
242
243         // Find the line on which the composite literal is declared.
244         split := bytes.Split(content, []byte("\n"))
245         lineNumber := fset.Position(expr.Lbrace).Line
246         firstLine := split[lineNumber-1] // lines are 1-indexed
247
248         // Trim the whitespace from the left of the line, and use the index
249         // to get the amount of whitespace on the left.
250         trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace)
251         index := bytes.Index(firstLine, trimmed)
252         whitespace := firstLine[:index]
253
254         var newExpr bytes.Buffer
255         if err := format.Node(&newExpr, fakeFset, cl); err != nil {
256                 return nil, fmt.Errorf("failed to format %s: %v", cl.Type, err)
257         }
258         split = bytes.Split(newExpr.Bytes(), []byte("\n"))
259         newText := bytes.NewBuffer(nil)
260         for i, s := range split {
261                 // Don't add the extra indentation to the first line.
262                 if i != 0 {
263                         newText.Write(whitespace)
264                 }
265                 newText.Write(s)
266                 if i < len(split)-1 {
267                         newText.WriteByte('\n')
268                 }
269         }
270         return &analysis.SuggestedFix{
271                 TextEdits: []analysis.TextEdit{
272                         {
273                                 Pos:     expr.Pos(),
274                                 End:     expr.End(),
275                                 NewText: newText.Bytes(),
276                         },
277                 },
278         }, nil
279 }
280
281 // populateValue constructs an expression to fill the value of a struct field.
282 //
283 // When the type of a struct field is a basic literal or interface, we return
284 // default values. For other types, such as maps, slices, and channels, we create
285 // expressions rather than using default values.
286 //
287 // The reasoning here is that users will call fillstruct with the intention of
288 // initializing the struct, in which case setting these fields to nil has no effect.
289 func populateValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
290         under := typ
291         if n, ok := typ.(*types.Named); ok {
292                 under = n.Underlying()
293         }
294         switch u := under.(type) {
295         case *types.Basic:
296                 switch {
297                 case u.Info()&types.IsNumeric != 0:
298                         return &ast.BasicLit{Kind: token.INT, Value: "0"}
299                 case u.Info()&types.IsBoolean != 0:
300                         return &ast.Ident{Name: "false"}
301                 case u.Info()&types.IsString != 0:
302                         return &ast.BasicLit{Kind: token.STRING, Value: `""`}
303                 default:
304                         panic("unknown basic type")
305                 }
306         case *types.Map:
307                 k := analysisinternal.TypeExpr(fset, f, pkg, u.Key())
308                 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
309                 if k == nil || v == nil {
310                         return nil
311                 }
312                 return &ast.CompositeLit{
313                         Type: &ast.MapType{
314                                 Key:   k,
315                                 Value: v,
316                         },
317                 }
318         case *types.Slice:
319                 s := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
320                 if s == nil {
321                         return nil
322                 }
323                 return &ast.CompositeLit{
324                         Type: &ast.ArrayType{
325                                 Elt: s,
326                         },
327                 }
328         case *types.Array:
329                 a := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
330                 if a == nil {
331                         return nil
332                 }
333                 return &ast.CompositeLit{
334                         Type: &ast.ArrayType{
335                                 Elt: a,
336                                 Len: &ast.BasicLit{
337                                         Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
338                                 },
339                         },
340                 }
341         case *types.Chan:
342                 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
343                 if v == nil {
344                         return nil
345                 }
346                 dir := ast.ChanDir(u.Dir())
347                 if u.Dir() == types.SendRecv {
348                         dir = ast.SEND | ast.RECV
349                 }
350                 return &ast.CallExpr{
351                         Fun: ast.NewIdent("make"),
352                         Args: []ast.Expr{
353                                 &ast.ChanType{
354                                         Dir:   dir,
355                                         Value: v,
356                                 },
357                         },
358                 }
359         case *types.Struct:
360                 s := analysisinternal.TypeExpr(fset, f, pkg, typ)
361                 if s == nil {
362                         return nil
363                 }
364                 return &ast.CompositeLit{
365                         Type: s,
366                 }
367         case *types.Signature:
368                 var params []*ast.Field
369                 for i := 0; i < u.Params().Len(); i++ {
370                         p := analysisinternal.TypeExpr(fset, f, pkg, u.Params().At(i).Type())
371                         if p == nil {
372                                 return nil
373                         }
374                         params = append(params, &ast.Field{
375                                 Type: p,
376                                 Names: []*ast.Ident{
377                                         {
378                                                 Name: u.Params().At(i).Name(),
379                                         },
380                                 },
381                         })
382                 }
383                 var returns []*ast.Field
384                 for i := 0; i < u.Results().Len(); i++ {
385                         r := analysisinternal.TypeExpr(fset, f, pkg, u.Results().At(i).Type())
386                         if r == nil {
387                                 return nil
388                         }
389                         returns = append(returns, &ast.Field{
390                                 Type: r,
391                         })
392                 }
393                 return &ast.FuncLit{
394                         Type: &ast.FuncType{
395                                 Params: &ast.FieldList{
396                                         List: params,
397                                 },
398                                 Results: &ast.FieldList{
399                                         List: returns,
400                                 },
401                         },
402                         Body: &ast.BlockStmt{},
403                 }
404         case *types.Pointer:
405                 switch u.Elem().(type) {
406                 case *types.Basic:
407                         return &ast.CallExpr{
408                                 Fun: &ast.Ident{
409                                         Name: "new",
410                                 },
411                                 Args: []ast.Expr{
412                                         &ast.Ident{
413                                                 Name: u.Elem().String(),
414                                         },
415                                 },
416                         }
417                 default:
418                         return &ast.UnaryExpr{
419                                 Op: token.AND,
420                                 X:  populateValue(fset, f, pkg, u.Elem()),
421                         }
422                 }
423         case *types.Interface:
424                 return ast.NewIdent("nil")
425         }
426         return nil
427 }