/
simplifyrange.go
116 lines (103 loc) · 2.87 KB
/
simplifyrange.go
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
// Copyright 2020 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 simplifyrange defines an Analyzer that simplifies range statements.
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
package simplifyrange
import (
"bytes"
"go/ast"
"go/printer"
"go/token"
"github.com/april1989/origin-go-tools/go/analysis"
"github.com/april1989/origin-go-tools/go/analysis/passes/inspect"
"github.com/april1989/origin-go-tools/go/ast/inspector"
)
const Doc = `check for range statement simplifications
A range of the form:
for x, _ = range v {...}
will be simplified to:
for x = range v {...}
A range of the form:
for _ = range v {...}
will be simplified to:
for range v {...}
This is one of the simplifications that "gofmt -s" applies.`
var Analyzer = &analysis.Analyzer{
Name: "simplifyrange",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.RangeStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
var copy *ast.RangeStmt
if stmt, ok := n.(*ast.RangeStmt); ok {
x := *stmt
copy = &x
}
if copy == nil {
return
}
end := newlineIndex(pass.Fset, copy)
// Range statements of the form: for i, _ := range x {}
var old ast.Expr
if isBlank(copy.Value) {
old = copy.Value
copy.Value = nil
}
// Range statements of the form: for _ := range x {}
if isBlank(copy.Key) && copy.Value == nil {
old = copy.Key
copy.Key = nil
}
// Return early if neither if condition is met.
if old == nil {
return
}
pass.Report(analysis.Diagnostic{
Pos: old.Pos(),
End: old.End(),
Message: "simplify range expression",
SuggestedFixes: suggestedFixes(pass.Fset, copy, end),
})
})
return nil, nil
}
func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix {
var b bytes.Buffer
printer.Fprint(&b, fset, rng)
stmt := b.Bytes()
index := bytes.Index(stmt, []byte("\n"))
// If there is a new line character, then don't replace the body.
if index != -1 {
stmt = stmt[:index]
}
return []analysis.SuggestedFix{{
Message: "Remove empty value",
TextEdits: []analysis.TextEdit{{
Pos: rng.Pos(),
End: end,
NewText: stmt[:index],
}},
}}
}
func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos {
var b bytes.Buffer
printer.Fprint(&b, fset, rng)
contents := b.Bytes()
index := bytes.Index(contents, []byte("\n"))
if index == -1 {
return rng.End()
}
return rng.Pos() + token.Pos(index)
}
func isBlank(x ast.Expr) bool {
ident, ok := x.(*ast.Ident)
return ok && ident.Name == "_"
}