2018-05-05 09:24:37 +03:00
package commands
import (
"context"
"fmt"
2018-05-06 22:58:04 +03:00
"go/build"
2018-05-05 09:24:37 +03:00
"log"
"strings"
2018-05-06 19:08:34 +03:00
"time"
2018-05-05 09:24:37 +03:00
2018-05-07 09:48:43 +03:00
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
2018-05-05 19:43:18 +03:00
"github.com/golangci/golangci-lint/pkg"
2018-05-05 09:24:37 +03:00
"github.com/golangci/golangci-lint/pkg/config"
2018-05-06 22:58:04 +03:00
"github.com/golangci/golangci-lint/pkg/fsutils"
"github.com/golangci/golangci-lint/pkg/golinters"
2018-05-08 11:54:30 +03:00
"github.com/golangci/golangci-lint/pkg/printers"
2018-05-05 09:24:37 +03:00
"github.com/golangci/golangci-lint/pkg/result"
2018-05-05 19:43:18 +03:00
"github.com/golangci/golangci-lint/pkg/result/processors"
2018-05-07 21:44:40 +03:00
"github.com/sirupsen/logrus"
2018-05-05 09:24:37 +03:00
"github.com/spf13/cobra"
2018-05-06 22:58:04 +03:00
"golang.org/x/tools/go/loader"
2018-05-05 09:24:37 +03:00
)
2018-05-06 19:08:34 +03:00
const exitCodeIfFailure = 3
2018-05-05 09:24:37 +03:00
func ( e * Executor ) initRun ( ) {
var runCmd = & cobra . Command {
Use : "run" ,
Short : "Run linters" ,
Run : e . executeRun ,
}
e . rootCmd . AddCommand ( runCmd )
2018-05-05 19:43:18 +03:00
rc := & e . cfg . Run
runCmd . Flags ( ) . StringVar ( & rc . OutFormat , "out-format" ,
2018-05-05 09:24:37 +03:00
config . OutFormatColoredLineNumber ,
fmt . Sprintf ( "Format of output: %s" , strings . Join ( config . OutFormats , "|" ) ) )
2018-05-08 11:54:30 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . PrintIssuedLine , "print-issued-lines" , true , "Print lines of code with issue" )
2018-05-05 19:43:18 +03:00
runCmd . Flags ( ) . IntVar ( & rc . ExitCodeIfIssuesFound , "issues-exit-code" ,
2018-05-05 11:08:14 +03:00
1 , "Exit code when issues were found" )
2018-05-06 13:28:00 +03:00
runCmd . Flags ( ) . StringSliceVar ( & rc . BuildTags , "build-tags" , [ ] string { } , "Build tags (not all linters support them)" )
2018-05-05 19:43:18 +03:00
2018-05-06 13:25:50 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Errcheck . CheckClose , "errcheck.check-close" , false , "Errcheck: check missed error checks on .Close() calls" )
2018-05-06 07:20:12 +03:00
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()" )
2018-05-05 22:22:21 +03:00
2018-05-06 07:20:12 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Govet . CheckShadowing , "govet.check-shadowing" , true , "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" )
2018-05-06 09:41:48 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Gofmt . Simplify , "gofmt.simplify" , true , "Gofmt: simplify code" )
2018-05-06 13:25:50 +03:00
2018-05-06 15:24:45 +03:00
runCmd . Flags ( ) . IntVar ( & rc . Gocyclo . MinComplexity , "gocyclo.min-complexity" ,
2018-05-07 12:43:52 +03:00
50 , "Minimal complexity of function to report it" )
2018-05-06 15:24:45 +03:00
2018-05-06 20:28:59 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Structcheck . CheckExportedFields , "structcheck.exported-fields" , false , "Structcheck: report about unused exported struct fields" )
runCmd . Flags ( ) . BoolVar ( & rc . Varcheck . CheckExportedFields , "varcheck.exported-fields" , false , "Varcheck: report about unused exported variables" )
2018-05-06 21:08:53 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Maligned . SuggestNewOrder , "maligned.suggest-new" , false , "Maligned: print suggested more optimal struct fields ordering" )
2018-05-06 22:58:04 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . Megacheck . EnableStaticcheck , "megacheck.staticcheck" , true , "Megacheck: run Staticcheck sub-linter: staticcheck is go vet on steroids, applying a ton of static analysis checks" )
runCmd . Flags ( ) . BoolVar ( & rc . Megacheck . EnableGosimple , "megacheck.gosimple" , true , "Megacheck: run Gosimple sub-linter: gosimple is a linter for Go source code that specialises on simplifying code" )
runCmd . Flags ( ) . BoolVar ( & rc . Megacheck . EnableUnused , "megacheck.unused" , true , "Megacheck: run Unused sub-linter: unused checks Go code for unused constants, variables, functions and types" )
2018-05-07 12:43:52 +03:00
2018-05-07 09:09:10 +03:00
runCmd . Flags ( ) . IntVar ( & rc . Dupl . Threshold , "dupl.threshold" ,
2018-05-08 12:10:24 +03:00
150 , "Dupl: Minimal threshold to detect copy-paste" )
2018-05-07 12:43:52 +03:00
runCmd . Flags ( ) . IntVar ( & rc . Goconst . MinStringLen , "goconst.min-len" ,
3 , "Goconst: minimum constant string length" )
runCmd . Flags ( ) . IntVar ( & rc . Goconst . MinOccurrencesCount , "goconst.min-occurrences" ,
3 , "Goconst: minimum occurences of constant string count to trigger issue" )
2018-05-06 22:58:04 +03:00
2018-05-06 13:25:50 +03:00
runCmd . Flags ( ) . StringSliceVarP ( & rc . EnabledLinters , "enable" , "E" , [ ] string { } , "Enable specific linter" )
runCmd . Flags ( ) . StringSliceVarP ( & rc . DisabledLinters , "disable" , "D" , [ ] string { } , "Disable specific linter" )
runCmd . Flags ( ) . BoolVar ( & rc . EnableAllLinters , "enable-all" , false , "Enable all linters" )
runCmd . Flags ( ) . BoolVar ( & rc . DisableAllLinters , "disable-all" , false , "Disable all linters" )
2018-05-06 13:41:42 +03:00
2018-05-06 19:08:34 +03:00
runCmd . Flags ( ) . DurationVar ( & rc . Deadline , "deadline" , time . Second * 30 , "Deadline for total work" )
2018-05-08 12:10:24 +03:00
runCmd . Flags ( ) . StringSliceVarP ( & rc . ExcludePatterns , "exclude" , "e" , [ ] string { } , "Exclude issue by regexp" )
runCmd . Flags ( ) . BoolVar ( & rc . UseDefaultExcludes , "exclude-use-default" , true ,
fmt . Sprintf ( "Use or not use default excludes: (%s)" , strings . Join ( config . DefaultExcludePatterns , "|" ) ) )
2018-05-08 08:15:55 +03:00
runCmd . Flags ( ) . IntVar ( & rc . MaxIssuesPerLinter , "max-issues-per-linter" , 50 , "Maximum issues count per one linter. Set to 0 to disable" )
2018-05-08 09:55:38 +03:00
runCmd . Flags ( ) . BoolVarP ( & rc . Diff , "new" , "n" , false , "Show only new issues: if there are unstaged changes or untracked files, only those changes are shown, else only changes in HEAD~ are shown" )
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`" )
2018-05-05 09:24:37 +03:00
}
2018-05-06 22:58:04 +03:00
func isFullImportNeeded ( linters [ ] pkg . Linter ) bool {
for _ , linter := range linters {
lc := pkg . GetLinterConfig ( linter . Name ( ) )
if lc . DoesFullImport {
return true
}
}
return false
}
2018-05-07 09:48:43 +03:00
func isSSAReprNeeded ( linters [ ] pkg . Linter ) bool {
for _ , linter := range linters {
lc := pkg . GetLinterConfig ( linter . Name ( ) )
if lc . NeedsSSARepr {
return true
}
}
return false
}
2018-05-06 22:58:04 +03:00
func loadWholeAppIfNeeded ( ctx context . Context , linters [ ] pkg . Linter , cfg * config . Run , paths * fsutils . ProjectPaths ) ( * loader . Program , * loader . Config , error ) {
if ! isFullImportNeeded ( linters ) {
return nil , nil , nil
}
startedAt := time . Now ( )
defer func ( ) {
2018-05-07 21:44:40 +03:00
logrus . Infof ( "Program loading took %s" , time . Since ( startedAt ) )
2018-05-06 22:58:04 +03:00
} ( )
bctx := build . Default
bctx . BuildTags = append ( bctx . BuildTags , cfg . BuildTags ... )
loadcfg := & loader . Config {
2018-05-07 09:09:10 +03:00
Build : & bctx ,
AllowErrors : true , // Try to analyze event partially
2018-05-06 22:58:04 +03:00
}
const needTests = true // TODO: configure and take into account in paths resolver
rest , err := loadcfg . FromArgs ( paths . MixedPaths ( ) , needTests )
if err != nil {
return nil , nil , fmt . Errorf ( "can't parepare load config with paths: %s" , err )
}
if len ( rest ) > 0 {
return nil , nil , fmt . Errorf ( "unhandled loading paths: %v" , rest )
}
prog , err := loadcfg . Load ( )
if err != nil {
return nil , nil , fmt . Errorf ( "can't load paths: %s" , err )
}
return prog , loadcfg , nil
}
2018-05-07 09:48:43 +03:00
func buildSSAProgram ( ctx context . Context , lprog * loader . Program ) * ssa . Program {
startedAt := time . Now ( )
defer func ( ) {
2018-05-07 21:44:40 +03:00
logrus . Infof ( "SSA repr building took %s" , time . Since ( startedAt ) )
2018-05-07 09:48:43 +03:00
} ( )
ssaProg := ssautil . CreateProgram ( lprog , ssa . GlobalDebug )
ssaProg . Build ( )
return ssaProg
}
2018-05-06 22:58:04 +03:00
func buildLintCtx ( ctx context . Context , linters [ ] pkg . Linter , cfg * config . Config ) ( * golinters . Context , error ) {
args := cfg . Run . Args
if len ( args ) == 0 {
args = [ ] string { "./..." }
}
2018-05-07 17:14:24 +03:00
paths , err := fsutils . GetPathsForAnalysis ( ctx , args )
2018-05-06 22:58:04 +03:00
if err != nil {
return nil , err
}
prog , loaderConfig , err := loadWholeAppIfNeeded ( ctx , linters , & cfg . Run , paths )
if err != nil {
return nil , err
}
2018-05-07 09:48:43 +03:00
var ssaProg * ssa . Program
if prog != nil && isSSAReprNeeded ( linters ) {
ssaProg = buildSSAProgram ( ctx , prog )
}
2018-05-06 22:58:04 +03:00
return & golinters . Context {
Paths : paths ,
Cfg : cfg ,
Program : prog ,
2018-05-07 09:48:43 +03:00
SSAProgram : ssaProg ,
2018-05-06 22:58:04 +03:00
LoaderConfig : loaderConfig ,
} , nil
}
2018-05-07 21:44:40 +03:00
func ( e * Executor ) runAnalysis ( ctx context . Context , args [ ] string ) ( chan result . Issue , error ) {
2018-05-06 19:08:34 +03:00
e . cfg . Run . Args = args
2018-05-06 13:25:50 +03:00
2018-05-06 22:58:04 +03:00
linters , err := pkg . GetEnabledLinters ( ctx , & e . cfg . Run )
if err != nil {
return nil , err
}
lintCtx , err := buildLintCtx ( ctx , linters , e . cfg )
if err != nil {
return nil , err
}
2018-05-08 12:10:24 +03:00
excludePatterns := e . cfg . Run . ExcludePatterns
if e . cfg . Run . UseDefaultExcludes {
excludePatterns = append ( excludePatterns , config . DefaultExcludePatterns ... )
}
var excludeTotalPattern string
if len ( excludePatterns ) != 0 {
excludeTotalPattern = fmt . Sprintf ( "(%s)" , strings . Join ( excludePatterns , "|" ) )
}
2018-05-06 19:08:34 +03:00
runner := pkg . SimpleRunner {
Processors : [ ] processors . Processor {
2018-05-08 12:10:24 +03:00
processors . NewExclude ( excludeTotalPattern ) ,
2018-05-07 21:44:40 +03:00
processors . NewNolint ( lintCtx . Program . Fset ) ,
processors . NewUniqByLine ( ) ,
2018-05-08 09:55:38 +03:00
processors . NewDiff ( e . cfg . Run . Diff , e . cfg . Run . DiffFromRevision , e . cfg . Run . DiffPatchFilePath ) ,
processors . NewMaxPerFileFromLinter ( ) ,
2018-05-08 08:15:55 +03:00
processors . NewMaxFromLinter ( e . cfg . Run . MaxIssuesPerLinter ) ,
2018-05-06 19:08:34 +03:00
processors . NewPathPrettifier ( ) ,
} ,
}
2018-05-05 19:43:18 +03:00
2018-05-07 21:44:40 +03:00
return runner . Run ( ctx , linters , lintCtx ) , nil
2018-05-06 19:08:34 +03:00
}
2018-05-05 19:43:18 +03:00
2018-05-06 19:08:34 +03:00
func ( e * Executor ) executeRun ( cmd * cobra . Command , args [ ] string ) {
2018-05-07 21:44:40 +03:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , e . cfg . Run . Deadline )
defer cancel ( )
2018-05-06 13:25:50 +03:00
2018-05-07 21:44:40 +03:00
defer func ( startedAt time . Time ) {
logrus . Infof ( "Run took %s" , time . Since ( startedAt ) )
} ( time . Now ( ) )
f := func ( ) error {
2018-05-06 19:08:34 +03:00
issues , err := e . runAnalysis ( ctx , args )
2018-05-05 19:43:18 +03:00
if err != nil {
2018-05-06 19:08:34 +03:00
return err
2018-05-05 09:24:37 +03:00
}
2018-05-08 11:54:30 +03:00
var p printers . Printer
if e . cfg . Run . OutFormat == config . OutFormatJSON {
p = printers . NewJSON ( )
} else {
p = printers . NewText ( e . cfg . Run . PrintIssuedLine , e . cfg . Run . OutFormat == config . OutFormatColoredLineNumber )
}
gotAnyIssues , err := p . Print ( issues )
2018-05-07 21:44:40 +03:00
if err != nil {
2018-05-08 11:54:30 +03:00
return fmt . Errorf ( "can't print %d issues: %s" , len ( issues ) , err )
2018-05-05 09:24:37 +03:00
}
2018-05-07 21:44:40 +03:00
if gotAnyIssues {
2018-05-06 19:08:34 +03:00
e . exitCode = e . cfg . Run . ExitCodeIfIssuesFound
return nil
2018-05-05 11:08:14 +03:00
}
2018-05-06 19:08:34 +03:00
return nil
2018-05-05 09:24:37 +03:00
}
2018-05-06 19:08:34 +03:00
if err := f ( ) ; err != nil {
2018-05-05 19:43:18 +03:00
log . Print ( err )
2018-05-06 19:08:34 +03:00
if e . exitCode == 0 {
e . exitCode = exitCodeIfFailure
}
2018-05-05 09:24:37 +03:00
}
}