/
lint.go
198 lines (177 loc) · 4.2 KB
/
lint.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Package lint provides abstractions on top of go/analysis.
package lint
import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
)
type Analyzer struct {
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
// this field is structured, providing access to severity, options
// etc.
Doc *Documentation
Analyzer *analysis.Analyzer
}
func (a *Analyzer) initialize() {
a.Analyzer.Doc = a.Doc.String()
if a.Analyzer.Flags.Usage == nil {
fs := flag.NewFlagSet("", flag.PanicOnError)
fs.Var(newVersionFlag(), "go", "Target Go version")
a.Analyzer.Flags = *fs
}
}
func InitializeAnalyzers(docs map[string]*Documentation, analyzers map[string]*analysis.Analyzer) []*Analyzer {
out := make([]*Analyzer, 0, len(analyzers))
for k, v := range analyzers {
v.Name = k
a := &Analyzer{
Doc: docs[k],
Analyzer: v,
}
a.initialize()
out = append(out, a)
}
return out
}
type Severity int
const (
SeverityNone Severity = iota
SeverityError
SeverityDeprecated
SeverityWarning
SeverityInfo
SeverityHint
)
type Documentation struct {
Title string
Text string
Since string
NonDefault bool
Options []string
Severity Severity
}
func Markdownify(m map[string]*Documentation) map[string]*Documentation {
for _, v := range m {
v.Title = toMarkdown(v.Title)
v.Text = toMarkdown(v.Text)
}
return m
}
func toMarkdown(s string) string {
return strings.ReplaceAll(s, `\'`, "`")
}
func (doc *Documentation) String() string {
if doc == nil {
return "Error: No documentation."
}
b := &strings.Builder{}
fmt.Fprintf(b, "%s\n\n", doc.Title)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.Text)
}
fmt.Fprint(b, "Available since\n ")
if doc.Since == "" {
fmt.Fprint(b, "unreleased")
} else {
fmt.Fprintf(b, "%s", doc.Since)
}
if doc.NonDefault {
fmt.Fprint(b, ", non-default")
}
fmt.Fprint(b, "\n")
if len(doc.Options) > 0 {
fmt.Fprintf(b, "\nOptions\n")
for _, opt := range doc.Options {
fmt.Fprintf(b, " %s", opt)
}
fmt.Fprint(b, "\n")
}
return b.String()
}
func newVersionFlag() flag.Getter {
tags := build.Default.ReleaseTags
v := tags[len(tags)-1][2:]
version := new(VersionFlag)
if err := version.Set(v); err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
return version
}
type VersionFlag int
func (v *VersionFlag) String() string {
return fmt.Sprintf("1.%d", *v)
}
func (v *VersionFlag) Set(s string) error {
if len(s) < 3 {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[0] != '1' {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[1] != '.' {
return fmt.Errorf("invalid Go version: %q", s)
}
i, err := strconv.Atoi(s[2:])
if err != nil {
return fmt.Errorf("invalid Go version: %q", s)
}
*v = VersionFlag(i)
return nil
}
func (v *VersionFlag) Get() interface{} {
return int(*v)
}
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
// that type switches are exhaustive.
func ExhaustiveTypeSwitch(v interface{}) {
panic(fmt.Sprintf("internal error: unhandled case %T", v))
}
// A directive is a comment of the form '//lint:<command>
// [arguments...]'. It represents instructions to the static analysis
// tool.
type Directive struct {
Command string
Arguments []string
Directive *ast.Comment
Node ast.Node
}
func parseDirective(s string) (cmd string, args []string) {
if !strings.HasPrefix(s, "//lint:") {
return "", nil
}
s = strings.TrimPrefix(s, "//lint:")
fields := strings.Split(s, " ")
return fields[0], fields[1:]
}
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
var dirs []Directive
for _, f := range files {
// OPT(dh): in our old code, we skip all the comment map work if we
// couldn't find any directives, benchmark if that's actually
// worth doing
cm := ast.NewCommentMap(fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
for _, c := range cg.List {
if !strings.HasPrefix(c.Text, "//lint:") {
continue
}
cmd, args := parseDirective(c.Text)
d := Directive{
Command: cmd,
Arguments: args,
Directive: c,
Node: node,
}
dirs = append(dirs, d)
}
}
}
}
return dirs
}