github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/analysis/useany/useany.go (about)

     1  // Copyright 2021 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 useany defines an Analyzer that checks for usage of interface{} in
     6  // constraints, rather than the predeclared any.
     7  package useany
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"go/token"
    13  	"go/types"
    14  
    15  	"github.com/powerman/golang-tools/go/analysis"
    16  	"github.com/powerman/golang-tools/go/analysis/passes/inspect"
    17  	"github.com/powerman/golang-tools/go/ast/inspector"
    18  	"github.com/powerman/golang-tools/internal/typeparams"
    19  )
    20  
    21  const Doc = `check for constraints that could be simplified to "any"`
    22  
    23  var Analyzer = &analysis.Analyzer{
    24  	Name:     "useany",
    25  	Doc:      Doc,
    26  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    27  	Run:      run,
    28  }
    29  
    30  func run(pass *analysis.Pass) (interface{}, error) {
    31  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    32  
    33  	universeAny := types.Universe.Lookup("any")
    34  	if universeAny == nil {
    35  		// Go <= 1.17. Nothing to check.
    36  		return nil, nil
    37  	}
    38  
    39  	nodeFilter := []ast.Node{
    40  		(*ast.TypeSpec)(nil),
    41  		(*ast.FuncType)(nil),
    42  	}
    43  
    44  	inspect.Preorder(nodeFilter, func(node ast.Node) {
    45  		var tparams *ast.FieldList
    46  		switch node := node.(type) {
    47  		case *ast.TypeSpec:
    48  			tparams = typeparams.ForTypeSpec(node)
    49  		case *ast.FuncType:
    50  			tparams = typeparams.ForFuncType(node)
    51  		default:
    52  			panic(fmt.Sprintf("unexpected node type %T", node))
    53  		}
    54  		if tparams.NumFields() == 0 {
    55  			return
    56  		}
    57  
    58  		for _, field := range tparams.List {
    59  			typ := pass.TypesInfo.Types[field.Type].Type
    60  			if typ == nil {
    61  				continue // something is wrong, but not our concern
    62  			}
    63  			iface, ok := typ.Underlying().(*types.Interface)
    64  			if !ok {
    65  				continue // invalid constraint
    66  			}
    67  
    68  			// If the constraint is the empty interface, offer a fix to use 'any'
    69  			// instead.
    70  			if iface.Empty() {
    71  				id, _ := field.Type.(*ast.Ident)
    72  				if id != nil && pass.TypesInfo.Uses[id] == universeAny {
    73  					continue
    74  				}
    75  
    76  				diag := analysis.Diagnostic{
    77  					Pos:     field.Type.Pos(),
    78  					End:     field.Type.End(),
    79  					Message: `could use "any" for this empty interface`,
    80  				}
    81  
    82  				// Only suggest a fix to 'any' if we actually resolve the predeclared
    83  				// any in this scope.
    84  				if scope := pass.TypesInfo.Scopes[node]; scope != nil {
    85  					if _, any := scope.LookupParent("any", token.NoPos); any == universeAny {
    86  						diag.SuggestedFixes = []analysis.SuggestedFix{{
    87  							Message: `use "any"`,
    88  							TextEdits: []analysis.TextEdit{{
    89  								Pos:     field.Type.Pos(),
    90  								End:     field.Type.End(),
    91  								NewText: []byte("any"),
    92  							}},
    93  						}}
    94  					}
    95  				}
    96  
    97  				pass.Report(diag)
    98  			}
    99  		}
   100  	})
   101  	return nil, nil
   102  }