2018-05-05 09:24:37 +03:00
package commands
import (
"context"
"encoding/json"
"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
"github.com/fatih/color"
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-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-06 22:58:04 +03:00
"github.com/golangci/golangci-shared/pkg/analytics"
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-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" ,
20 , "Minimal complexity of function to report it" )
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 09:09:10 +03:00
runCmd . Flags ( ) . IntVar ( & rc . Dupl . Threshold , "dupl.threshold" ,
20 , "Minimal threshold to detect copy-paste" )
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-06 13:41:42 +03:00
runCmd . Flags ( ) . StringSliceVarP ( & rc . ExcludePatterns , "exclude" , "e" , config . DefaultExcludePatterns , "Exclude issue by regexp" )
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 ( ) {
analytics . Log ( ctx ) . Infof ( "Program loading took %s" , time . Since ( startedAt ) )
} ( )
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 ( ) {
analytics . Log ( ctx ) . Infof ( "SSA repr building took %s" , time . Since ( startedAt ) )
} ( )
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 { "./..." }
}
paths , err := fsutils . GetPathsForAnalysis ( args )
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-06 19:08:34 +03:00
func ( e * Executor ) runAnalysis ( ctx context . Context , args [ ] string ) ( [ ] result . Issue , error ) {
2018-05-06 22:58:04 +03:00
startedAt := time . Now ( )
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-06 19:08:34 +03:00
runner := pkg . SimpleRunner {
Processors : [ ] processors . Processor {
processors . MaxLinterIssuesPerFile { } ,
processors . NewExcludeProcessor ( fmt . Sprintf ( "(%s)" , strings . Join ( e . cfg . Run . ExcludePatterns , "|" ) ) ) ,
2018-05-06 22:58:04 +03:00
processors . NewNolintProcessor ( lintCtx . Program ) ,
2018-05-06 19:08:34 +03:00
processors . UniqByLineProcessor { } ,
processors . NewPathPrettifier ( ) ,
} ,
}
2018-05-05 19:43:18 +03:00
2018-05-06 22:58:04 +03:00
issues , err := runner . Run ( ctx , linters , lintCtx )
2018-05-06 19:08:34 +03:00
if err != nil {
return nil , err
}
2018-05-05 09:24:37 +03:00
2018-05-06 22:58:04 +03:00
analytics . Log ( ctx ) . Infof ( "Analysis took %s" , time . Since ( startedAt ) )
2018-05-06 19:08:34 +03:00
return issues , nil
}
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 ) {
f := func ( ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , e . cfg . Run . Deadline )
defer cancel ( )
2018-05-06 13:25:50 +03:00
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-05 19:43:18 +03:00
if err := outputIssues ( e . cfg . Run . OutFormat , issues ) ; err != nil {
2018-05-06 19:08:34 +03:00
return fmt . Errorf ( "can't output %d issues: %s" , len ( issues ) , err )
2018-05-05 09:24:37 +03:00
}
2018-05-05 11:08:14 +03:00
if len ( issues ) != 0 {
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
}
}
func outputIssues ( format string , issues [ ] result . Issue ) error {
if format == config . OutFormatLineNumber || format == config . OutFormatColoredLineNumber {
if len ( issues ) == 0 {
outStr := "Congrats! No issues were found."
if format == config . OutFormatColoredLineNumber {
outStr = color . GreenString ( outStr )
}
2018-05-06 13:25:50 +03:00
fmt . Println ( outStr )
2018-05-05 09:24:37 +03:00
}
for _ , i := range issues {
2018-05-05 19:43:18 +03:00
text := i . Text
if format == config . OutFormatColoredLineNumber {
text = color . RedString ( text )
}
2018-05-06 13:25:50 +03:00
fmt . Printf ( "%s:%d: %s\n" , i . File , i . LineNumber , text )
2018-05-05 09:24:37 +03:00
}
return nil
}
if format == config . OutFormatJSON {
outputJSON , err := json . Marshal ( issues )
if err != nil {
return err
}
2018-05-06 13:25:50 +03:00
fmt . Print ( string ( outputJSON ) )
2018-05-05 09:24:37 +03:00
return nil
}
return fmt . Errorf ( "unknown output format %q" , format )
}