github.com/wgliang/gometalinter@v2.0.6-0.20180523041418-a75adcf7cb0e+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: gas
   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: gas
   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  	"gas": {
   235  		Command:           `gas -fmt=csv`,
   236  		Pattern:           `^(?P<path>.*?\.go),(?P<line>\d+),(?P<message>[^,]+,[^,]+,[^,]+)`,
   237  		InstallFrom:       "github.com/GoASTScanner/gas",
   238  		PartitionStrategy: partitionPathsAsFiles,
   239  		defaultEnabled:    true,
   240  		IsFast:            true,
   241  	},
   242  	"goconst": {
   243  		Command:           `goconst -min-occurrences {min_occurrences} -min-length {min_const_length}`,
   244  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   245  		InstallFrom:       "github.com/jgautheron/goconst/cmd/goconst",
   246  		PartitionStrategy: partitionPathsAsDirectories,
   247  		defaultEnabled:    true,
   248  		IsFast:            true,
   249  	},
   250  	"gocyclo": {
   251  		Command:           `gocyclo -over {mincyclo}`,
   252  		Pattern:           `^(?P<cyclo>\d+)\s+\S+\s(?P<function>\S+)\s+(?P<path>.*?\.go):(?P<line>\d+):(\d+)$`,
   253  		InstallFrom:       "github.com/alecthomas/gocyclo",
   254  		PartitionStrategy: partitionPathsAsDirectories,
   255  		defaultEnabled:    true,
   256  		IsFast:            true,
   257  	},
   258  	"gofmt": {
   259  		Command:           `gofmt -l -s`,
   260  		Pattern:           `^(?P<path>.*?\.go)$`,
   261  		PartitionStrategy: partitionPathsAsFiles,
   262  		IsFast:            true,
   263  	},
   264  	"goimports": {
   265  		Command:           `goimports -l`,
   266  		Pattern:           `^(?P<path>.*?\.go)$`,
   267  		InstallFrom:       "golang.org/x/tools/cmd/goimports",
   268  		PartitionStrategy: partitionPathsAsFiles,
   269  		IsFast:            true,
   270  	},
   271  	"golint": {
   272  		Command:           `golint -min_confidence {min_confidence}`,
   273  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   274  		InstallFrom:       "github.com/golang/lint/golint",
   275  		PartitionStrategy: partitionPathsAsDirectories,
   276  		defaultEnabled:    true,
   277  		IsFast:            true,
   278  	},
   279  	"gosimple": {
   280  		Command:           `gosimple`,
   281  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   282  		InstallFrom:       "honnef.co/go/tools/cmd/gosimple",
   283  		PartitionStrategy: partitionPathsAsPackages,
   284  	},
   285  	"gotype": {
   286  		Command:           `gotype -e {tests=-t}`,
   287  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   288  		InstallFrom:       "golang.org/x/tools/cmd/gotype",
   289  		PartitionStrategy: partitionPathsByDirectory,
   290  		defaultEnabled:    true,
   291  		IsFast:            true,
   292  	},
   293  	"gotypex": {
   294  		Command:           `gotype -e -x`,
   295  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   296  		InstallFrom:       "golang.org/x/tools/cmd/gotype",
   297  		PartitionStrategy: partitionPathsByDirectory,
   298  		defaultEnabled:    true,
   299  		IsFast:            true,
   300  	},
   301  	"ineffassign": {
   302  		Command:           `ineffassign -n`,
   303  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   304  		InstallFrom:       "github.com/gordonklaus/ineffassign",
   305  		PartitionStrategy: partitionPathsAsDirectories,
   306  		defaultEnabled:    true,
   307  		IsFast:            true,
   308  	},
   309  	"interfacer": {
   310  		Command:           `interfacer`,
   311  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   312  		InstallFrom:       "mvdan.cc/interfacer",
   313  		PartitionStrategy: partitionPathsAsPackages,
   314  		defaultEnabled:    true,
   315  	},
   316  	"lll": {
   317  		Command:           `lll -g -l {maxlinelength}`,
   318  		Pattern:           `PATH:LINE:MESSAGE`,
   319  		InstallFrom:       "github.com/walle/lll/cmd/lll",
   320  		PartitionStrategy: partitionPathsAsFiles,
   321  		IsFast:            true,
   322  	},
   323  	"megacheck": {
   324  		Command:           `megacheck`,
   325  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   326  		InstallFrom:       "honnef.co/go/tools/cmd/megacheck",
   327  		PartitionStrategy: partitionPathsAsPackages,
   328  		defaultEnabled:    true,
   329  	},
   330  	"misspell": {
   331  		Command:           `misspell -j 1 --locale "{misspelllocale}"`,
   332  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   333  		InstallFrom:       "github.com/client9/misspell/cmd/misspell",
   334  		PartitionStrategy: partitionPathsAsFiles,
   335  		IsFast:            true,
   336  	},
   337  	"nakedret": {
   338  		Command:           `nakedret`,
   339  		Pattern:           `^(?P<path>.*?\.go):(?P<line>\d+)\s*(?P<message>.*)$`,
   340  		InstallFrom:       "github.com/alexkohler/nakedret",
   341  		PartitionStrategy: partitionPathsAsDirectories,
   342  	},
   343  	"safesql": {
   344  		Command:           `safesql`,
   345  		Pattern:           `^- (?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+)$`,
   346  		InstallFrom:       "github.com/stripe/safesql",
   347  		PartitionStrategy: partitionPathsAsPackages,
   348  	},
   349  	"staticcheck": {
   350  		Command:           `staticcheck`,
   351  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   352  		InstallFrom:       "honnef.co/go/tools/cmd/staticcheck",
   353  		PartitionStrategy: partitionPathsAsPackages,
   354  	},
   355  	"structcheck": {
   356  		Command:           `structcheck {tests=-t}`,
   357  		Pattern:           `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`,
   358  		InstallFrom:       "github.com/opennota/check/cmd/structcheck",
   359  		PartitionStrategy: partitionPathsAsPackages,
   360  		defaultEnabled:    true,
   361  	},
   362  	"test": {
   363  		Command:           `go test`,
   364  		Pattern:           `^--- FAIL: .*$\s+(?P<path>.*?\.go):(?P<line>\d+): (?P<message>.*)$`,
   365  		PartitionStrategy: partitionPathsAsPackages,
   366  	},
   367  	"testify": {
   368  		Command:           `go test`,
   369  		Pattern:           `Location:\s+(?P<path>.*?\.go):(?P<line>\d+)$\s+Error:\s+(?P<message>[^\n]+)`,
   370  		PartitionStrategy: partitionPathsAsPackages,
   371  	},
   372  	"unconvert": {
   373  		Command:           `unconvert`,
   374  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   375  		InstallFrom:       "github.com/mdempsky/unconvert",
   376  		PartitionStrategy: partitionPathsAsPackages,
   377  		defaultEnabled:    true,
   378  	},
   379  	"unparam": {
   380  		Command:           `unparam {not_tests=-tests=false}`,
   381  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   382  		InstallFrom:       "mvdan.cc/unparam",
   383  		PartitionStrategy: partitionPathsAsPackages,
   384  	},
   385  	"unused": {
   386  		Command:           `unused`,
   387  		Pattern:           `PATH:LINE:COL:MESSAGE`,
   388  		InstallFrom:       "honnef.co/go/tools/cmd/unused",
   389  		PartitionStrategy: partitionPathsAsPackages,
   390  	},
   391  	"varcheck": {
   392  		Command:           `varcheck`,
   393  		Pattern:           `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
   394  		InstallFrom:       "github.com/opennota/check/cmd/varcheck",
   395  		PartitionStrategy: partitionPathsAsPackages,
   396  		defaultEnabled:    true,
   397  	},
   398  	"vet": {
   399  		Command:           `go vet`,
   400  		Pattern:           vetPattern,
   401  		PartitionStrategy: partitionPathsAsPackages,
   402  		defaultEnabled:    true,
   403  		IsFast:            true,
   404  	},
   405  	"vetshadow": {
   406  		Command:           `go vet --shadow`,
   407  		Pattern:           vetPattern,
   408  		PartitionStrategy: partitionPathsAsPackages,
   409  		defaultEnabled:    true,
   410  		IsFast:            true,
   411  	},
   412  }