aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/analysis/useany/useany.go
blob: 73e2f763316d8e1b42c70ad196f2139b34f4e44a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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
}