Skip to content

Commit 00d45ba

Browse files
committed
(Resolves golangci#1981) Allow exit failure only on specified severities
1 parent a303529 commit 00d45ba

9 files changed

+147
-5
lines changed

.golangci.reference.yml

+6
Original file line numberDiff line numberDiff line change
@@ -2443,6 +2443,12 @@ severity:
24432443
# Default: false
24442444
case-sensitive: true
24452445

2446+
# Issue severities required to cause an exit with a non-successful code. An empty list means
2447+
# that any issue severity will cause a non-successful exit code. Severities should match the
2448+
# supported severity names of the selected out format.
2449+
# Default: []
2450+
fail-on-severities: ["warning", "error"]
2451+
24462452
# When a list of severity rules are provided, severity information will be added to lint issues.
24472453
# Severity rules have the same filtering capability as exclude rules
24482454
# except you are allowed to specify one matcher per severity rule.

pkg/commands/run.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is
233233
fs.BoolVar(&ic.WholeFiles, "whole-files", false,
234234
wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)"))
235235
fs.BoolVar(&ic.NeedFix, "fix", false, "Fix found issues (if it's supported by the linter)")
236+
237+
// Severities config
238+
sc := &cfg.Severity
239+
fs.StringSliceVar(&sc.FailOnSeverities, "fail-on-severities", nil,
240+
wh("Fail only if issues matching these severities are found"))
236241
}
237242

238243
func (e *Executor) initRunConfiguration(cmd *cobra.Command) {
@@ -371,8 +376,26 @@ func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
371376
}
372377

373378
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
374-
if len(issues) != 0 {
375-
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
379+
if len(issues) == 0 {
380+
return
381+
}
382+
// treat empty set of failure severities as "fail on all issues"
383+
if len(e.cfg.Severity.FailOnSeverities) == 0 {
384+
if len(issues) > 0 {
385+
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
386+
}
387+
return
388+
}
389+
390+
failSeveritySet := map[string]struct{}{}
391+
for _, s := range e.cfg.Severity.FailOnSeverities {
392+
failSeveritySet[s] = struct{}{}
393+
}
394+
for i := range issues {
395+
if _, isFailure := failSeveritySet[issues[i].Severity]; isFailure {
396+
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
397+
return
398+
}
376399
}
377400
}
378401

pkg/config/reader.go

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ func (r *FileReader) validateConfig() error {
144144
if len(c.Severity.Rules) > 0 && c.Severity.Default == "" {
145145
return errors.New("can't set severity rule option: no default severity defined")
146146
}
147+
if len(c.Severity.FailOnSeverities) > 0 && c.Severity.Default == "" {
148+
return errors.New("can't set severity failure set option: no default severity defined")
149+
}
147150
for i, rule := range c.Severity.Rules {
148151
if err := rule.Validate(); err != nil {
149152
return fmt.Errorf("error in severity rule #%d: %v", i, err)

pkg/config/severity.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package config
33
const severityRuleMinConditionsCount = 1
44

55
type Severity struct {
6-
Default string `mapstructure:"default-severity"`
7-
CaseSensitive bool `mapstructure:"case-sensitive"`
8-
Rules []SeverityRule `mapstructure:"rules"`
6+
Default string `mapstructure:"default-severity"`
7+
CaseSensitive bool `mapstructure:"case-sensitive"`
8+
FailOnSeverities []string `mapstructure:"fail-on-severities"`
9+
Rules []SeverityRule `mapstructure:"rules"`
910
}
1011

1112
type SeverityRule struct {

test/run_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,37 @@ func TestUnusedCheckExported(t *testing.T) {
452452
ExpectNoIssues()
453453
}
454454

455+
func TestSuccessfulExitOnInsufficientlySevereIssues(t *testing.T) {
456+
testshared.NewRunnerBuilder(t).
457+
WithConfigFile("testdata_etc/fail_on_severities/staticcheck_ignore_warning_level.yml").
458+
WithTargetPath("testdata_etc/fail_on_severities/...").
459+
Runner().
460+
Install().
461+
Run().
462+
ExpectExitCode(exitcodes.Success)
463+
}
464+
465+
func TestFailedExitOnSufficientlySevereIssues(t *testing.T) {
466+
testshared.NewRunnerBuilder(t).
467+
WithConfigFile("testdata_etc/fail_on_severities/staticcheck_ignore_info_level.yml").
468+
WithTargetPath("testdata_etc/fail_on_severities/...").
469+
Runner().
470+
Install().
471+
Run().
472+
ExpectExitCode(exitcodes.IssuesFound)
473+
}
474+
475+
func TestFailedExitOnSufficientlySevereIssuesCmdFlag(t *testing.T) {
476+
testshared.NewRunnerBuilder(t).
477+
WithConfigFile("testdata_etc/fail_on_severities/staticcheck_ignore_warning_level.yml").
478+
WithTargetPath("testdata_etc/fail_on_severities/...").
479+
WithArgs("--fail-on-severities", "warning,error").
480+
Runner().
481+
Install().
482+
Run().
483+
ExpectExitCode(exitcodes.IssuesFound)
484+
}
485+
455486
func TestConfigFileIsDetected(t *testing.T) {
456487
testshared.InstallGolangciLint(t)
457488

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package testdata
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
const half = 0.5
8+
9+
// Deprecated: use floatRounder instead
10+
func deprecatedFloatRounder(f float64) int64 {
11+
return int64(f)
12+
}
13+
14+
func floatRounder(f float64) int64 {
15+
sgn := int64(1)
16+
if f < 0 {
17+
sgn = -1
18+
}
19+
out := int64(f)
20+
if f-float64(out) > half {
21+
out++
22+
}
23+
return sgn * out
24+
}
25+
26+
func StaticCheckExitSuccessOnInfo() {
27+
fmt.Println(deprecatedFloatRounder(1.0)) // want "SA1019: deprecatedFloatRounder is deprecated"
28+
fmt.Println(floatRounder(1.0))
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
linter-settings:
2+
disable-all: true
3+
enable:
4+
- staticcheck
5+
severity:
6+
default-severity: error
7+
fail-on-severities:
8+
- warning
9+
- error
10+
rules:
11+
- linters:
12+
- staticcheck
13+
severity: info
14+
text: 'SA1019:'
15+
- linters:
16+
- staticcheck
17+
severity: warning
18+
text: 'SA4016:'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
linter-settings:
2+
disable-all: true
3+
enable:
4+
- staticcheck
5+
severity:
6+
default-severity: error
7+
fail-on-severities:
8+
- error
9+
rules:
10+
- linters:
11+
- staticcheck
12+
severity: info
13+
text: 'SA1019:'
14+
- linters:
15+
- staticcheck
16+
severity: warning
17+
text: 'SA4016:'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testdata
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func uselessFunction(val int) int {
8+
return val ^ 0
9+
}
10+
11+
func StaticCheckExitFailOnInfo() {
12+
x := uselessFunction(1)
13+
fmt.Println(x)
14+
}

0 commit comments

Comments
 (0)