1 // Copyright (c) 2013 The Go Authors. All rights reserved.
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.
7 // Package lintutil provides helpers for writing linter command lines.
8 package lintutil // import "honnef.co/go/tools/lint/lintutil"
30 "honnef.co/go/tools/config"
31 "honnef.co/go/tools/internal/cache"
32 "honnef.co/go/tools/lint"
33 "honnef.co/go/tools/lint/lintutil/format"
34 "honnef.co/go/tools/version"
36 "golang.org/x/tools/go/analysis"
37 "golang.org/x/tools/go/buildutil"
38 "golang.org/x/tools/go/packages"
41 func NewVersionFlag() flag.Getter {
42 tags := build.Default.ReleaseTags
43 v := tags[len(tags)-1][2:]
44 version := new(VersionFlag)
45 if err := version.Set(v); err != nil {
46 panic(fmt.Sprintf("internal error: %s", err))
53 func (v *VersionFlag) String() string {
54 return fmt.Sprintf("1.%d", *v)
58 func (v *VersionFlag) Set(s string) error {
60 return errors.New("invalid Go version")
63 return errors.New("invalid Go version")
66 return errors.New("invalid Go version")
68 i, err := strconv.Atoi(s[2:])
73 func (v *VersionFlag) Get() interface{} {
77 func usage(name string, flags *flag.FlagSet) func() {
79 fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
80 fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
81 fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
82 fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
83 fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
84 fmt.Fprintf(os.Stderr, "Flags:\n")
91 func (list *list) String() string {
92 return `"` + strings.Join(*list, ",") + `"`
95 func (list *list) Set(s string) error {
101 *list = strings.Split(s, ",")
105 func FlagSet(name string) *flag.FlagSet {
106 flags := flag.NewFlagSet("", flag.ExitOnError)
107 flags.Usage = usage(name, flags)
108 flags.String("tags", "", "List of `build tags`")
109 flags.Bool("tests", true, "Include tests")
110 flags.Bool("version", false, "Print version and exit")
111 flags.Bool("show-ignored", false, "Don't filter ignored problems")
112 flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
113 flags.String("explain", "", "Print description of `check`")
115 flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
116 flags.String("debug.memprofile", "", "Write memory profile to `file`")
117 flags.Bool("debug.version", false, "Print detailed version information about this program")
118 flags.Bool("debug.no-compile-errors", false, "Don't print compile errors")
119 flags.String("debug.measure-analyzers", "", "Write analysis measurements to `file`. `file` will be opened for appending if it already exists.")
120 flags.Uint("debug.repeat-analyzers", 0, "Run analyzers `num` times")
122 checks := list{"inherit"}
124 flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
125 flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
127 tags := build.Default.ReleaseTags
128 v := tags[len(tags)-1][2:]
129 version := new(VersionFlag)
130 if err := version.Set(v); err != nil {
131 panic(fmt.Sprintf("internal error: %s", err))
134 flags.Var(version, "go", "Target Go `version` in the format '1.x'")
138 func findCheck(cs []*analysis.Analyzer, check string) (*analysis.Analyzer, bool) {
139 for _, c := range cs {
147 func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs *flag.FlagSet) {
148 tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
149 tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
150 goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
151 formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
152 printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
153 showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
154 explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
156 cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
157 memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
158 debugVersion := fs.Lookup("debug.version").Value.(flag.Getter).Get().(bool)
159 debugNoCompile := fs.Lookup("debug.no-compile-errors").Value.(flag.Getter).Get().(bool)
160 debugRepeat := fs.Lookup("debug.repeat-analyzers").Value.(flag.Getter).Get().(uint)
162 var measureAnalyzers func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration)
163 if path := fs.Lookup("debug.measure-analyzers").Value.(flag.Getter).Get().(string); path != "" {
164 f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
170 measureAnalyzers = func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration) {
173 if _, err := fmt.Fprintf(f, "%s\t%s\t%d\n", analysis.Name, pkg.ID, d.Nanoseconds()); err != nil {
174 log.Println("error writing analysis measurements:", err)
179 cfg := config.Config{}
180 cfg.Checks = *fs.Lookup("checks").Value.(*list)
182 exit := func(code int) {
183 if cpuProfile != "" {
184 pprof.StopCPUProfile()
186 if memProfile != "" {
187 f, err := os.Create(memProfile)
192 pprof.WriteHeapProfile(f)
196 if cpuProfile != "" {
197 f, err := os.Create(cpuProfile)
201 pprof.StartCPUProfile(f)
214 // Validate that the tags argument is well-formed. go/packages
215 // doesn't detect malformed build flags and returns unhelpful
217 tf := buildutil.TagsFlag{}
218 if err := tf.Set(tags); err != nil {
219 fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", tags, err))
224 var haystack []*analysis.Analyzer
225 haystack = append(haystack, cs...)
226 for _, cum := range cums {
227 haystack = append(haystack, cum.Analyzer())
229 check, ok := findCheck(haystack, explain)
231 fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
235 fmt.Fprintln(os.Stderr, explain, "has no documentation")
238 fmt.Println(check.Doc)
242 ps, err := Lint(cs, cums, fs.Args(), &Options{
245 GoVersion: goVersion,
247 PrintAnalyzerMeasurement: measureAnalyzers,
248 RepeatAnalyzers: debugRepeat,
251 fmt.Fprintln(os.Stderr, err)
255 var f format.Formatter
258 f = format.Text{W: os.Stdout}
260 f = &format.Stylish{W: os.Stdout}
262 f = format.JSON{W: os.Stdout}
264 fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
275 fail := *fs.Lookup("fail").Value.(*list)
276 analyzers := make([]*analysis.Analyzer, len(cs), len(cs)+len(cums))
278 for _, cum := range cums {
279 analyzers = append(analyzers, cum.Analyzer())
281 shouldExit := lint.FilterChecks(analyzers, fail)
282 shouldExit["compile"] = true
285 for _, p := range ps {
286 if p.Check == "compile" && debugNoCompile {
289 if p.Severity == lint.Ignored && !showIgnored {
293 if shouldExit[p.Check] {
296 p.Severity = lint.Warning
301 if f, ok := f.(format.Statter); ok {
302 f.Stats(total, errors, warnings, ignored)
310 type Options struct {
316 PrintAnalyzerMeasurement func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration)
320 func computeSalt() ([]byte, error) {
321 if version.Version != "devel" {
322 return []byte(version.Version), nil
324 p, err := os.Executable()
334 if _, err := io.Copy(h, f); err != nil {
337 return h.Sum(nil), nil
340 func Lint(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, paths []string, opt *Options) ([]lint.Problem, error) {
341 salt, err := computeSalt()
343 return nil, fmt.Errorf("could not compute salt for cache: %s", err)
353 CumulativeCheckers: cums,
354 GoVersion: opt.GoVersion,
356 RepeatAnalyzers: opt.RepeatAnalyzers,
358 l.Stats.PrintAnalyzerMeasurement = opt.PrintAnalyzerMeasurement
359 cfg := &packages.Config{}
364 cfg.BuildFlags = append(cfg.BuildFlags, "-tags", opt.Tags)
367 printStats := func() {
368 // Individual stats are read atomically, but overall there
369 // is no synchronisation. For printing rough progress
370 // information, this doesn't matter.
371 switch atomic.LoadUint32(&l.Stats.State) {
372 case lint.StateInitializing:
373 fmt.Fprintln(os.Stderr, "Status: initializing")
374 case lint.StateGraph:
375 fmt.Fprintln(os.Stderr, "Status: loading package graph")
376 case lint.StateProcessing:
377 fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d; Problems: %d\n",
378 atomic.LoadUint32(&l.Stats.ProcessedInitialPackages),
379 atomic.LoadUint32(&l.Stats.InitialPackages),
380 atomic.LoadUint32(&l.Stats.ProcessedPackages),
381 atomic.LoadUint32(&l.Stats.TotalPackages),
382 atomic.LoadUint32(&l.Stats.ActiveWorkers),
383 atomic.LoadUint32(&l.Stats.TotalWorkers),
384 atomic.LoadUint32(&l.Stats.Problems),
386 case lint.StateCumulative:
387 fmt.Fprintln(os.Stderr, "Status: processing cumulative checkers")
390 if len(infoSignals) > 0 {
391 ch := make(chan os.Signal, 1)
392 signal.Notify(ch, infoSignals...)
393 defer signal.Stop(ch)
401 ps, err := l.Lint(cfg, paths)
405 var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
407 func parsePos(pos string) token.Position {
408 if pos == "-" || pos == "" {
409 return token.Position{}
411 parts := posRe.FindStringSubmatch(pos)
413 panic(fmt.Sprintf("internal error: malformed position %q", pos))
416 line, _ := strconv.Atoi(parts[2])
417 col, _ := strconv.Atoi(parts[3])
418 return token.Position{
425 func InitializeAnalyzers(docs map[string]*lint.Documentation, analyzers map[string]*analysis.Analyzer) map[string]*analysis.Analyzer {
426 out := make(map[string]*analysis.Analyzer, len(analyzers))
427 for k, v := range analyzers {
434 panic(fmt.Sprintf("missing documentation for check %s", k))
436 vc.Doc = doc.String()
437 if vc.Flags.Usage == nil {
438 fs := flag.NewFlagSet("", flag.PanicOnError)
439 fs.Var(NewVersionFlag(), "go", "Target Go version")