diff options
Diffstat (limited to 'vendor/honnef.co/go/tools/lint/lintutil/util.go')
-rw-r--r-- | vendor/honnef.co/go/tools/lint/lintutil/util.go | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/vendor/honnef.co/go/tools/lint/lintutil/util.go b/vendor/honnef.co/go/tools/lint/lintutil/util.go new file mode 100644 index 0000000..fe0279f --- /dev/null +++ b/vendor/honnef.co/go/tools/lint/lintutil/util.go | |||
@@ -0,0 +1,392 @@ | |||
1 | // Copyright (c) 2013 The Go Authors. All rights reserved. | ||
2 | // | ||
3 | // Use of this source code is governed by a BSD-style | ||
4 | // license that can be found in the LICENSE file or at | ||
5 | // https://developers.google.com/open-source/licenses/bsd. | ||
6 | |||
7 | // Package lintutil provides helpers for writing linter command lines. | ||
8 | package lintutil // import "honnef.co/go/tools/lint/lintutil" | ||
9 | |||
10 | import ( | ||
11 | "crypto/sha256" | ||
12 | "errors" | ||
13 | "flag" | ||
14 | "fmt" | ||
15 | "go/build" | ||
16 | "go/token" | ||
17 | "io" | ||
18 | "log" | ||
19 | "os" | ||
20 | "os/signal" | ||
21 | "regexp" | ||
22 | "runtime" | ||
23 | "runtime/pprof" | ||
24 | "strconv" | ||
25 | "strings" | ||
26 | "sync/atomic" | ||
27 | |||
28 | "honnef.co/go/tools/config" | ||
29 | "honnef.co/go/tools/internal/cache" | ||
30 | "honnef.co/go/tools/lint" | ||
31 | "honnef.co/go/tools/lint/lintutil/format" | ||
32 | "honnef.co/go/tools/version" | ||
33 | |||
34 | "golang.org/x/tools/go/analysis" | ||
35 | "golang.org/x/tools/go/buildutil" | ||
36 | "golang.org/x/tools/go/packages" | ||
37 | ) | ||
38 | |||
39 | func NewVersionFlag() flag.Getter { | ||
40 | tags := build.Default.ReleaseTags | ||
41 | v := tags[len(tags)-1][2:] | ||
42 | version := new(VersionFlag) | ||
43 | if err := version.Set(v); err != nil { | ||
44 | panic(fmt.Sprintf("internal error: %s", err)) | ||
45 | } | ||
46 | return version | ||
47 | } | ||
48 | |||
49 | type VersionFlag int | ||
50 | |||
51 | func (v *VersionFlag) String() string { | ||
52 | return fmt.Sprintf("1.%d", *v) | ||
53 | |||
54 | } | ||
55 | |||
56 | func (v *VersionFlag) Set(s string) error { | ||
57 | if len(s) < 3 { | ||
58 | return errors.New("invalid Go version") | ||
59 | } | ||
60 | if s[0] != '1' { | ||
61 | return errors.New("invalid Go version") | ||
62 | } | ||
63 | if s[1] != '.' { | ||
64 | return errors.New("invalid Go version") | ||
65 | } | ||
66 | i, err := strconv.Atoi(s[2:]) | ||
67 | *v = VersionFlag(i) | ||
68 | return err | ||
69 | } | ||
70 | |||
71 | func (v *VersionFlag) Get() interface{} { | ||
72 | return int(*v) | ||
73 | } | ||
74 | |||
75 | func usage(name string, flags *flag.FlagSet) func() { | ||
76 | return func() { | ||
77 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", name) | ||
78 | fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name) | ||
79 | fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name) | ||
80 | fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name) | ||
81 | fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name) | ||
82 | fmt.Fprintf(os.Stderr, "Flags:\n") | ||
83 | flags.PrintDefaults() | ||
84 | } | ||
85 | } | ||
86 | |||
87 | type list []string | ||
88 | |||
89 | func (list *list) String() string { | ||
90 | return `"` + strings.Join(*list, ",") + `"` | ||
91 | } | ||
92 | |||
93 | func (list *list) Set(s string) error { | ||
94 | if s == "" { | ||
95 | *list = nil | ||
96 | return nil | ||
97 | } | ||
98 | |||
99 | *list = strings.Split(s, ",") | ||
100 | return nil | ||
101 | } | ||
102 | |||
103 | func FlagSet(name string) *flag.FlagSet { | ||
104 | flags := flag.NewFlagSet("", flag.ExitOnError) | ||
105 | flags.Usage = usage(name, flags) | ||
106 | flags.String("tags", "", "List of `build tags`") | ||
107 | flags.Bool("tests", true, "Include tests") | ||
108 | flags.Bool("version", false, "Print version and exit") | ||
109 | flags.Bool("show-ignored", false, "Don't filter ignored problems") | ||
110 | flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')") | ||
111 | flags.String("explain", "", "Print description of `check`") | ||
112 | |||
113 | flags.String("debug.cpuprofile", "", "Write CPU profile to `file`") | ||
114 | flags.String("debug.memprofile", "", "Write memory profile to `file`") | ||
115 | flags.Bool("debug.version", false, "Print detailed version information about this program") | ||
116 | flags.Bool("debug.no-compile-errors", false, "Don't print compile errors") | ||
117 | |||
118 | checks := list{"inherit"} | ||
119 | fail := list{"all"} | ||
120 | flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.") | ||
121 | flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.") | ||
122 | |||
123 | tags := build.Default.ReleaseTags | ||
124 | v := tags[len(tags)-1][2:] | ||
125 | version := new(VersionFlag) | ||
126 | if err := version.Set(v); err != nil { | ||
127 | panic(fmt.Sprintf("internal error: %s", err)) | ||
128 | } | ||
129 | |||
130 | flags.Var(version, "go", "Target Go `version` in the format '1.x'") | ||
131 | return flags | ||
132 | } | ||
133 | |||
134 | func findCheck(cs []*analysis.Analyzer, check string) (*analysis.Analyzer, bool) { | ||
135 | for _, c := range cs { | ||
136 | if c.Name == check { | ||
137 | return c, true | ||
138 | } | ||
139 | } | ||
140 | return nil, false | ||
141 | } | ||
142 | |||
143 | func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs *flag.FlagSet) { | ||
144 | tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string) | ||
145 | tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool) | ||
146 | goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int) | ||
147 | formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string) | ||
148 | printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool) | ||
149 | showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool) | ||
150 | explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string) | ||
151 | |||
152 | cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string) | ||
153 | memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string) | ||
154 | debugVersion := fs.Lookup("debug.version").Value.(flag.Getter).Get().(bool) | ||
155 | debugNoCompile := fs.Lookup("debug.no-compile-errors").Value.(flag.Getter).Get().(bool) | ||
156 | |||
157 | cfg := config.Config{} | ||
158 | cfg.Checks = *fs.Lookup("checks").Value.(*list) | ||
159 | |||
160 | exit := func(code int) { | ||
161 | if cpuProfile != "" { | ||
162 | pprof.StopCPUProfile() | ||
163 | } | ||
164 | if memProfile != "" { | ||
165 | f, err := os.Create(memProfile) | ||
166 | if err != nil { | ||
167 | panic(err) | ||
168 | } | ||
169 | runtime.GC() | ||
170 | pprof.WriteHeapProfile(f) | ||
171 | } | ||
172 | os.Exit(code) | ||
173 | } | ||
174 | if cpuProfile != "" { | ||
175 | f, err := os.Create(cpuProfile) | ||
176 | if err != nil { | ||
177 | log.Fatal(err) | ||
178 | } | ||
179 | pprof.StartCPUProfile(f) | ||
180 | } | ||
181 | |||
182 | if debugVersion { | ||
183 | version.Verbose() | ||
184 | exit(0) | ||
185 | } | ||
186 | |||
187 | if printVersion { | ||
188 | version.Print() | ||
189 | exit(0) | ||
190 | } | ||
191 | |||
192 | // Validate that the tags argument is well-formed. go/packages | ||
193 | // doesn't detect malformed build flags and returns unhelpful | ||
194 | // errors. | ||
195 | tf := buildutil.TagsFlag{} | ||
196 | if err := tf.Set(tags); err != nil { | ||
197 | fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", tags, err)) | ||
198 | exit(1) | ||
199 | } | ||
200 | |||
201 | if explain != "" { | ||
202 | var haystack []*analysis.Analyzer | ||
203 | haystack = append(haystack, cs...) | ||
204 | for _, cum := range cums { | ||
205 | haystack = append(haystack, cum.Analyzer()) | ||
206 | } | ||
207 | check, ok := findCheck(haystack, explain) | ||
208 | if !ok { | ||
209 | fmt.Fprintln(os.Stderr, "Couldn't find check", explain) | ||
210 | exit(1) | ||
211 | } | ||
212 | if check.Doc == "" { | ||
213 | fmt.Fprintln(os.Stderr, explain, "has no documentation") | ||
214 | exit(1) | ||
215 | } | ||
216 | fmt.Println(check.Doc) | ||
217 | exit(0) | ||
218 | } | ||
219 | |||
220 | ps, err := Lint(cs, cums, fs.Args(), &Options{ | ||
221 | Tags: tags, | ||
222 | LintTests: tests, | ||
223 | GoVersion: goVersion, | ||
224 | Config: cfg, | ||
225 | }) | ||
226 | if err != nil { | ||
227 | fmt.Fprintln(os.Stderr, err) | ||
228 | exit(1) | ||
229 | } | ||
230 | |||
231 | var f format.Formatter | ||
232 | switch formatter { | ||
233 | case "text": | ||
234 | f = format.Text{W: os.Stdout} | ||
235 | case "stylish": | ||
236 | f = &format.Stylish{W: os.Stdout} | ||
237 | case "json": | ||
238 | f = format.JSON{W: os.Stdout} | ||
239 | default: | ||
240 | fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter) | ||
241 | exit(2) | ||
242 | } | ||
243 | |||
244 | var ( | ||
245 | total int | ||
246 | errors int | ||
247 | warnings int | ||
248 | ) | ||
249 | |||
250 | fail := *fs.Lookup("fail").Value.(*list) | ||
251 | analyzers := make([]*analysis.Analyzer, len(cs), len(cs)+len(cums)) | ||
252 | copy(analyzers, cs) | ||
253 | for _, cum := range cums { | ||
254 | analyzers = append(analyzers, cum.Analyzer()) | ||
255 | } | ||
256 | shouldExit := lint.FilterChecks(analyzers, fail) | ||
257 | shouldExit["compile"] = true | ||
258 | |||
259 | total = len(ps) | ||
260 | for _, p := range ps { | ||
261 | if p.Check == "compile" && debugNoCompile { | ||
262 | continue | ||
263 | } | ||
264 | if p.Severity == lint.Ignored && !showIgnored { | ||
265 | continue | ||
266 | } | ||
267 | if shouldExit[p.Check] { | ||
268 | errors++ | ||
269 | } else { | ||
270 | p.Severity = lint.Warning | ||
271 | warnings++ | ||
272 | } | ||
273 | f.Format(p) | ||
274 | } | ||
275 | if f, ok := f.(format.Statter); ok { | ||
276 | f.Stats(total, errors, warnings) | ||
277 | } | ||
278 | if errors > 0 { | ||
279 | exit(1) | ||
280 | } | ||
281 | exit(0) | ||
282 | } | ||
283 | |||
284 | type Options struct { | ||
285 | Config config.Config | ||
286 | |||
287 | Tags string | ||
288 | LintTests bool | ||
289 | GoVersion int | ||
290 | } | ||
291 | |||
292 | func computeSalt() ([]byte, error) { | ||
293 | if version.Version != "devel" { | ||
294 | return []byte(version.Version), nil | ||
295 | } | ||
296 | p, err := os.Executable() | ||
297 | if err != nil { | ||
298 | return nil, err | ||
299 | } | ||
300 | f, err := os.Open(p) | ||
301 | if err != nil { | ||
302 | return nil, err | ||
303 | } | ||
304 | defer f.Close() | ||
305 | h := sha256.New() | ||
306 | if _, err := io.Copy(h, f); err != nil { | ||
307 | return nil, err | ||
308 | } | ||
309 | return h.Sum(nil), nil | ||
310 | } | ||
311 | |||
312 | func Lint(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, paths []string, opt *Options) ([]lint.Problem, error) { | ||
313 | salt, err := computeSalt() | ||
314 | if err != nil { | ||
315 | return nil, fmt.Errorf("could not compute salt for cache: %s", err) | ||
316 | } | ||
317 | cache.SetSalt(salt) | ||
318 | |||
319 | if opt == nil { | ||
320 | opt = &Options{} | ||
321 | } | ||
322 | |||
323 | l := &lint.Linter{ | ||
324 | Checkers: cs, | ||
325 | CumulativeCheckers: cums, | ||
326 | GoVersion: opt.GoVersion, | ||
327 | Config: opt.Config, | ||
328 | } | ||
329 | cfg := &packages.Config{} | ||
330 | if opt.LintTests { | ||
331 | cfg.Tests = true | ||
332 | } | ||
333 | if opt.Tags != "" { | ||
334 | cfg.BuildFlags = append(cfg.BuildFlags, "-tags", opt.Tags) | ||
335 | } | ||
336 | |||
337 | printStats := func() { | ||
338 | // Individual stats are read atomically, but overall there | ||
339 | // is no synchronisation. For printing rough progress | ||
340 | // information, this doesn't matter. | ||
341 | switch atomic.LoadUint32(&l.Stats.State) { | ||
342 | case lint.StateInitializing: | ||
343 | fmt.Fprintln(os.Stderr, "Status: initializing") | ||
344 | case lint.StateGraph: | ||
345 | fmt.Fprintln(os.Stderr, "Status: loading package graph") | ||
346 | case lint.StateProcessing: | ||
347 | fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d; Problems: %d\n", | ||
348 | atomic.LoadUint32(&l.Stats.ProcessedInitialPackages), | ||
349 | atomic.LoadUint32(&l.Stats.InitialPackages), | ||
350 | atomic.LoadUint32(&l.Stats.ProcessedPackages), | ||
351 | atomic.LoadUint32(&l.Stats.TotalPackages), | ||
352 | atomic.LoadUint32(&l.Stats.ActiveWorkers), | ||
353 | atomic.LoadUint32(&l.Stats.TotalWorkers), | ||
354 | atomic.LoadUint32(&l.Stats.Problems), | ||
355 | ) | ||
356 | case lint.StateCumulative: | ||
357 | fmt.Fprintln(os.Stderr, "Status: processing cumulative checkers") | ||
358 | } | ||
359 | } | ||
360 | if len(infoSignals) > 0 { | ||
361 | ch := make(chan os.Signal, 1) | ||
362 | signal.Notify(ch, infoSignals...) | ||
363 | defer signal.Stop(ch) | ||
364 | go func() { | ||
365 | for range ch { | ||
366 | printStats() | ||
367 | } | ||
368 | }() | ||
369 | } | ||
370 | |||
371 | return l.Lint(cfg, paths) | ||
372 | } | ||
373 | |||
374 | var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`) | ||
375 | |||
376 | func parsePos(pos string) token.Position { | ||
377 | if pos == "-" || pos == "" { | ||
378 | return token.Position{} | ||
379 | } | ||
380 | parts := posRe.FindStringSubmatch(pos) | ||
381 | if parts == nil { | ||
382 | panic(fmt.Sprintf("internal error: malformed position %q", pos)) | ||
383 | } | ||
384 | file := parts[1] | ||
385 | line, _ := strconv.Atoi(parts[2]) | ||
386 | col, _ := strconv.Atoi(parts[3]) | ||
387 | return token.Position{ | ||
388 | Filename: file, | ||
389 | Line: line, | ||
390 | Column: col, | ||
391 | } | ||
392 | } | ||