-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathanalyzer.go
148 lines (127 loc) · 3.53 KB
/
analyzer.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package nolint
import (
"go/ast"
"go/token"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "nolint",
Doc: "make the go/analysis checkers be able to ignore diagnostics with \"Nolint\" comment",
Run: run,
RunDespiteErrors: true,
ResultType: reflect.TypeOf(new(NoLinter)),
// FactTypes []Fact
}
func run(pass *analysis.Pass) (interface{}, error) {
noLinter := &NoLinter{
fset: pass.Fset,
marks: map[mark]bool{},
}
for _, f := range pass.Files {
noLinter.file = f
noLinter.commentMap = nil
ast.Walk(noLinter, f)
}
return noLinter, nil
}
type NoLinter struct {
fset *token.FileSet
file *ast.File
commentMap ast.CommentMap
marks map[mark]bool
}
func (n *NoLinter) Visit(node ast.Node) ast.Visitor {
if node == nil {
return n
}
// Ensure we initialized the comment map if we don't have one.
if n.commentMap == nil {
n.commentMap = ast.NewCommentMap(n.fset, node, n.file.Comments)
}
// No need to match comments with comments.
switch node.(type) {
case *ast.Comment, *ast.CommentGroup:
return n
}
// Update the comment map to target the current node we're visiting.
n.commentMap.Update(nil, node)
// Get the comments related to this specific node. If there are no comments
// there can't be any nolint directive.
commentsForNode, ok := n.commentMap[node]
if !ok {
return n
}
// If we would have multiple comment groups for the node we only care for
// the last one since we want the directive to be added right above our
// node.
lastCommentForNode := commentsForNode[len(commentsForNode)-1]
n.regMarks(lastCommentForNode, node)
return n
}
func (n *NoLinter) IgnoreDiagnostic(diagnostic analysis.Diagnostic) bool {
for _, category := range []string{diagnostic.Category, ""} {
if n.marks[n.genMark(n.fset, diagnostic.Pos, category)] {
return true
}
}
return false
}
func (n *NoLinter) IgnoreNode(r analysis.Range, category string) bool {
for _, category := range []string{category, ""} {
if n.marks[n.genMark(n.fset, r.Pos(), category)] {
return true
}
}
return false
}
type mark struct {
Filename string
Line int
Category string
}
func (n *NoLinter) genMark(fset *token.FileSet, pos token.Pos, category string) mark {
p := fset.Position(pos)
return mark{
Filename: p.Filename,
Line: p.Line,
Category: category,
}
}
func (n *NoLinter) regMark(r analysis.Range, category string) {
category = strings.TrimSpace(category)
n.marks[n.genMark(n.fset, r.Pos(), category)] = true
}
func (n *NoLinter) regMarks(comment *ast.CommentGroup, node ast.Node) {
var (
lastCommentIdx int
allCommentText = comment.Text()
commentLines = strings.Split(allCommentText, "\n")
commentLineNo = n.fset.Position(comment.End()).Line
nodeLineNo = n.fset.Position(node.Pos()).Line
)
if commentLineNo != nodeLineNo && commentLineNo != nodeLineNo-1 {
// The `nolint` comment does not end on the same line or the line above
// the node..
return
}
switch {
case len(commentLines) == 1:
lastCommentIdx = len(commentLines) - 1
case len(commentLines) >= 2:
lastCommentIdx = len(commentLines) - 2
}
lastLineOfComment := commentLines[lastCommentIdx]
for _, block := range strings.Split(lastLineOfComment, "//") {
block := strings.TrimSpace(block)
if block == "nolint" {
n.regMark(node, "")
}
if strings.HasPrefix(block, "nolint:") {
for _, category := range strings.Split(strings.TrimPrefix(block, "nolint:"), ",") {
n.regMark(node, strings.TrimSpace(category))
}
}
}
}