File: analyzer.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.25.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 22,724 kB
  • sloc: javascript: 2,027; asm: 1,645; sh: 166; yacc: 155; makefile: 49; ansic: 8
file content (134 lines) | stat: -rw-r--r-- 3,592 bytes parent folder | download | duplicates (2)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright 2019 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 sortslice defines an Analyzer that checks for calls
// to sort.Slice that do not use a slice type as first argument.
package sortslice

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/types"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/inspect"
	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
	"golang.org/x/tools/go/ast/inspector"
	"golang.org/x/tools/go/types/typeutil"
)

const Doc = `check the argument type of sort.Slice

sort.Slice requires an argument of a slice type. Check that
the interface{} value passed to sort.Slice is actually a slice.`

var Analyzer = &analysis.Analyzer{
	Name:     "sortslice",
	Doc:      Doc,
	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice",
	Requires: []*analysis.Analyzer{inspect.Analyzer},
	Run:      run,
}

func run(pass *analysis.Pass) (interface{}, error) {
	if !analysisutil.Imports(pass.Pkg, "sort") {
		return nil, nil // doesn't directly import sort
	}

	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

	nodeFilter := []ast.Node{
		(*ast.CallExpr)(nil),
	}

	inspect.Preorder(nodeFilter, func(n ast.Node) {
		call := n.(*ast.CallExpr)
		fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
		if !analysisutil.IsFunctionNamed(fn, "sort", "Slice", "SliceStable", "SliceIsSorted") {
			return
		}

		arg := call.Args[0]
		typ := pass.TypesInfo.Types[arg].Type

		if tuple, ok := typ.(*types.Tuple); ok {
			typ = tuple.At(0).Type() // special case for Slice(f(...))
		}

		switch typ.Underlying().(type) {
		case *types.Slice, *types.Interface:
			return
		}

		// Restore typ to the original type, we may unwrap the tuple above,
		// typ might not be the type of arg.
		typ = pass.TypesInfo.Types[arg].Type

		var fixes []analysis.SuggestedFix
		switch v := typ.Underlying().(type) {
		case *types.Array:
			var buf bytes.Buffer
			format.Node(&buf, pass.Fset, &ast.SliceExpr{
				X:      arg,
				Slice3: false,
				Lbrack: arg.End() + 1,
				Rbrack: arg.End() + 3,
			})
			fixes = append(fixes, analysis.SuggestedFix{
				Message: "Get a slice of the full array",
				TextEdits: []analysis.TextEdit{{
					Pos:     arg.Pos(),
					End:     arg.End(),
					NewText: buf.Bytes(),
				}},
			})
		case *types.Pointer:
			_, ok := v.Elem().Underlying().(*types.Slice)
			if !ok {
				break
			}
			var buf bytes.Buffer
			format.Node(&buf, pass.Fset, &ast.StarExpr{
				X: arg,
			})
			fixes = append(fixes, analysis.SuggestedFix{
				Message: "Dereference the pointer to the slice",
				TextEdits: []analysis.TextEdit{{
					Pos:     arg.Pos(),
					End:     arg.End(),
					NewText: buf.Bytes(),
				}},
			})
		case *types.Signature:
			if v.Params().Len() != 0 || v.Results().Len() != 1 {
				break
			}
			if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
				break
			}
			var buf bytes.Buffer
			format.Node(&buf, pass.Fset, &ast.CallExpr{
				Fun: arg,
			})
			fixes = append(fixes, analysis.SuggestedFix{
				Message: "Call the function",
				TextEdits: []analysis.TextEdit{{
					Pos:     arg.Pos(),
					End:     arg.End(),
					NewText: buf.Bytes(),
				}},
			})
		}

		pass.Report(analysis.Diagnostic{
			Pos:            call.Pos(),
			End:            call.End(),
			Message:        fmt.Sprintf("%s's argument must be a slice; is called with %s", fn.FullName(), typ.String()),
			SuggestedFixes: fixes,
		})
	})
	return nil, nil
}