Skip to content

Commit d46b481

Browse files
sspainkashanbrown
authored andcommitted
Add "revive" linter (golangci#1729)
1 parent 4add72a commit d46b481

File tree

9 files changed

+241
-6
lines changed

9 files changed

+241
-6
lines changed

.golangci.example.yml

+7
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@ linters-settings:
350350
rowserrcheck:
351351
packages:
352352
- github.com/jmoiron/sqlx
353+
revive:
354+
# see https://github.com/mgechev/revive#available-rules for details.
355+
ignore-generated-header: true
356+
severity: warning
357+
rules:
358+
- name: indent-error-flow
359+
severity: warning
353360
testpackage:
354361
# regexp pattern to skip files
355362
skip-regexp: (export|internal)_test\.go

.golangci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ linters:
117117
# - nestif
118118
# - prealloc
119119
# - testpackage
120+
# - revive
120121
# - wsl
121122

122123
issues:

go.mod

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ require (
3939
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb // v1.0
4040
github.com/mattn/go-colorable v0.1.8
4141
github.com/mbilski/exhaustivestruct v1.2.0
42+
github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81
43+
github.com/mgechev/revive v1.0.3
4244
github.com/mitchellh/go-homedir v1.1.0
4345
github.com/mitchellh/go-ps v1.0.0
4446
github.com/moricho/tparallel v0.2.1
@@ -69,9 +71,8 @@ require (
6971
github.com/ultraware/whitespace v0.0.4
7072
github.com/uudashr/gocognit v1.0.1
7173
github.com/valyala/quicktemplate v1.6.3
72-
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect
7374
golang.org/x/text v0.3.4 // indirect
74-
golang.org/x/tools v0.0.0-20210105210202-9ed45478a130
75+
golang.org/x/tools v0.1.0
7576
gopkg.in/yaml.v2 v2.4.0
7677
honnef.co/go/tools v0.0.1-2020.1.6
7778
mvdan.cc/gofumpt v0.1.0

go.sum

+14-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/config.go

+18
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ type LintersSettings struct {
268268
Gofumpt GofumptSettings
269269
ErrorLint ErrorLintSettings
270270
Makezero MakezeroSettings
271+
Revive ReviveSettings
271272
Thelper ThelperSettings
272273
Forbidigo ForbidigoSettings
273274
Ifshort IfshortSettings
@@ -397,6 +398,23 @@ type MakezeroSettings struct {
397398
Always bool
398399
}
399400

401+
type ReviveSettings struct {
402+
IgnoreGeneratedHeader bool `mapstructure:"ignore-generated-header"`
403+
Confidence float64
404+
Severity string
405+
Rules []struct {
406+
Name string
407+
Arguments []interface{}
408+
Severity string
409+
}
410+
ErrorCode int `mapstructure:"error-code"`
411+
WarningCode int `mapstructure:"warning-code"`
412+
Directives []struct {
413+
Name string
414+
Severity string
415+
}
416+
}
417+
400418
type ThelperSettings struct {
401419
Test struct {
402420
First bool `mapstructure:"first"`

pkg/golinters/revive.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package golinters
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"go/token"
7+
"io/ioutil"
8+
9+
"github.com/mgechev/dots"
10+
reviveConfig "github.com/mgechev/revive/config"
11+
"github.com/mgechev/revive/lint"
12+
"golang.org/x/tools/go/analysis"
13+
14+
"github.com/golangci/golangci-lint/pkg/config"
15+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
16+
"github.com/golangci/golangci-lint/pkg/lint/linter"
17+
"github.com/golangci/golangci-lint/pkg/result"
18+
)
19+
20+
const reviveName = "revive"
21+
22+
// jsonObject defines a JSON object of an failure
23+
type jsonObject struct {
24+
Severity lint.Severity
25+
lint.Failure `json:",inline"`
26+
}
27+
28+
// NewNewRevive returns a new Revive linter.
29+
func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter {
30+
var issues []goanalysis.Issue
31+
32+
analyzer := &analysis.Analyzer{
33+
Name: goanalysis.TheOnlyAnalyzerName,
34+
Doc: goanalysis.TheOnlyanalyzerDoc,
35+
}
36+
37+
return goanalysis.NewLinter(
38+
reviveName,
39+
"Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.",
40+
[]*analysis.Analyzer{analyzer},
41+
nil,
42+
).WithContextSetter(func(lintCtx *linter.Context) {
43+
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
44+
var files []string
45+
46+
for _, file := range pass.Files {
47+
files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename)
48+
}
49+
50+
conf, err := setReviveConfig(cfg)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
formatter, err := reviveConfig.GetFormatter("json")
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
revive := lint.New(ioutil.ReadFile)
61+
62+
lintingRules, err := reviveConfig.GetLintingRules(conf)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
packages, err := dots.ResolvePackages(files, []string{})
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
failures, err := revive.Lint(packages, lintingRules, *conf)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
formatChan := make(chan lint.Failure)
78+
exitChan := make(chan bool)
79+
80+
var output string
81+
go func() {
82+
output, err = formatter.Format(formatChan, *conf)
83+
if err != nil {
84+
lintCtx.Log.Errorf("Format error: %v", err)
85+
}
86+
exitChan <- true
87+
}()
88+
89+
for f := range failures {
90+
if f.Confidence < conf.Confidence {
91+
continue
92+
}
93+
94+
formatChan <- f
95+
}
96+
97+
close(formatChan)
98+
<-exitChan
99+
100+
var results []jsonObject
101+
err = json.Unmarshal([]byte(output), &results)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
for i := range results {
107+
issues = append(issues, goanalysis.NewIssue(&result.Issue{
108+
Severity: string(results[i].Severity),
109+
Text: fmt.Sprintf("%q", results[i].Failure.Failure),
110+
Pos: token.Position{
111+
Filename: results[i].Position.Start.Filename,
112+
Line: results[i].Position.Start.Line,
113+
Offset: results[i].Position.Start.Offset,
114+
Column: results[i].Position.Start.Column,
115+
},
116+
LineRange: &result.Range{
117+
From: results[i].Position.Start.Line,
118+
To: results[i].Position.End.Line,
119+
},
120+
FromLinter: reviveName,
121+
}, pass))
122+
}
123+
124+
return nil, nil
125+
}
126+
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
127+
return issues
128+
}).WithLoadMode(goanalysis.LoadModeSyntax)
129+
}
130+
131+
func setReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) {
132+
// Get revive default configuration
133+
conf, err := reviveConfig.GetConfig("")
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
// Default is false
139+
conf.IgnoreGeneratedHeader = cfg.IgnoreGeneratedHeader
140+
141+
if cfg.Severity != "" {
142+
conf.Severity = lint.Severity(cfg.Severity)
143+
}
144+
145+
if cfg.Confidence != 0 {
146+
conf.Confidence = cfg.Confidence
147+
}
148+
149+
// By default golangci-lint ignores missing doc comments, follow same convention by removing this default rule
150+
// Relevant issue: https://github.com/golangci/golangci-lint/issues/456
151+
delete(conf.Rules, "exported")
152+
153+
if len(cfg.Rules) != 0 {
154+
// Clear default rules, only use rules defined in config
155+
conf.Rules = make(map[string]lint.RuleConfig, len(cfg.Rules))
156+
}
157+
for _, r := range cfg.Rules {
158+
conf.Rules[r.Name] = lint.RuleConfig{Arguments: r.Arguments, Severity: lint.Severity(r.Severity)}
159+
}
160+
161+
conf.ErrorCode = cfg.ErrorCode
162+
conf.WarningCode = cfg.WarningCode
163+
164+
if len(cfg.Directives) != 0 {
165+
// Clear default Directives, only use Directives defined in config
166+
conf.Directives = make(map[string]lint.DirectiveConfig, len(cfg.Directives))
167+
}
168+
for _, d := range cfg.Directives {
169+
conf.Directives[d.Name] = lint.DirectiveConfig{Severity: lint.Severity(d.Severity)}
170+
}
171+
172+
return conf, nil
173+
}

pkg/lint/lintersdb/manager.go

+5
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
9696
var thelperCfg *config.ThelperSettings
9797
var predeclaredCfg *config.PredeclaredSettings
9898
var ifshortCfg *config.IfshortSettings
99+
var reviveCfg *config.ReviveSettings
99100
if m.cfg != nil {
100101
govetCfg = &m.cfg.LintersSettings.Govet
101102
testpackageCfg = &m.cfg.LintersSettings.Testpackage
@@ -104,6 +105,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
104105
thelperCfg = &m.cfg.LintersSettings.Thelper
105106
predeclaredCfg = &m.cfg.LintersSettings.Predeclared
106107
ifshortCfg = &m.cfg.LintersSettings.Ifshort
108+
reviveCfg = &m.cfg.LintersSettings.Revive
107109
}
108110
const megacheckName = "megacheck"
109111
lcs := []*linter.Config{
@@ -352,6 +354,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
352354
linter.NewConfig(golinters.NewPredeclared(predeclaredCfg)).
353355
WithPresets(linter.PresetStyle).
354356
WithURL("https://github.com/nishanths/predeclared"),
357+
linter.NewConfig(golinters.NewRevive(reviveCfg)).
358+
WithPresets(linter.PresetStyle).
359+
WithURL("https://github.com/mgechev/revive"),
355360

356361
// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
357362
linter.NewConfig(golinters.NewNoLintLint()).

test/testdata/configs/revive.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
linters-settings:
2+
revive:
3+
ignore-generated-header: true
4+
severity: warning
5+
rules:
6+
- name: indent-error-flow
7+
severity: warning

test/testdata/revive.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//args: -Erevive
2+
//config_path: testdata/configs/revive.yml
3+
package testdata
4+
5+
import "time"
6+
7+
func testRevive(t *time.Duration) error {
8+
if t == nil {
9+
return nil
10+
} else { // ERROR "if block ends with a return statement, so drop this else and outdent its block"
11+
return nil
12+
}
13+
}

0 commit comments

Comments
 (0)