| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package useany defines an Analyzer that checks for usage of interface{} in |
| // constraints, rather than the predeclared any. |
| package useany |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/go/analysis/passes/inspect" |
| "golang.org/x/tools/go/ast/inspector" |
| "golang.org/x/tools/internal/typeparams" |
| ) |
| |
| const Doc = `check for constraints that could be simplified to "any"` |
| |
| var Analyzer = &analysis.Analyzer{ |
| Name: "useany", |
| Doc: Doc, |
| Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| Run: run, |
| } |
| |
| func run(pass *analysis.Pass) (interface{}, error) { |
| inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| |
| universeAny := types.Universe.Lookup("any") |
| if universeAny == nil { |
| // Go <= 1.17. Nothing to check. |
| return nil, nil |
| } |
| |
| nodeFilter := []ast.Node{ |
| (*ast.TypeSpec)(nil), |
| (*ast.FuncType)(nil), |
| } |
| |
| inspect.Preorder(nodeFilter, func(node ast.Node) { |
| var tparams *ast.FieldList |
| switch node := node.(type) { |
| case *ast.TypeSpec: |
| tparams = typeparams.ForTypeSpec(node) |
| case *ast.FuncType: |
| tparams = typeparams.ForFuncType(node) |
| default: |
| panic(fmt.Sprintf("unexpected node type %T", node)) |
| } |
| if tparams.NumFields() == 0 { |
| return |
| } |
| |
| for _, field := range tparams.List { |
| typ := pass.TypesInfo.Types[field.Type].Type |
| if typ == nil { |
| continue // something is wrong, but not our concern |
| } |
| iface, ok := typ.Underlying().(*types.Interface) |
| if !ok { |
| continue // invalid constraint |
| } |
| |
| // If the constraint is the empty interface, offer a fix to use 'any' |
| // instead. |
| if iface.Empty() { |
| id, _ := field.Type.(*ast.Ident) |
| if id != nil && pass.TypesInfo.Uses[id] == universeAny { |
| continue |
| } |
| |
| diag := analysis.Diagnostic{ |
| Pos: field.Type.Pos(), |
| End: field.Type.End(), |
| Message: `could use "any" for this empty interface`, |
| } |
| |
| // Only suggest a fix to 'any' if we actually resolve the predeclared |
| // any in this scope. |
| if scope := pass.TypesInfo.Scopes[node]; scope != nil { |
| if _, any := scope.LookupParent("any", token.NoPos); any == universeAny { |
| diag.SuggestedFixes = []analysis.SuggestedFix{{ |
| Message: `use "any"`, |
| TextEdits: []analysis.TextEdit{{ |
| Pos: field.Type.Pos(), |
| End: field.Type.End(), |
| NewText: []byte("any"), |
| }}, |
| }} |
| } |
| } |
| |
| pass.Report(diag) |
| } |
| } |
| }) |
| return nil, nil |
| } |