Skip to content

Commit a35a96f

Browse files
author
Sebastian Spaink
committed
Add revive as plugin
1 parent 3ef13a8 commit a35a96f

File tree

8 files changed

+246
-6
lines changed

8 files changed

+246
-6
lines changed

.golangci.example.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ linters-settings:
350350
rowserrcheck:
351351
packages:
352352
- github.com/jmoiron/sqlx
353+
revive:
354+
ignore-generated-header: true
355+
severity: warning
356+
rules:
357+
- name: indent-error-flow
358+
severity: warning
353359
testpackage:
354360
# regexp pattern to skip files
355361
skip-regexp: (export|internal)_test\.go

go.mod

Lines changed: 3 additions & 2 deletions
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

Lines changed: 14 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/config.go

Lines changed: 18 additions & 0 deletions
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

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

pkg/lint/lintersdb/manager.go

Lines changed: 4 additions & 0 deletions
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,8 @@ 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+
WithURL("https://github.com/mgechev/revive"),
355359

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

test/testdata/configs/revive.yml

Lines changed: 7 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)