github.com/pankona/gometalinter@v2.0.11+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+),(?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 "gosimple": { 296 Command: `gosimple`, 297 Pattern: `PATH:LINE:COL:MESSAGE`, 298 InstallFrom: "honnef.co/go/tools/cmd/gosimple", 299 PartitionStrategy: partitionPathsAsPackages, 300 }, 301 "gotype": { 302 Command: `gotype -e {tests=-t}`, 303 Pattern: `PATH:LINE:COL:MESSAGE`, 304 InstallFrom: "golang.org/x/tools/cmd/gotype", 305 PartitionStrategy: partitionPathsByDirectory, 306 defaultEnabled: true, 307 IsFast: true, 308 }, 309 "gotypex": { 310 Command: `gotype -e -x`, 311 Pattern: `PATH:LINE:COL:MESSAGE`, 312 InstallFrom: "golang.org/x/tools/cmd/gotype", 313 PartitionStrategy: partitionPathsByDirectory, 314 defaultEnabled: true, 315 IsFast: true, 316 }, 317 "ineffassign": { 318 Command: `ineffassign -n`, 319 Pattern: `PATH:LINE:COL:MESSAGE`, 320 InstallFrom: "github.com/gordonklaus/ineffassign", 321 PartitionStrategy: partitionPathsAsDirectories, 322 defaultEnabled: true, 323 IsFast: true, 324 }, 325 "interfacer": { 326 Command: `interfacer`, 327 Pattern: `PATH:LINE:COL:MESSAGE`, 328 InstallFrom: "mvdan.cc/interfacer", 329 PartitionStrategy: partitionPathsAsPackages, 330 defaultEnabled: true, 331 }, 332 "lll": { 333 Command: `lll -g -l {maxlinelength}`, 334 Pattern: `PATH:LINE:MESSAGE`, 335 InstallFrom: "github.com/walle/lll/cmd/lll", 336 PartitionStrategy: partitionPathsAsFiles, 337 IsFast: true, 338 }, 339 "megacheck": { 340 Command: `megacheck`, 341 Pattern: `PATH:LINE:COL:MESSAGE`, 342 InstallFrom: "honnef.co/go/tools/cmd/megacheck", 343 PartitionStrategy: partitionPathsAsPackages, 344 defaultEnabled: true, 345 }, 346 "misspell": { 347 Command: `misspell -j 1 --locale "{misspelllocale}"`, 348 Pattern: `PATH:LINE:COL:MESSAGE`, 349 InstallFrom: "github.com/client9/misspell/cmd/misspell", 350 PartitionStrategy: partitionPathsAsFiles, 351 IsFast: true, 352 }, 353 "nakedret": { 354 Command: `nakedret`, 355 Pattern: `^(?P<path>.*?\.go):(?P<line>\d+)\s*(?P<message>.*)$`, 356 InstallFrom: "github.com/alexkohler/nakedret", 357 PartitionStrategy: partitionPathsAsDirectories, 358 }, 359 "safesql": { 360 Command: `safesql`, 361 Pattern: `^- (?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+)$`, 362 InstallFrom: "github.com/stripe/safesql", 363 PartitionStrategy: partitionPathsAsPackages, 364 }, 365 "staticcheck": { 366 Command: `staticcheck`, 367 Pattern: `PATH:LINE:COL:MESSAGE`, 368 InstallFrom: "honnef.co/go/tools/cmd/staticcheck", 369 PartitionStrategy: partitionPathsAsPackages, 370 }, 371 "structcheck": { 372 Command: `structcheck {tests=-t}`, 373 Pattern: `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`, 374 InstallFrom: "github.com/opennota/check/cmd/structcheck", 375 PartitionStrategy: partitionPathsAsPackages, 376 defaultEnabled: true, 377 }, 378 "test": { 379 Command: `go test`, 380 Pattern: `(?m:^\t(?P<path>.*?\.go):(?P<line>\d+): (?P<message>.+)$)`, 381 PartitionStrategy: partitionPathsAsPackages, 382 }, 383 "testify": { 384 Command: `go test`, 385 Pattern: `(?m:^\s+Error Trace:\s+(?P<path>.+?.go):(?P<line>\d+)\n\s+Error:\s+(?P<message>.+?)[:\s]*$)`, 386 PartitionStrategy: partitionPathsAsPackages, 387 }, 388 "unconvert": { 389 Command: `unconvert`, 390 Pattern: `PATH:LINE:COL:MESSAGE`, 391 InstallFrom: "github.com/mdempsky/unconvert", 392 PartitionStrategy: partitionPathsAsPackages, 393 defaultEnabled: true, 394 }, 395 "unparam": { 396 Command: `unparam {not_tests=-tests=false}`, 397 Pattern: `PATH:LINE:COL:MESSAGE`, 398 InstallFrom: "mvdan.cc/unparam", 399 PartitionStrategy: partitionPathsAsPackages, 400 }, 401 "unused": { 402 Command: `unused`, 403 Pattern: `PATH:LINE:COL:MESSAGE`, 404 InstallFrom: "honnef.co/go/tools/cmd/unused", 405 PartitionStrategy: partitionPathsAsPackages, 406 }, 407 "varcheck": { 408 Command: `varcheck`, 409 Pattern: `^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`, 410 InstallFrom: "github.com/opennota/check/cmd/varcheck", 411 PartitionStrategy: partitionPathsAsPackages, 412 defaultEnabled: true, 413 }, 414 "vet": { 415 Command: `go vet`, 416 Pattern: vetPattern, 417 PartitionStrategy: partitionPathsAsPackages, 418 defaultEnabled: true, 419 IsFast: true, 420 }, 421 "vetshadow": { 422 Command: `go vet --shadow`, 423 Pattern: vetPattern, 424 PartitionStrategy: partitionPathsAsPackages, 425 defaultEnabled: true, 426 IsFast: true, 427 }, 428 }