group of fixes after running on golang source code

This commit is contained in:
golangci 2018-05-08 17:13:16 +03:00
parent 6f384926cf
commit d993d3a264
31 changed files with 331 additions and 103 deletions

4
Gopkg.lock generated
View File

@ -101,7 +101,7 @@
"lib/cfg", "lib/cfg",
"lib/whitelist" "lib/whitelist"
] ]
revision = "31bebf17d90d40b728c41fecdd5acf5fb8654fc7" revision = "1a9ab8120ec96a014df3afab2d6c47f7e12d8928"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -140,7 +140,7 @@
"golangci", "golangci",
"internal/errcheck" "internal/errcheck"
] ]
revision = "7a3d63cfc6fbd9e46f2e551f9b8d1e9c69bffab1" revision = "d2c0791055c651c4bb6798ee1aacbd27ad377aa8"
source = "github.com/golangci/errcheck" source = "github.com/golangci/errcheck"
[[projects]] [[projects]]

View File

@ -1,2 +1,2 @@
test: test:
go test -v ./... go test -v -race ./...

View File

@ -3,6 +3,7 @@ package commands
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/golangci/golangci-lint/pkg" "github.com/golangci/golangci-lint/pkg"
@ -20,7 +21,7 @@ func (e *Executor) initLinters() {
func printLinterConfigs(lcs []pkg.LinterConfig) { func printLinterConfigs(lcs []pkg.LinterConfig) {
for _, lc := range lcs { for _, lc := range lcs {
fmt.Printf("%s: %s\n", color.YellowString(lc.Linter.Name()), lc.Desc) fmt.Printf("%s: %s\n", color.YellowString(lc.Linter.Name()), lc.Linter.Desc())
} }
} }
@ -39,5 +40,15 @@ func (e Executor) executeLinters(cmd *cobra.Command, args []string) {
color.Red("\nDisabled by default linters:\n") color.Red("\nDisabled by default linters:\n")
printLinterConfigs(disabledLCs) printLinterConfigs(disabledLCs)
color.Green("\nLinters presets:")
for _, p := range pkg.AllPresets() {
linters := pkg.GetAllLintersForPreset(p)
linterNames := []string{}
for _, linter := range linters {
linterNames = append(linterNames, linter.Name())
}
fmt.Printf("%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", "))
}
os.Exit(0) os.Exit(0)
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"go/build" "go/build"
"go/token"
"log" "log"
"strings" "strings"
"time" "time"
@ -38,16 +39,16 @@ func (e *Executor) initRun() {
fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))) fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|")))
runCmd.Flags().BoolVar(&rc.PrintIssuedLine, "print-issued-lines", true, "Print lines of code with issue") runCmd.Flags().BoolVar(&rc.PrintIssuedLine, "print-issued-lines", true, "Print lines of code with issue")
runCmd.Flags().BoolVar(&rc.PrintLinterName, "print-linter-name", true, "Print linter name in issue line") runCmd.Flags().BoolVar(&rc.PrintLinterName, "print-linter-name", true, "Print linter name in issue line")
runCmd.Flags().BoolVar(&rc.PrintWelcomeMessage, "print-welcome", true, "Print welcome message")
runCmd.Flags().IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", runCmd.Flags().IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
1, "Exit code when issues were found") 1, "Exit code when issues were found")
runCmd.Flags().StringSliceVar(&rc.BuildTags, "build-tags", []string{}, "Build tags (not all linters support them)") runCmd.Flags().StringSliceVar(&rc.BuildTags, "build-tags", []string{}, "Build tags (not all linters support them)")
runCmd.Flags().BoolVar(&rc.Errcheck.CheckClose, "errcheck.check-close", false, "Errcheck: check missed error checks on .Close() calls")
runCmd.Flags().BoolVar(&rc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results") runCmd.Flags().BoolVar(&rc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results")
runCmd.Flags().BoolVar(&rc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()") runCmd.Flags().BoolVar(&rc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
runCmd.Flags().BoolVar(&rc.Govet.CheckShadowing, "govet.check-shadowing", true, "Govet: check for shadowed variables") runCmd.Flags().BoolVar(&rc.Govet.CheckShadowing, "govet.check-shadowing", false, "Govet: check for shadowed variables")
runCmd.Flags().Float64Var(&rc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it") runCmd.Flags().Float64Var(&rc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it")
@ -90,6 +91,9 @@ func (e *Executor) initRun() {
runCmd.Flags().StringVar(&rc.DiffFromRevision, "new-from-rev", "", "Show only new issues created after git revision `REV`") runCmd.Flags().StringVar(&rc.DiffFromRevision, "new-from-rev", "", "Show only new issues created after git revision `REV`")
runCmd.Flags().StringVar(&rc.DiffPatchFilePath, "new-from-patch", "", "Show only new issues created in git patch with file path `PATH`") runCmd.Flags().StringVar(&rc.DiffPatchFilePath, "new-from-patch", "", "Show only new issues created in git patch with file path `PATH`")
runCmd.Flags().BoolVar(&rc.AnalyzeTests, "tests", false, "Analyze tests (*_test.go)") runCmd.Flags().BoolVar(&rc.AnalyzeTests, "tests", false, "Analyze tests (*_test.go)")
runCmd.Flags().StringSliceVarP(&rc.Presets, "presets", "p", []string{},
fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(pkg.AllPresets(), "|")))
} }
func isFullImportNeeded(linters []pkg.Linter) bool { func isFullImportNeeded(linters []pkg.Linter) bool {
@ -208,10 +212,15 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (chan result.
if len(excludePatterns) != 0 { if len(excludePatterns) != 0 {
excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|")) excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|"))
} }
fset := token.NewFileSet()
if lintCtx.Program != nil {
fset = lintCtx.Program.Fset
}
runner := pkg.SimpleRunner{ runner := pkg.SimpleRunner{
Processors: []processors.Processor{ Processors: []processors.Processor{
processors.NewExclude(excludeTotalPattern), processors.NewExclude(excludeTotalPattern),
processors.NewNolint(lintCtx.Program.Fset), processors.NewCgo(),
processors.NewNolint(fset),
processors.NewUniqByLine(), processors.NewUniqByLine(),
processors.NewDiff(e.cfg.Run.Diff, e.cfg.Run.DiffFromRevision, e.cfg.Run.DiffPatchFilePath), processors.NewDiff(e.cfg.Run.Diff, e.cfg.Run.DiffFromRevision, e.cfg.Run.DiffPatchFilePath),
processors.NewMaxPerFileFromLinter(), processors.NewMaxPerFileFromLinter(),
@ -231,6 +240,10 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
logrus.Infof("Run took %s", time.Since(startedAt)) logrus.Infof("Run took %s", time.Since(startedAt))
}(time.Now()) }(time.Now())
if e.cfg.Run.PrintWelcomeMessage {
fmt.Println("Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)")
}
f := func() error { f := func() error {
issues, err := e.runAnalysis(ctx, args) issues, err := e.runAnalysis(ctx, args)
if err != nil { if err != nil {

View File

@ -15,6 +15,7 @@ const (
var OutFormats = []string{OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON} var OutFormats = []string{OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON}
var DefaultExcludePatterns = []string{ var DefaultExcludePatterns = []string{
"Error return value of `(os\\.Std(out|err)\\.Write|.*\\.Close)` is not checked",
"should have comment", "should have comment",
"comment on exported method", "comment on exported method",
"G104", // disable what errcheck does: it reports on Close etc "G104", // disable what errcheck does: it reports on Close etc
@ -33,14 +34,14 @@ type Run struct { // nolint:maligned
BuildTags []string BuildTags []string
OutFormat string OutFormat string
PrintIssuedLine bool PrintIssuedLine bool
PrintLinterName bool PrintLinterName bool
PrintWelcomeMessage bool
ExitCodeIfIssuesFound int ExitCodeIfIssuesFound int
Errcheck struct { Errcheck struct {
CheckClose bool
CheckTypeAssertions bool CheckTypeAssertions bool
CheckAssignToBlank bool CheckAssignToBlank bool
} }
@ -83,6 +84,8 @@ type Run struct { // nolint:maligned
EnableAllLinters bool EnableAllLinters bool
DisableAllLinters bool DisableAllLinters bool
Presets []string
ExcludePatterns []string ExcludePatterns []string
UseDefaultExcludes bool UseDefaultExcludes bool

View File

@ -3,6 +3,7 @@ package pkg
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"sync" "sync"
"github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/config"
@ -10,12 +11,61 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const (
PresetFormatting = "format"
PresetComplexity = "complexity"
PresetStyle = "style"
PresetBugs = "bugs"
PresetUnused = "unused"
PresetPerformance = "performance"
)
func AllPresets() []string {
return []string{PresetBugs, PresetUnused, PresetFormatting, PresetStyle, PresetComplexity, PresetPerformance}
}
func allPresetsSet() map[string]bool {
ret := map[string]bool{}
for _, p := range AllPresets() {
ret[p] = true
}
return ret
}
type LinterConfig struct { type LinterConfig struct {
Desc string
Linter Linter Linter Linter
EnabledByDefault bool EnabledByDefault bool
DoesFullImport bool DoesFullImport bool
NeedsSSARepr bool NeedsSSARepr bool
InPresets []string
}
func (lc LinterConfig) WithFullImport() LinterConfig {
lc.DoesFullImport = true
return lc
}
func (lc LinterConfig) WithSSA() LinterConfig {
lc.DoesFullImport = true
lc.NeedsSSARepr = true
return lc
}
func (lc LinterConfig) WithPresets(presets ...string) LinterConfig {
lc.InPresets = presets
return lc
}
func (lc LinterConfig) WithDisabledByDefault() LinterConfig {
lc.EnabledByDefault = false
return lc
}
func newLinterConfig(linter Linter) LinterConfig {
return LinterConfig{
Linter: linter,
EnabledByDefault: true,
}
} }
var nameToLC map[string]LinterConfig var nameToLC map[string]LinterConfig
@ -37,46 +87,26 @@ func GetLinterConfig(name string) *LinterConfig {
return &lc return &lc
} }
func enabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig {
return LinterConfig{
EnabledByDefault: true,
Linter: linter,
Desc: desc,
DoesFullImport: doesFullImport,
NeedsSSARepr: needsSSARepr,
}
}
func disabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig {
return LinterConfig{
EnabledByDefault: false,
Linter: linter,
Desc: desc,
DoesFullImport: doesFullImport,
NeedsSSARepr: needsSSARepr,
}
}
func GetAllSupportedLinterConfigs() []LinterConfig { func GetAllSupportedLinterConfigs() []LinterConfig {
return []LinterConfig{ return []LinterConfig{
enabledByDefault(golinters.Govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string", false, false), newLinterConfig(golinters.Govet{}).WithPresets(PresetBugs),
enabledByDefault(golinters.Errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases", true, false), newLinterConfig(golinters.Errcheck{}).WithFullImport().WithPresets(PresetBugs),
enabledByDefault(golinters.Golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", false, false), newLinterConfig(golinters.Golint{}).WithDisabledByDefault().WithPresets(PresetStyle),
enabledByDefault(golinters.Megacheck{}, "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused", true, true), newLinterConfig(golinters.Megacheck{}).WithSSA().WithPresets(PresetBugs, PresetUnused, PresetStyle),
enabledByDefault(golinters.Gas{}, "Inspects source code for security problems", true, false), newLinterConfig(golinters.Gas{}).WithFullImport().WithPresets(PresetBugs),
enabledByDefault(golinters.Structcheck{}, "Finds unused struct fields", true, false), newLinterConfig(golinters.Structcheck{}).WithFullImport().WithPresets(PresetUnused),
enabledByDefault(golinters.Varcheck{}, "Finds unused global variables and constants", true, false), newLinterConfig(golinters.Varcheck{}).WithFullImport().WithPresets(PresetUnused),
enabledByDefault(golinters.Interfacer{}, "Linter that suggests narrower interface types", true, true), newLinterConfig(golinters.Interfacer{}).WithDisabledByDefault().WithSSA().WithPresets(PresetStyle),
enabledByDefault(golinters.Unconvert{}, "Remove unnecessary type conversions", true, false), newLinterConfig(golinters.Unconvert{}).WithDisabledByDefault().WithFullImport().WithPresets(PresetStyle),
enabledByDefault(golinters.Ineffassign{}, "Detects when assignments to existing variables are not used", false, false), newLinterConfig(golinters.Ineffassign{}).WithPresets(PresetUnused),
enabledByDefault(golinters.Dupl{}, "Tool for code clone detection", false, false), newLinterConfig(golinters.Dupl{}).WithDisabledByDefault().WithPresets(PresetStyle),
enabledByDefault(golinters.Goconst{}, "Finds repeated strings that could be replaced by a constant", false, false), newLinterConfig(golinters.Goconst{}).WithDisabledByDefault().WithPresets(PresetStyle),
enabledByDefault(golinters.Deadcode{}, "Finds unused code", true, false), newLinterConfig(golinters.Deadcode{}).WithFullImport().WithPresets(PresetUnused),
enabledByDefault(golinters.Gocyclo{}, "Computes and checks the cyclomatic complexity of functions", false, false), newLinterConfig(golinters.Gocyclo{}).WithDisabledByDefault().WithPresets(PresetComplexity),
disabledByDefault(golinters.Gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification", false, false), newLinterConfig(golinters.Gofmt{}).WithDisabledByDefault().WithPresets(PresetFormatting),
disabledByDefault(golinters.Gofmt{UseGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports", false, false), newLinterConfig(golinters.Gofmt{UseGoimports: true}).WithDisabledByDefault().WithPresets(PresetFormatting),
disabledByDefault(golinters.Maligned{}, "Tool to detect Go structs that would take less memory if their fields were sorted", true, false), newLinterConfig(golinters.Maligned{}).WithFullImport().WithDisabledByDefault().WithPresets(PresetPerformance),
} }
} }
@ -132,6 +162,17 @@ func validateEnabledDisabledLintersConfig(cfg *config.Run) error {
} }
} }
allPresets := allPresetsSet()
for _, p := range cfg.Presets {
if !allPresets[p] {
return fmt.Errorf("no such preset %q: only next presets exist: (%s)", p, strings.Join(AllPresets(), "|"))
}
}
if len(cfg.Presets) != 0 && cfg.EnableAllLinters {
return fmt.Errorf("--presets is incompatible with --enable-all")
}
if cfg.EnableAllLinters && cfg.DisableAllLinters { if cfg.EnableAllLinters && cfg.DisableAllLinters {
return fmt.Errorf("--enable-all and --disable-all options must not be combined") return fmt.Errorf("--enable-all and --disable-all options must not be combined")
} }
@ -164,12 +205,29 @@ func validateEnabledDisabledLintersConfig(cfg *config.Run) error {
return nil return nil
} }
func GetAllLintersForPreset(p string) []Linter {
ret := []Linter{}
for _, lc := range GetAllSupportedLinterConfigs() {
for _, ip := range lc.InPresets {
if p == ip {
ret = append(ret, lc.Linter)
break
}
}
}
return ret
}
func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) { func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) {
if err := validateEnabledDisabledLintersConfig(cfg); err != nil { if err := validateEnabledDisabledLintersConfig(cfg); err != nil {
return nil, err return nil, err
} }
resultLintersSet := map[string]Linter{} resultLintersSet := map[string]Linter{}
switch { switch {
case len(cfg.Presets) != 0:
break // imply --disable-all
case cfg.EnableAllLinters: case cfg.EnableAllLinters:
resultLintersSet = lintersToMap(getAllSupportedLinters()) resultLintersSet = lintersToMap(getAllSupportedLinters())
case cfg.DisableAllLinters: case cfg.DisableAllLinters:
@ -182,6 +240,32 @@ func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) {
resultLintersSet[name] = getLinterByName(name) resultLintersSet[name] = getLinterByName(name)
} }
// XXX: hacks because of sub-linters in megacheck
megacheckWasEnabledByUser := resultLintersSet["megacheck"] != nil
if !megacheckWasEnabledByUser {
cfg.Megacheck.EnableGosimple = false
cfg.Megacheck.EnableStaticcheck = false
cfg.Megacheck.EnableUnused = false
}
for _, p := range cfg.Presets {
for _, linter := range GetAllLintersForPreset(p) {
resultLintersSet[linter.Name()] = linter
}
if !megacheckWasEnabledByUser {
if p == PresetBugs {
cfg.Megacheck.EnableStaticcheck = true
}
if p == PresetStyle {
cfg.Megacheck.EnableGosimple = true
}
if p == PresetUnused {
cfg.Megacheck.EnableUnused = true
}
}
}
for _, name := range cfg.DisabledLinters { for _, name := range cfg.DisabledLinters {
delete(resultLintersSet, name) delete(resultLintersSet, name)
} }
@ -192,7 +276,10 @@ func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) {
resultLinters = append(resultLinters, linter) resultLinters = append(resultLinters, linter)
resultLinterNames = append(resultLinterNames, name) resultLinterNames = append(resultLinterNames, name)
} }
logrus.Infof("Enabled linters: %s", resultLinterNames) logrus.Infof("Active linters: %s", resultLinterNames)
if len(cfg.Presets) != 0 {
logrus.Infof("Active presets: %s", cfg.Presets)
}
return resultLinters, nil return resultLinters, nil
} }

View File

@ -56,6 +56,8 @@ func testOneSource(t *testing.T, sourcePath string) {
"--print-issued-lines=false", "--print-issued-lines=false",
"--print-linter-name=false", "--print-linter-name=false",
"--out-format=line-number", "--out-format=line-number",
"--print-welcome=false",
"--govet.check-shadowing=true",
sourcePath) sourcePath)
runGoErrchk(cmd, t) runGoErrchk(cmd, t)
} }

View File

@ -14,6 +14,10 @@ func (Deadcode) Name() string {
return "deadcode" return "deadcode"
} }
func (Deadcode) Desc() string {
return "Finds unused code"
}
func (d Deadcode) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (d Deadcode) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues, err := deadcodeAPI.Run(lintCtx.Program) issues, err := deadcodeAPI.Run(lintCtx.Program)
if err != nil { if err != nil {

View File

@ -15,6 +15,10 @@ func (Dupl) Name() string {
return "dupl" return "dupl"
} }
func (Dupl) Desc() string {
return "Tool for code clone detection"
}
func (d Dupl) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (d Dupl) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues, err := duplAPI.Run(lintCtx.Paths.Files, lintCtx.RunCfg().Dupl.Threshold) issues, err := duplAPI.Run(lintCtx.Paths.Files, lintCtx.RunCfg().Dupl.Threshold)
if err != nil { if err != nil {

View File

@ -3,7 +3,6 @@ package golinters
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result"
errcheckAPI "github.com/kisielk/errcheck/golangci" errcheckAPI "github.com/kisielk/errcheck/golangci"
@ -15,6 +14,10 @@ func (Errcheck) Name() string {
return "errcheck" return "errcheck"
} }
func (Errcheck) Desc() string {
return "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"
}
func (e Errcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (e Errcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
errCfg := &lintCtx.RunCfg().Errcheck errCfg := &lintCtx.RunCfg().Errcheck
issues, err := errcheckAPI.Run(lintCtx.Program, errCfg.CheckAssignToBlank, errCfg.CheckTypeAssertions) issues, err := errcheckAPI.Run(lintCtx.Program, errCfg.CheckAssignToBlank, errCfg.CheckTypeAssertions)
@ -24,10 +27,6 @@ func (e Errcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, er
var res []result.Issue var res []result.Issue
for _, i := range issues { for _, i := range issues {
if !errCfg.CheckClose && strings.HasSuffix(i.FuncName, ".Close") {
continue
}
var text string var text string
if i.FuncName != "" { if i.FuncName != "" {
text = fmt.Sprintf("Error return value of %s is not checked", formatCode(i.FuncName, lintCtx.RunCfg())) text = fmt.Sprintf("Error return value of %s is not checked", formatCode(i.FuncName, lintCtx.RunCfg()))

View File

@ -19,6 +19,10 @@ func (Gas) Name() string {
return "gas" return "gas"
} }
func (Gas) Desc() string {
return "Inspects source code for security problems"
}
func (lint Gas) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (lint Gas) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
gasConfig := gas.NewConfig() gasConfig := gas.NewConfig()
enabledRules := rules.Generate() enabledRules := rules.Generate()

View File

@ -14,17 +14,27 @@ func (Goconst) Name() string {
return "goconst" return "goconst"
} }
func (Goconst) Desc() string {
return "Finds repeated strings that could be replaced by a constant"
}
func (lint Goconst) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (lint Goconst) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues, err := goconstAPI.Run(lintCtx.Paths.Files, true, var goconstIssues []goconstAPI.Issue
lintCtx.RunCfg().Goconst.MinStringLen, // TODO: make it cross-package: pass package names inside goconst
lintCtx.RunCfg().Goconst.MinOccurrencesCount, for _, files := range lintCtx.Paths.FilesGrouppedByDirs() {
) issues, err := goconstAPI.Run(files, true,
if err != nil { lintCtx.RunCfg().Goconst.MinStringLen,
return nil, err lintCtx.RunCfg().Goconst.MinOccurrencesCount,
)
if err != nil {
return nil, err
}
goconstIssues = append(goconstIssues, issues...)
} }
var res []result.Issue var res []result.Issue
for _, i := range issues { for _, i := range goconstIssues {
textBegin := fmt.Sprintf("string %s has %d occurrences", formatCode(i.Str, lintCtx.RunCfg()), i.OccurencesCount) textBegin := fmt.Sprintf("string %s has %d occurrences", formatCode(i.Str, lintCtx.RunCfg()), i.OccurencesCount)
var textEnd string var textEnd string
if i.MatchingConst == "" { if i.MatchingConst == "" {

View File

@ -14,6 +14,10 @@ func (Gocyclo) Name() string {
return "gocyclo" return "gocyclo"
} }
func (Gocyclo) Desc() string {
return "Computes and checks the cyclomatic complexity of functions"
}
func (g Gocyclo) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (g Gocyclo) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
stats := gocycloAPI.Run(lintCtx.Paths.MixedPaths()) stats := gocycloAPI.Run(lintCtx.Paths.MixedPaths())

View File

@ -25,6 +25,14 @@ func (g Gofmt) Name() string {
return "gofmt" return "gofmt"
} }
func (g Gofmt) Desc() string {
if g.UseGoimports {
return "Goimports does everything that gofmt does. Additionally it checks unused imports"
}
return "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification"
}
func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) { func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) {
lines := bytes.Split(h.Body, []byte{'\n'}) lines := bytes.Split(h.Body, []byte{'\n'})
lineNumber := int(h.OrigStartLine - 1) lineNumber := int(h.OrigStartLine - 1)

View File

@ -15,6 +15,10 @@ func (Golint) Name() string {
return "golint" return "golint"
} }
func (Golint) Desc() string {
return "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"
}
func (g Golint) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (g Golint) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
var issues []result.Issue var issues []result.Issue
for _, pkgFiles := range lintCtx.Paths.FilesGrouppedByDirs() { for _, pkgFiles := range lintCtx.Paths.FilesGrouppedByDirs() {

View File

@ -13,14 +13,23 @@ func (Govet) Name() string {
return "govet" return "govet"
} }
func (Govet) Desc() string {
return "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string"
}
func (g Govet) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (g Govet) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues, err := govetAPI.Run(lintCtx.Paths.MixedPaths(), lintCtx.RunCfg().BuildTags, lintCtx.RunCfg().Govet.CheckShadowing) // TODO: check .S asm files: govet can do it if pass dirs
if err != nil { var govetIssues []govetAPI.Issue
return nil, err for _, files := range lintCtx.Paths.FilesGrouppedByDirs() {
issues, err := govetAPI.Run(files, lintCtx.RunCfg().Govet.CheckShadowing)
if err != nil {
return nil, err
}
govetIssues = append(govetIssues, issues...)
} }
var res []result.Issue var res []result.Issue
for _, i := range issues { for _, i := range govetIssues {
res = append(res, result.Issue{ res = append(res, result.Issue{
Pos: i.Pos, Pos: i.Pos,
Text: i.Message, Text: i.Message,

View File

@ -14,6 +14,10 @@ func (Ineffassign) Name() string {
return "ineffassign" return "ineffassign"
} }
func (Ineffassign) Desc() string {
return "Detects when assignments to existing variables are not used"
}
func (lint Ineffassign) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (lint Ineffassign) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues := ineffassignAPI.Run(lintCtx.Paths.Files) issues := ineffassignAPI.Run(lintCtx.Paths.Files)

View File

@ -14,6 +14,10 @@ func (Interfacer) Name() string {
return "interfacer" return "interfacer"
} }
func (Interfacer) Desc() string {
return "Linter that suggests narrower interface types"
}
func (lint Interfacer) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (lint Interfacer) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
c := new(check.Checker) c := new(check.Checker)
c.Program(lintCtx.Program) c.Program(lintCtx.Program)

View File

@ -14,6 +14,10 @@ func (Maligned) Name() string {
return "maligned" return "maligned"
} }
func (Maligned) Desc() string {
return "Tool to detect Go structs that would take less memory if their fields were sorted"
}
func (m Maligned) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (m Maligned) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues := malignedAPI.Run(lintCtx.Program) issues := malignedAPI.Run(lintCtx.Program)

View File

@ -13,6 +13,10 @@ func (Megacheck) Name() string {
return "megacheck" return "megacheck"
} }
func (Megacheck) Desc() string {
return "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused"
}
func (m Megacheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (m Megacheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
c := lintCtx.RunCfg().Megacheck c := lintCtx.RunCfg().Megacheck
issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, lintCtx.SSAProgram, c.EnableStaticcheck, c.EnableGosimple, c.EnableUnused) issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, lintCtx.SSAProgram, c.EnableStaticcheck, c.EnableGosimple, c.EnableUnused)

View File

@ -14,6 +14,10 @@ func (Structcheck) Name() string {
return "structcheck" return "structcheck"
} }
func (Structcheck) Desc() string {
return "Finds unused struct fields"
}
func (s Structcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (s Structcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues := structcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Structcheck.CheckExportedFields) issues := structcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Structcheck.CheckExportedFields)

View File

@ -13,6 +13,10 @@ func (Unconvert) Name() string {
return "unconvert" return "unconvert"
} }
func (Unconvert) Desc() string {
return "Remove unnecessary type conversions"
}
func (lint Unconvert) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (lint Unconvert) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
positions := unconvertAPI.Run(lintCtx.Program) positions := unconvertAPI.Run(lintCtx.Program)
var res []result.Issue var res []result.Issue

View File

@ -14,6 +14,10 @@ func (Varcheck) Name() string {
return "varcheck" return "varcheck"
} }
func (Varcheck) Desc() string {
return "Finds unused global variables and constants"
}
func (v Varcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { func (v Varcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
issues := varcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Varcheck.CheckExportedFields) issues := varcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Varcheck.CheckExportedFields)

View File

@ -10,4 +10,5 @@ import (
type Linter interface { type Linter interface {
Run(ctx context.Context, lintCtx *golinters.Context) ([]result.Issue, error) Run(ctx context.Context, lintCtx *golinters.Context) ([]result.Issue, error)
Name() string Name() string
Desc() string
} }

View File

@ -0,0 +1,30 @@
package processors
import (
"strings"
"github.com/golangci/golangci-lint/pkg/result"
)
type Cgo struct {
}
var _ Processor = Cgo{}
func NewCgo() *Cgo {
return &Cgo{}
}
func (p Cgo) Name() string {
return "cgo"
}
func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) {
return filterIssues(issues, func(i *result.Issue) bool {
// some linters (.e.g gas, deadcode) return incorrect filepaths for cgo issues,
// it breaks next processing, so skip them
return !strings.HasSuffix(i.FilePath(), "/C")
}), nil
}
func (Cgo) Finish() {}

View File

@ -1,6 +1,7 @@
package processors package processors
import ( import (
"fmt"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
@ -43,7 +44,7 @@ func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
if comments == nil { if comments == nil {
file, err := parser.ParseFile(p.fset, i.FilePath(), nil, parser.ParseComments) file, err := parser.ParseFile(p.fset, i.FilePath(), nil, parser.ParseComments)
if err != nil { if err != nil {
return true, err return false, fmt.Errorf("can't parse file %s", i.FilePath())
} }
comments = extractFileComments(p.fset, file.Comments...) comments = extractFileComments(p.fset, file.Comments...)

View File

@ -1,6 +1,10 @@
package processors package processors
import "github.com/golangci/golangci-lint/pkg/result" import (
"fmt"
"github.com/golangci/golangci-lint/pkg/result"
)
func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue { func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue {
retIssues := make([]result.Issue, 0, len(issues)) retIssues := make([]result.Issue, 0, len(issues))
@ -18,8 +22,9 @@ func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool,
for _, i := range issues { for _, i := range issues {
ok, err := filter(&i) ok, err := filter(&i)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("can't filter issue %#v: %s", i, err)
} }
if ok { if ok {
retIssues = append(retIssues, i) retIssues = append(retIssues, i)
} }

View File

@ -34,11 +34,7 @@ func (r *SimpleRunner) runLinter(ctx context.Context, linter Linter, lintCtx *go
startedAt := time.Now() startedAt := time.Now()
res, err = linter.Run(ctx, lintCtx) res, err = linter.Run(ctx, lintCtx)
if err == nil && len(res) != 0 { logrus.Infof("worker #%d: linter %s took %s and found %d issues (before processing them)", i, linter.Name(),
res = r.processIssues(ctx, res)
}
logrus.Infof("worker #%d: linter %s took %s and found %d issues", i, linter.Name(),
time.Since(startedAt), len(res)) time.Since(startedAt), len(res))
return return
} }
@ -120,6 +116,11 @@ func (r SimpleRunner) runGo(ctx context.Context, linters []Linter, lintCtx *goli
} }
finishedN++ finishedN++
if len(res.issues) != 0 {
res.issues = r.processIssues(ctx, res.issues)
}
for _, i := range res.issues { for _, i := range res.issues {
retIssues <- i retIssues <- i
} }

View File

@ -32,3 +32,8 @@ func IgnoreCloseInDeferMissingErrHandling() {
panic(resp) panic(resp)
} }
func IgnoreStdxWrite() {
os.Stdout.Write([]byte{})
os.Stderr.Write([]byte{})
}

View File

@ -1,9 +1,7 @@
package govet package govet
import ( import (
"fmt"
"go/token" "go/token"
"os"
"strings" "strings"
) )
@ -14,7 +12,7 @@ type Issue struct {
var foundIssues []Issue var foundIssues []Issue
func Run(paths, buildTags []string, checkShadowing bool) ([]Issue, error) { func Run(files []string, checkShadowing bool) ([]Issue, error) {
foundIssues = nil foundIssues = nil
if checkShadowing { if checkShadowing {
@ -26,37 +24,16 @@ func Run(paths, buildTags []string, checkShadowing bool) ([]Issue, error) {
} }
} }
tagList = buildTags
initPrintFlags() initPrintFlags()
initUnusedFlags() initUnusedFlags()
for _, name := range paths { filesRun = true
// Is it a directory? for _, name := range files {
fi, err := os.Stat(name) if !strings.HasSuffix(name, "_test.go") {
if err != nil { includesNonTest = true
warnf("error walking tree: %s", err)
continue
}
if fi.IsDir() {
dirsRun = true
} else {
filesRun = true
if !strings.HasSuffix(name, "_test.go") {
includesNonTest = true
}
} }
} }
if dirsRun && filesRun { if doPackage(files, nil) == nil {
return nil, fmt.Errorf("can't mix dirs and files")
}
if dirsRun {
for _, name := range paths {
doPackageDir(name)
}
return foundIssues, nil
}
if doPackage(paths, nil) == nil {
return nil, nil return nil, nil
} }

View File

@ -226,6 +226,18 @@ type visitor struct {
errors []UncheckedError errors []UncheckedError
} }
func getSelName(sel *ast.SelectorExpr) string {
if ident, ok := sel.X.(*ast.Ident); ok {
return fmt.Sprintf("%s.%s", ident.Name, sel.Sel.Name)
}
if s, ok := sel.X.(*ast.SelectorExpr); ok {
return fmt.Sprintf("%s.%s", getSelName(s), sel.Sel.Name)
}
return ""
}
func (v *visitor) fullName(call *ast.CallExpr) (string, bool) { func (v *visitor) fullName(call *ast.CallExpr) (string, bool) {
if ident, ok := call.Fun.(*ast.Ident); ok { if ident, ok := call.Fun.(*ast.Ident); ok {
return ident.Name, true return ident.Name, true
@ -235,6 +247,12 @@ func (v *visitor) fullName(call *ast.CallExpr) (string, bool) {
if !ok { if !ok {
return "", false return "", false
} }
name := getSelName(sel)
if name != "" {
return name, true
}
fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func) fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func)
if !ok { if !ok {
// Shouldn't happen, but be paranoid // Shouldn't happen, but be paranoid