gitee.com/lonely0422/gometalinter.git@v3.0.1-0.20190307123442-32416ab75314+incompatible/linters.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  
    11  	kingpin "gopkg.in/alecthomas/kingpin.v3-unstable"
    12  )
    13  
    14  type LinterConfig struct {
    15  	Command           string
    16  	Pattern           string
    17  	InstallFrom       string
    18  	PartitionStrategy partitionStrategy
    19  	IsFast            bool
    20  	defaultEnabled    bool
    21  }
    22  
    23  type Linter struct {
    24  	LinterConfig
    25  	Name  string
    26  	regex *regexp.Regexp
    27  }
    28  
    29  // NewLinter returns a new linter from a config
    30  func NewLinter(name string, config LinterConfig) (*Linter, error) {
    31  	if p, ok := predefinedPatterns[config.Pattern]; ok {
    32  		config.Pattern = p
    33  	}
    34  	regex, err := regexp.Compile("(?m:" + config.Pattern + ")")
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	if config.PartitionStrategy == nil {
    39  		config.PartitionStrategy = partitionPathsAsDirectories
    40  	}
    41  	return &Linter{
    42  		LinterConfig: config,
    43  		Name:         name,
    44  		regex:        regex,
    45  	}, nil
    46  }
    47  
    48  func (l *Linter) String() string {
    49  	return l.Name
    50  }
    51  
    52  var predefinedPatterns = map[string]string{
    53  	"PATH:LINE:COL:MESSAGE": `^(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
    54  	"PATH:LINE:MESSAGE":     `^(?P<path>.*?\.go):(?P<line>\d+):\s*(?P<message>.*)$`,
    55  }
    56  
    57  func getLinterByName(name string, overrideConf LinterConfig) *Linter {
    58  	conf := defaultLinters[name]
    59  	if val := overrideConf.Command; val != "" {
    60  		conf.Command = val
    61  	}
    62  	if val := overrideConf.Pattern; val != "" {
    63  		conf.Pattern = val
    64  	}
    65  	if val := overrideConf.InstallFrom; val != "" {
    66  		conf.InstallFrom = val
    67  	}
    68  	if overrideConf.IsFast {
    69  		conf.IsFast = true
    70  	}
    71  	if val := overrideConf.PartitionStrategy; val != nil {
    72  		conf.PartitionStrategy = val
    73  	}
    74  
    75  	linter, _ := NewLinter(name, conf)
    76  	return linter
    77  }
    78  
    79  func parseLinterConfigSpec(name string, spec string) (LinterConfig, error) {
    80  	parts := strings.SplitN(spec, ":", 2)
    81  	if len(parts) < 2 {
    82  		return LinterConfig{}, fmt.Errorf("linter spec needs at least two components")
    83  	}
    84  
    85  	config := defaultLinters[name]
    86  	config.Command, config.Pattern = parts[0], parts[1]
    87  	if predefined, ok := predefinedPatterns[config.Pattern]; ok {
    88  		config.Pattern = predefined
    89  	}
    90  
    91  	return config, nil
    92  }
    93  
    94  func makeInstallCommand(linters ...string) []string {
    95  	cmd := []string{"get"}
    96  	if config.VendoredLinters {
    97  		cmd = []string{"install"}
    98  	} else {
    99  		if config.Update {
   100  			cmd = append(cmd, "-u")
   101  		}
   102  		if config.Force {
   103  			cmd = append(cmd, "-f")
   104  		}
   105  		if config.DownloadOnly {
   106  			cmd = append(cmd, "-d")
   107  		}
   108  	}
   109  	if config.Debug {
   110  		cmd = append(cmd, "-v")
   111  	}
   112  	cmd = append(cmd, linters...)
   113  	return cmd
   114  }
   115  
   116  func installLintersWithOneCommand(targets []string) error {
   117  	cmd := makeInstallCommand(targets...)
   118  	debug("go %s", strings.Join(cmd, " "))
   119  	c := exec.Command("go", cmd...) // nolint: gosec
   120  	c.Stdout = os.Stdout
   121  	c.Stderr = os.Stderr
   122  	return c.Run()
   123  }
   124  
   125  func installLintersIndividually(targets []string) {
   126  	failed := []string{}
   127  	for _, target := range targets {
   128  		cmd := makeInstallCommand(target)
   129  		debug("go %s", strings.Join(cmd, " "))
   130  		c := exec.Command("go", cmd...) // nolint: gosec
   131  		c.Stdout = os.Stdout
   132  		c.Stderr = os.Stderr
   133  		if err := c.Run(); err != nil {
   134  			warning("failed to install %s: %s", target, err)
   135  			failed = append(failed, target)
   136  		}
   137  	}
   138  	if len(failed) > 0 {
   139  		kingpin.Fatalf("failed to install the following linters: %s", strings.Join(failed, ", "))
   140  	}
   141  }
   142  
   143  func installLinters() {
   144  	names := make([]string, 0, len(defaultLinters))
   145  	targets := make([]string, 0, len(defaultLinters))
   146  	for name, config := range defaultLinters {
   147  		if config.InstallFrom == "" {
   148  			continue
   149  		}
   150  		names = append(names, name)
   151  		targets = append(targets, config.InstallFrom)
   152  	}
   153  	sort.Strings(names)
   154  	namesStr := strings.Join(names, "\n  ")
   155  	if config.DownloadOnly {
   156  		fmt.Printf("Downloading:\n  %s\n", namesStr)
   157  	} else {
   158  		fmt.Printf("Installing:\n  %s\n", namesStr)
   159  	}
   160  	err := installLintersWithOneCommand(targets)
   161  	if err == nil {
   162  		return
   163  	}
   164  	warning("failed to install one or more linters: %s (installing individually)", err)
   165  	installLintersIndividually(targets)
   166  }
   167  
   168  func getDefaultLinters() []*Linter {
   169  	out := []*Linter{}
   170  	for name, config := range defaultLinters {
   171  		linter, err := NewLinter(name, config)
   172  		kingpin.FatalIfError(err, "invalid linter %q", name)
   173  		out = append(out, linter)
   174  	}
   175  	return out
   176  }
   177  
   178  func defaultEnabled() []string {
   179  	enabled := []string{}
   180  	for name, config := range defaultLinters {
   181  		if config.defaultEnabled {
   182  			enabled = append(enabled, name)
   183  		}
   184  	}
   185  	return enabled
   186  }
   187  
   188  func validateLinters(linters map[string]*Linter, config *Config) error {
   189  	var unknownLinters []string
   190  	for name := range linters {
   191  		if _, isDefault := defaultLinters[name]; !isDefault {
   192  			if _, isCustom := config.Linters[name]; !isCustom {
   193  				unknownLinters = append(unknownLinters, name)
   194  			}
   195  		}
   196  	}
   197  	if len(unknownLinters) > 0 {
   198  		return fmt.Errorf("unknown linters: %s", strings.Join(unknownLinters, ", "))
   199  	}
   200  	return nil
   201  }
   202  
   203  const vetPattern = `^(?:vet:.*?\.go:\s+(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*))|((?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*))|(?:(?P<path>.*?\.go):(?P<line>\d+):\s*(?P<message>.*))$`
   204  
   205  var defaultLinters = map[string]LinterConfig{
   206  	"maligned": {
   207  		Command:           "maligned",
   208  		Pattern:           `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`,
   209  		InstallFrom:       "github.com/mdempsky/maligned",
   210  		PartitionStrategy: partitionPathsAsPackages,
   211  		defaultEnabled:    true,
   212  	},
   213  	"deadcode": {
   214  		Command:           "deadcode",
   215  		Pattern:           `^deadcode: (?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
   216  		InstallFrom:       "github.com/tsenart/deadcode",
   217  		PartitionStrategy: partitionPathsAsDirectories,
   218  		defaultEnabled:    true,
   219  	},
   220  	"dupl": {
   221  		Command:           `dupl -plumbing -threshold {duplthreshold}`,
   222  		Pattern:           `^(?P<path>.*?\.go):(?P<line>\d+)-\d+:\s*(?P<message>.*)$`,
   223  		InstallFrom:       "github.com/mibk/dupl",
   224  		PartitionStrategy: partitionPathsAsFiles,
   225  		IsFast:            true,
   226  	},
   227  	"errcheck": {
   228  		Command:           `errcheck -abspath {not_tests=-ignoretests}`,
   229  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   230  		InstallFrom:       "github.com/kisielk/errcheck",
   231  		PartitionStrategy: partitionPathsAsPackages,
   232  		defaultEnabled:    true,
   233  	},
   234  	"gosec": {
   235  		Command:           `gosec -fmt=csv`,
   236  		Pattern:           `^(?P<path>.*?\.go),(?P<line>\d+)(-\d+)?,(?P<message>[^,]+,[^,]+,[^,]+)`,
   237  		InstallFrom:       "github.com/securego/gosec/cmd/gosec",
   238  		PartitionStrategy: partitionPathsAsPackages,
   239  		defaultEnabled:    true,
   240  		IsFast:            true,
   241  	},
   242  	"gochecknoinits": {
   243  		Command:           `gochecknoinits`,
   244  		Pattern:           `^(?P<path>.*?\.go):(?P<line>\d+) (?P<message>.*)`,
   245  		InstallFrom:       "4d63.com/gochecknoinits",
   246  		PartitionStrategy: partitionPathsAsDirectories,
   247  		defaultEnabled:    false,
   248  		IsFast:            true,
   249  	},
   250  	"gochecknoglobals": {
   251  		Command:           `gochecknoglobals`,
   252  		Pattern:           `^(?P<path>.*?\.go):(?P<line>\d+) (?P<message>.*)`,
   253  		InstallFrom:       "4d63.com/gochecknoglobals",
   254  		PartitionStrategy: partitionPathsAsDirectories,
   255  		defaultEnabled:    false,
   256  		IsFast:            true,
   257  	},
   258  	"goconst": {
   259  		Command:           `goconst -min-occurrences {min_occurrences} -min-length {min_const_length}`,
   260  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   261  		InstallFrom:       "github.com/jgautheron/goconst/cmd/goconst",
   262  		PartitionStrategy: partitionPathsAsDirectories,
   263  		defaultEnabled:    true,
   264  		IsFast:            true,
   265  	},
   266  	"gocyclo": {
   267  		Command:           `gocyclo -over {mincyclo}`,
   268  		Pattern:           `^(?P<cyclo>\d+)\s+\S+\s(?P<function>\S+)\s+(?P<path>.*?\.go):(?P<line>\d+):(\d+)$`,
   269  		InstallFrom:       "github.com/alecthomas/gocyclo",
   270  		PartitionStrategy: partitionPathsAsDirectories,
   271  		defaultEnabled:    true,
   272  		IsFast:            true,
   273  	},
   274  	"gofmt": {
   275  		Command:           `gofmt -l -s`,
   276  		Pattern:           `^(?P<path>.*?\.go)$`,
   277  		PartitionStrategy: partitionPathsAsFiles,
   278  		IsFast:            true,
   279  	},
   280  	"goimports": {
   281  		Command:           `goimports -l`,
   282  		Pattern:           `^(?P<path>.*?\.go)$`,
   283  		InstallFrom:       "golang.org/x/tools/cmd/goimports",
   284  		PartitionStrategy: partitionPathsAsFiles,
   285  		IsFast:            true,
   286  	},
   287  	"golint": {
   288  		Command:           `golint -min_confidence {min_confidence}`,
   289  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   290  		InstallFrom:       "github.com/golang/lint/golint",
   291  		PartitionStrategy: partitionPathsAsDirectories,
   292  		defaultEnabled:    true,
   293  		IsFast:            true,
   294  	},
   295  	"gotype": {
   296  		Command:           `gotype -e {tests=-t}`,
   297  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   298  		InstallFrom:       "golang.org/x/tools/cmd/gotype",
   299  		PartitionStrategy: partitionPathsByDirectory,
   300  		defaultEnabled:    true,
   301  		IsFast:            true,
   302  	},
   303  	"gotypex": {
   304  		Command:           `gotype -e -x`,
   305  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   306  		InstallFrom:       "golang.org/x/tools/cmd/gotype",
   307  		PartitionStrategy: partitionPathsByDirectory,
   308  		defaultEnabled:    true,
   309  		IsFast:            true,
   310  	},
   311  	"ineffassign": {
   312  		Command:           `ineffassign -n`,
   313  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   314  		InstallFrom:       "github.com/gordonklaus/ineffassign",
   315  		PartitionStrategy: partitionPathsAsDirectories,
   316  		defaultEnabled:    true,
   317  		IsFast:            true,
   318  	},
   319  	"interfacer": {
   320  		Command:           `interfacer`,
   321  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   322  		InstallFrom:       "mvdan.cc/interfacer",
   323  		PartitionStrategy: partitionPathsAsPackages,
   324  		defaultEnabled:    true,
   325  	},
   326  	"lll": {
   327  		Command:           `lll -g -l {maxlinelength}`,
   328  		Pattern:           `PATH:LINE:MESSAGE`,
   329  		InstallFrom:       "github.com/walle/lll/cmd/lll",
   330  		PartitionStrategy: partitionPathsAsFiles,
   331  		IsFast:            true,
   332  	},
   333  	"misspell": {
   334  		Command:           `misspell -j 1 --locale "{misspelllocale}"`,
   335  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   336  		InstallFrom:       "github.com/client9/misspell/cmd/misspell",
   337  		PartitionStrategy: partitionPathsAsFiles,
   338  		IsFast:            true,
   339  	},
   340  	"nakedret": {
   341  		Command:           `nakedret`,
   342  		Pattern:           `^(?P<path>.*?\.go):(?P<line>\d+)\s*(?P<message>.*)$`,
   343  		InstallFrom:       "github.com/alexkohler/nakedret",
   344  		PartitionStrategy: partitionPathsAsDirectories,
   345  	},
   346  	"safesql": {
   347  		Command:           `safesql`,
   348  		Pattern:           `^- (?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+)$`,
   349  		InstallFrom:       "github.com/stripe/safesql",
   350  		PartitionStrategy: partitionPathsAsPackages,
   351  	},
   352  	"staticcheck": {
   353  		Command:           `staticcheck`,
   354  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   355  		InstallFrom:       "honnef.co/go/tools/cmd/staticcheck",
   356  		PartitionStrategy: partitionPathsAsPackages,
   357  		defaultEnabled: true,
   358  	},
   359  	"structcheck": {
   360  		Command:           `structcheck {tests=-t}`,
   361  		Pattern:           `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`,
   362  		InstallFrom:       "github.com/opennota/check/cmd/structcheck",
   363  		PartitionStrategy: partitionPathsAsPackages,
   364  		defaultEnabled:    true,
   365  	},
   366  	"test": {
   367  		Command:           `go test`,
   368  		Pattern:           `(?m:^\t(?P<path>.*?\.go):(?P<line>\d+): (?P<message>.+)$)`,
   369  		PartitionStrategy: partitionPathsAsPackages,
   370  	},
   371  	"testify": {
   372  		Command:           `go test`,
   373  		Pattern:           `(?m:^\s+Error Trace:\s+(?P<path>.+?.go):(?P<line>\d+)\n\s+Error:\s+(?P<message>.+?)[:\s]*$)`,
   374  		PartitionStrategy: partitionPathsAsPackages,
   375  	},
   376  	"unconvert": {
   377  		Command:           `unconvert`,
   378  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   379  		InstallFrom:       "github.com/mdempsky/unconvert",
   380  		PartitionStrategy: partitionPathsAsPackages,
   381  		defaultEnabled:    true,
   382  	},
   383  	"unparam": {
   384  		Command:           `unparam {not_tests=-tests=false}`,
   385  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   386  		InstallFrom:       "mvdan.cc/unparam",
   387  		PartitionStrategy: partitionPathsAsPackages,
   388  	},
   389  	"varcheck": {
   390  		Command:           `varcheck`,
   391  		Pattern:           `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
   392  		InstallFrom:       "github.com/opennota/check/cmd/varcheck",
   393  		PartitionStrategy: partitionPathsAsPackages,
   394  		defaultEnabled:    true,
   395  	},
   396  	"vet": {
   397  		Command:           `go vet`,
   398  		Pattern:           vetPattern,
   399  		PartitionStrategy: partitionPathsAsPackages,
   400  		defaultEnabled:    true,
   401  		IsFast:            true,
   402  	},
   403  	"vetshadow": {
   404  		Command:           `go vet --shadow`,
   405  		Pattern:           vetPattern,
   406  		PartitionStrategy: partitionPathsAsPackages,
   407  		defaultEnabled:    true,
   408  		IsFast:            true,
   409  	},
   410  }