vikunja-api/vendor/honnef.co/go/tools/lint/lintutil/util.go

395 lines
9.1 KiB
Go
Raw Normal View History

2018-12-28 23:15:05 +01:00
// Copyright (c) 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.
// Package lintutil provides helpers for writing linter command lines.
package lintutil // import "honnef.co/go/tools/lint/lintutil"
import (
"errors"
"flag"
"fmt"
"go/build"
"go/token"
2019-02-18 20:32:41 +01:00
"log"
2018-12-28 23:15:05 +01:00
"os"
2019-02-18 20:32:41 +01:00
"regexp"
"runtime"
2019-04-22 12:59:42 +02:00
"runtime/debug"
2019-02-18 20:32:41 +01:00
"runtime/pprof"
2018-12-28 23:15:05 +01:00
"strconv"
"strings"
2019-02-18 20:32:41 +01:00
"time"
2018-12-28 23:15:05 +01:00
2019-02-18 20:32:41 +01:00
"honnef.co/go/tools/config"
2018-12-28 23:15:05 +01:00
"honnef.co/go/tools/lint"
2019-02-18 20:32:41 +01:00
"honnef.co/go/tools/lint/lintutil/format"
2018-12-28 23:15:05 +01:00
"honnef.co/go/tools/version"
2019-02-18 20:32:41 +01:00
"golang.org/x/tools/go/packages"
2018-12-28 23:15:05 +01:00
)
func usage(name string, flags *flag.FlagSet) func() {
return func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
fmt.Fprintf(os.Stderr, "Flags:\n")
flags.PrintDefaults()
}
}
func parseIgnore(s string) ([]lint.Ignore, error) {
var out []lint.Ignore
if len(s) == 0 {
return nil, nil
}
for _, part := range strings.Fields(s) {
p := strings.Split(part, ":")
if len(p) != 2 {
return nil, errors.New("malformed ignore string")
}
path := p[0]
checks := strings.Split(p[1], ",")
out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks})
}
return out, nil
}
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 errors.New("invalid Go version")
}
if s[0] != '1' {
return errors.New("invalid Go version")
}
if s[1] != '.' {
return errors.New("invalid Go version")
}
i, err := strconv.Atoi(s[2:])
*v = versionFlag(i)
return err
}
func (v *versionFlag) Get() interface{} {
return int(*v)
}
2019-02-18 20:32:41 +01:00
type list []string
func (list *list) String() string {
return `"` + strings.Join(*list, ",") + `"`
}
func (list *list) Set(s string) error {
if s == "" {
*list = nil
return nil
}
*list = strings.Split(s, ",")
return nil
}
2018-12-28 23:15:05 +01:00
func FlagSet(name string) *flag.FlagSet {
flags := flag.NewFlagSet("", flag.ExitOnError)
flags.Usage = usage(name, flags)
flags.String("tags", "", "List of `build tags`")
2019-02-18 20:32:41 +01:00
flags.String("ignore", "", "Deprecated: use linter directives instead")
2018-12-28 23:15:05 +01:00
flags.Bool("tests", true, "Include tests")
flags.Bool("version", false, "Print version and exit")
flags.Bool("show-ignored", false, "Don't filter ignored problems")
2019-02-18 20:32:41 +01:00
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
2019-04-22 12:59:42 +02:00
flags.String("explain", "", "Print description of `check`")
2019-02-18 20:32:41 +01:00
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
flags.Bool("debug.print-stats", false, "Print debug statistics")
flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
flags.String("debug.memprofile", "", "Write memory profile to `file`")
checks := list{"inherit"}
fail := list{"all"}
flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
2018-12-28 23:15:05 +01:00
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))
}
flags.Var(version, "go", "Target Go `version` in the format '1.x'")
return flags
}
2019-04-22 12:59:42 +02:00
func findCheck(cs []lint.Checker, check string) (lint.Check, bool) {
for _, c := range cs {
for _, cc := range c.Checks() {
if cc.ID == check {
return cc, true
}
}
}
return lint.Check{}, false
}
2019-02-18 20:32:41 +01:00
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
2019-04-22 12:59:42 +02:00
if _, ok := os.LookupEnv("GOGC"); !ok {
debug.SetGCPercent(50)
}
2018-12-28 23:15:05 +01:00
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
2019-02-18 20:32:41 +01:00
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
2018-12-28 23:15:05 +01:00
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
2019-04-22 12:59:42 +02:00
explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
2018-12-28 23:15:05 +01:00
2019-02-18 20:32:41 +01:00
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
cfg := config.Config{}
cfg.Checks = *fs.Lookup("checks").Value.(*list)
exit := func(code int) {
if cpuProfile != "" {
pprof.StopCPUProfile()
}
if memProfile != "" {
f, err := os.Create(memProfile)
if err != nil {
panic(err)
}
runtime.GC()
pprof.WriteHeapProfile(f)
}
os.Exit(code)
}
if cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
}
2018-12-28 23:15:05 +01:00
if printVersion {
version.Print()
2019-02-18 20:32:41 +01:00
exit(0)
2018-12-28 23:15:05 +01:00
}
2019-04-22 12:59:42 +02:00
if explain != "" {
check, ok := findCheck(cs, explain)
if !ok {
fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
exit(1)
}
if check.Doc == "" {
fmt.Fprintln(os.Stderr, explain, "has no documentation")
exit(1)
}
fmt.Println(check.Doc)
exit(0)
}
2019-02-18 20:32:41 +01:00
ps, err := Lint(cs, fs.Args(), &Options{
2018-12-28 23:15:05 +01:00
Tags: strings.Fields(tags),
LintTests: tests,
Ignores: ignore,
GoVersion: goVersion,
ReturnIgnored: showIgnored,
2019-02-18 20:32:41 +01:00
Config: cfg,
MaxConcurrentJobs: maxConcurrentJobs,
PrintStats: printStats,
2018-12-28 23:15:05 +01:00
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
2019-02-18 20:32:41 +01:00
exit(1)
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
var f format.Formatter
switch formatter {
2018-12-28 23:15:05 +01:00
case "text":
2019-02-18 20:32:41 +01:00
f = format.Text{W: os.Stdout}
case "stylish":
f = &format.Stylish{W: os.Stdout}
2018-12-28 23:15:05 +01:00
case "json":
2019-02-18 20:32:41 +01:00
f = format.JSON{W: os.Stdout}
2018-12-28 23:15:05 +01:00
default:
2019-02-18 20:32:41 +01:00
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
exit(2)
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
var (
total int
errors int
warnings int
)
fail := *fs.Lookup("fail").Value.(*list)
var allChecks []string
2018-12-28 23:15:05 +01:00
for _, p := range ps {
2019-02-18 20:32:41 +01:00
allChecks = append(allChecks, p.Check)
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
shouldExit := lint.FilterChecks(allChecks, fail)
total = len(ps)
for _, p := range ps {
if shouldExit[p.Check] {
errors++
} else {
p.Severity = lint.Warning
warnings++
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
f.Format(p)
}
if f, ok := f.(format.Statter); ok {
f.Stats(total, errors, warnings)
}
if errors > 0 {
exit(1)
2018-12-28 23:15:05 +01:00
}
}
type Options struct {
2019-02-18 20:32:41 +01:00
Config config.Config
2018-12-28 23:15:05 +01:00
Tags []string
LintTests bool
Ignores string
GoVersion int
ReturnIgnored bool
2019-02-18 20:32:41 +01:00
MaxConcurrentJobs int
PrintStats bool
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, error) {
stats := lint.PerfStats{
CheckerInits: map[string]time.Duration{},
}
2018-12-28 23:15:05 +01:00
if opt == nil {
opt = &Options{}
}
ignores, err := parseIgnore(opt.Ignores)
if err != nil {
return nil, err
}
2019-02-18 20:32:41 +01:00
conf := &packages.Config{
Mode: packages.LoadAllSyntax,
Tests: opt.LintTests,
BuildFlags: []string{
"-tags=" + strings.Join(opt.Tags, " "),
2018-12-28 23:15:05 +01:00
},
}
2019-02-18 20:32:41 +01:00
t := time.Now()
if len(paths) == 0 {
paths = []string{"."}
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
pkgs, err := packages.Load(conf, paths...)
2018-12-28 23:15:05 +01:00
if err != nil {
return nil, err
}
2019-02-18 20:32:41 +01:00
stats.PackageLoading = time.Since(t)
2019-04-22 12:59:42 +02:00
runtime.GC()
2018-12-28 23:15:05 +01:00
2019-02-18 20:32:41 +01:00
var problems []lint.Problem
workingPkgs := make([]*packages.Package, 0, len(pkgs))
for _, pkg := range pkgs {
if pkg.IllTyped {
problems = append(problems, compileErrors(pkg)...)
} else {
workingPkgs = append(workingPkgs, pkg)
2018-12-28 23:15:05 +01:00
}
}
2019-02-18 20:32:41 +01:00
if len(workingPkgs) == 0 {
return problems, nil
}
l := &lint.Linter{
Checkers: cs,
Ignores: ignores,
GoVersion: opt.GoVersion,
ReturnIgnored: opt.ReturnIgnored,
Config: opt.Config,
MaxConcurrentJobs: opt.MaxConcurrentJobs,
PrintStats: opt.PrintStats,
}
problems = append(problems, l.Lint(workingPkgs, &stats)...)
2018-12-28 23:15:05 +01:00
return problems, nil
}
2019-02-18 20:32:41 +01:00
var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
func parsePos(pos string) token.Position {
if pos == "-" || pos == "" {
return token.Position{}
}
parts := posRe.FindStringSubmatch(pos)
if parts == nil {
panic(fmt.Sprintf("internal error: malformed position %q", pos))
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
file := parts[1]
line, _ := strconv.Atoi(parts[2])
col, _ := strconv.Atoi(parts[3])
return token.Position{
Filename: file,
Line: line,
Column: col,
2018-12-28 23:15:05 +01:00
}
}
2019-02-18 20:32:41 +01:00
func compileErrors(pkg *packages.Package) []lint.Problem {
if !pkg.IllTyped {
return nil
}
if len(pkg.Errors) == 0 {
// transitively ill-typed
var ps []lint.Problem
for _, imp := range pkg.Imports {
ps = append(ps, compileErrors(imp)...)
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
return ps
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
var ps []lint.Problem
for _, err := range pkg.Errors {
p := lint.Problem{
Position: parsePos(err.Pos),
Text: err.Msg,
Check: "compile",
}
ps = append(ps, p)
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
return ps
2018-12-28 23:15:05 +01:00
}
2019-02-18 20:32:41 +01:00
func ProcessArgs(name string, cs []lint.Checker, args []string) {
2018-12-28 23:15:05 +01:00
flags := FlagSet(name)
flags.Parse(args)
ProcessFlagSet(cs, flags)
}