diff --git a/.golangci.example.yml b/.golangci.example.yml index 752ad4b4274e..8e8c8ba7e486 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -350,6 +350,13 @@ linters-settings: rowserrcheck: packages: - github.com/jmoiron/sqlx + revive: + # see https://github.com/mgechev/revive#available-rules for details. + ignore-generated-header: true + severity: warning + rules: + - name: indent-error-flow + severity: warning testpackage: # regexp pattern to skip files skip-regexp: (export|internal)_test\.go diff --git a/.golangci.yml b/.golangci.yml index 830b3f405c3b..d8806b143a0e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -117,6 +117,7 @@ linters: # - nestif # - prealloc # - testpackage + # - revive # - wsl issues: diff --git a/go.mod b/go.mod index db1ed3326b34..99cd1c20a75d 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,8 @@ require ( github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb // v1.0 github.com/mattn/go-colorable v0.1.8 github.com/mbilski/exhaustivestruct v1.2.0 + github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 + github.com/mgechev/revive v1.0.3 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-ps v1.0.0 github.com/moricho/tparallel v0.2.1 @@ -69,9 +71,8 @@ require ( github.com/ultraware/whitespace v0.0.4 github.com/uudashr/gocognit v1.0.1 github.com/valyala/quicktemplate v1.6.3 - golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect golang.org/x/text v0.3.4 // indirect - golang.org/x/tools v0.0.0-20210105210202-9ed45478a130 + golang.org/x/tools v0.1.0 gopkg.in/yaml.v2 v2.4.0 honnef.co/go/tools v0.0.1-2020.1.6 mvdan.cc/gofumpt v0.1.0 diff --git a/go.sum b/go.sum index 25a3338cbf96..a29345cd893a 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -255,12 +257,18 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 h1:QASJXOGm2RZ5Ardbc86qNFvby9AqkLDibfChMtAg5QM= +github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.0.3 h1:z3FL6IFFN3JKzHYHD8O1ExH9g/4lAGJ5x1+9rPZgsFg= +github.com/mgechev/revive v1.0.3/go.mod h1:POGGZagSo/0frdr7VeAifzS5Uka0d0GPiM35MsTO8nE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -293,6 +301,8 @@ github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= @@ -522,8 +532,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -582,8 +592,8 @@ golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210102185154-773b96fafca2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105210202-9ed45478a130 h1:8qSBr5nyKsEgkP918Pu5FFDZpTtLIjXSo6mrtdVOFfk= -golang.org/x/tools v0.0.0-20210105210202-9ed45478a130/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 4fcf427300fc..2a0050a2e6d1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -268,6 +268,7 @@ type LintersSettings struct { Gofumpt GofumptSettings ErrorLint ErrorLintSettings Makezero MakezeroSettings + Revive ReviveSettings Thelper ThelperSettings Forbidigo ForbidigoSettings Ifshort IfshortSettings @@ -397,6 +398,23 @@ type MakezeroSettings struct { Always bool } +type ReviveSettings struct { + IgnoreGeneratedHeader bool `mapstructure:"ignore-generated-header"` + Confidence float64 + Severity string + Rules []struct { + Name string + Arguments []interface{} + Severity string + } + ErrorCode int `mapstructure:"error-code"` + WarningCode int `mapstructure:"warning-code"` + Directives []struct { + Name string + Severity string + } +} + type ThelperSettings struct { Test struct { First bool `mapstructure:"first"` diff --git a/pkg/golinters/revive.go b/pkg/golinters/revive.go new file mode 100644 index 000000000000..377ad946b07d --- /dev/null +++ b/pkg/golinters/revive.go @@ -0,0 +1,173 @@ +package golinters + +import ( + "encoding/json" + "fmt" + "go/token" + "io/ioutil" + + "github.com/mgechev/dots" + reviveConfig "github.com/mgechev/revive/config" + "github.com/mgechev/revive/lint" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const reviveName = "revive" + +// jsonObject defines a JSON object of an failure +type jsonObject struct { + Severity lint.Severity + lint.Failure `json:",inline"` +} + +// NewNewRevive returns a new Revive linter. +func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter { + var issues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + } + + return goanalysis.NewLinter( + reviveName, + "Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + var files []string + + for _, file := range pass.Files { + files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename) + } + + conf, err := setReviveConfig(cfg) + if err != nil { + return nil, err + } + + formatter, err := reviveConfig.GetFormatter("json") + if err != nil { + return nil, err + } + + revive := lint.New(ioutil.ReadFile) + + lintingRules, err := reviveConfig.GetLintingRules(conf) + if err != nil { + return nil, err + } + + packages, err := dots.ResolvePackages(files, []string{}) + if err != nil { + return nil, err + } + + failures, err := revive.Lint(packages, lintingRules, *conf) + if err != nil { + return nil, err + } + + formatChan := make(chan lint.Failure) + exitChan := make(chan bool) + + var output string + go func() { + output, err = formatter.Format(formatChan, *conf) + if err != nil { + lintCtx.Log.Errorf("Format error: %v", err) + } + exitChan <- true + }() + + for f := range failures { + if f.Confidence < conf.Confidence { + continue + } + + formatChan <- f + } + + close(formatChan) + <-exitChan + + var results []jsonObject + err = json.Unmarshal([]byte(output), &results) + if err != nil { + return nil, err + } + + for i := range results { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Severity: string(results[i].Severity), + Text: fmt.Sprintf("%q", results[i].Failure.Failure), + Pos: token.Position{ + Filename: results[i].Position.Start.Filename, + Line: results[i].Position.Start.Line, + Offset: results[i].Position.Start.Offset, + Column: results[i].Position.Start.Column, + }, + LineRange: &result.Range{ + From: results[i].Position.Start.Line, + To: results[i].Position.End.Line, + }, + FromLinter: reviveName, + }, pass)) + } + + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return issues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} + +func setReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) { + // Get revive default configuration + conf, err := reviveConfig.GetConfig("") + if err != nil { + return nil, err + } + + // Default is false + conf.IgnoreGeneratedHeader = cfg.IgnoreGeneratedHeader + + if cfg.Severity != "" { + conf.Severity = lint.Severity(cfg.Severity) + } + + if cfg.Confidence != 0 { + conf.Confidence = cfg.Confidence + } + + // By default golangci-lint ignores missing doc comments, follow same convention by removing this default rule + // Relevant issue: https://github.com/golangci/golangci-lint/issues/456 + delete(conf.Rules, "exported") + + if len(cfg.Rules) != 0 { + // Clear default rules, only use rules defined in config + conf.Rules = make(map[string]lint.RuleConfig, len(cfg.Rules)) + } + for _, r := range cfg.Rules { + conf.Rules[r.Name] = lint.RuleConfig{Arguments: r.Arguments, Severity: lint.Severity(r.Severity)} + } + + conf.ErrorCode = cfg.ErrorCode + conf.WarningCode = cfg.WarningCode + + if len(cfg.Directives) != 0 { + // Clear default Directives, only use Directives defined in config + conf.Directives = make(map[string]lint.DirectiveConfig, len(cfg.Directives)) + } + for _, d := range cfg.Directives { + conf.Directives[d.Name] = lint.DirectiveConfig{Severity: lint.Severity(d.Severity)} + } + + return conf, nil +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index b8f4ed880df7..f2d392f18b8e 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -96,6 +96,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var thelperCfg *config.ThelperSettings var predeclaredCfg *config.PredeclaredSettings var ifshortCfg *config.IfshortSettings + var reviveCfg *config.ReviveSettings if m.cfg != nil { govetCfg = &m.cfg.LintersSettings.Govet testpackageCfg = &m.cfg.LintersSettings.Testpackage @@ -104,6 +105,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { thelperCfg = &m.cfg.LintersSettings.Thelper predeclaredCfg = &m.cfg.LintersSettings.Predeclared ifshortCfg = &m.cfg.LintersSettings.Ifshort + reviveCfg = &m.cfg.LintersSettings.Revive } const megacheckName = "megacheck" lcs := []*linter.Config{ @@ -352,6 +354,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { linter.NewConfig(golinters.NewPredeclared(predeclaredCfg)). WithPresets(linter.PresetStyle). WithURL("https://github.com/nishanths/predeclared"), + linter.NewConfig(golinters.NewRevive(reviveCfg)). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/mgechev/revive"), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint()). diff --git a/test/testdata/configs/revive.yml b/test/testdata/configs/revive.yml new file mode 100644 index 000000000000..7f7ac0b3a346 --- /dev/null +++ b/test/testdata/configs/revive.yml @@ -0,0 +1,7 @@ +linters-settings: + revive: + ignore-generated-header: true + severity: warning + rules: + - name: indent-error-flow + severity: warning diff --git a/test/testdata/revive.go b/test/testdata/revive.go new file mode 100644 index 000000000000..224880b36524 --- /dev/null +++ b/test/testdata/revive.go @@ -0,0 +1,13 @@ +//args: -Erevive +//config_path: testdata/configs/revive.yml +package testdata + +import "time" + +func testRevive(t *time.Duration) error { + if t == nil { + return nil + } else { // ERROR "if block ends with a return statement, so drop this else and outdent its block" + return nil + } +}