Skip to content

Commit 980a911

Browse files
authored
fix: sanitize severities by output format (#5359)
1 parent 15a92de commit 980a911

20 files changed

+454
-230
lines changed

pkg/logutils/logutils.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ const (
3636

3737
// Printers.
3838
const (
39-
DebugKeyTabPrinter = "tab_printer"
40-
DebugKeyTextPrinter = "text_printer"
39+
DebugKeyCheckstylePrinter = "checkstyle_printer"
40+
DebugKeyCodeClimatePrinter = "codeclimate_printer"
41+
DebugKeySarifPrinter = "sarif_printer"
42+
DebugKeyTabPrinter = "tab_printer"
43+
DebugKeyTeamCityPrinter = "teamcity_printer"
44+
DebugKeyTextPrinter = "text_printer"
4145
)
4246

4347
// Processors.

pkg/printers/checkstyle.go

+43-29
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,34 @@ import (
99
"github.com/go-xmlfmt/xmlfmt"
1010
"golang.org/x/exp/maps"
1111

12+
"github.com/golangci/golangci-lint/pkg/logutils"
1213
"github.com/golangci/golangci-lint/pkg/result"
1314
)
1415

1516
const defaultCheckstyleSeverity = "error"
1617

17-
type checkstyleOutput struct {
18-
XMLName xml.Name `xml:"checkstyle"`
19-
Version string `xml:"version,attr"`
20-
Files []*checkstyleFile `xml:"file"`
21-
}
22-
23-
type checkstyleFile struct {
24-
Name string `xml:"name,attr"`
25-
Errors []*checkstyleError `xml:"error"`
26-
}
27-
28-
type checkstyleError struct {
29-
Column int `xml:"column,attr"`
30-
Line int `xml:"line,attr"`
31-
Message string `xml:"message,attr"`
32-
Severity string `xml:"severity,attr"`
33-
Source string `xml:"source,attr"`
34-
}
35-
18+
// Checkstyle prints issues in the Checkstyle format.
19+
// https://checkstyle.org/config.html
3620
type Checkstyle struct {
37-
w io.Writer
21+
log logutils.Log
22+
w io.Writer
23+
sanitizer severitySanitizer
3824
}
3925

40-
func NewCheckstyle(w io.Writer) *Checkstyle {
41-
return &Checkstyle{w: w}
26+
func NewCheckstyle(log logutils.Log, w io.Writer) *Checkstyle {
27+
return &Checkstyle{
28+
log: log.Child(logutils.DebugKeyCheckstylePrinter),
29+
w: w,
30+
sanitizer: severitySanitizer{
31+
// https://checkstyle.org/config.html#Severity
32+
// https://checkstyle.org/property_types.html#SeverityLevel
33+
allowedSeverities: []string{"ignore", "info", "warning", defaultCheckstyleSeverity},
34+
defaultSeverity: defaultCheckstyleSeverity,
35+
},
36+
}
4237
}
4338

44-
func (p Checkstyle) Print(issues []result.Issue) error {
39+
func (p *Checkstyle) Print(issues []result.Issue) error {
4540
out := checkstyleOutput{
4641
Version: "5.0",
4742
}
@@ -59,22 +54,22 @@ func (p Checkstyle) Print(issues []result.Issue) error {
5954
files[issue.FilePath()] = file
6055
}
6156

62-
severity := defaultCheckstyleSeverity
63-
if issue.Severity != "" {
64-
severity = issue.Severity
65-
}
66-
6757
newError := &checkstyleError{
6858
Column: issue.Column(),
6959
Line: issue.Line(),
7060
Message: issue.Text,
7161
Source: issue.FromLinter,
72-
Severity: severity,
62+
Severity: p.sanitizer.Sanitize(issue.Severity),
7363
}
7464

7565
file.Errors = append(file.Errors, newError)
7666
}
7767

68+
err := p.sanitizer.Err()
69+
if err != nil {
70+
p.log.Infof("%v", err)
71+
}
72+
7873
out.Files = maps.Values(files)
7974

8075
sort.Slice(out.Files, func(i, j int) bool {
@@ -93,3 +88,22 @@ func (p Checkstyle) Print(issues []result.Issue) error {
9388

9489
return nil
9590
}
91+
92+
type checkstyleOutput struct {
93+
XMLName xml.Name `xml:"checkstyle"`
94+
Version string `xml:"version,attr"`
95+
Files []*checkstyleFile `xml:"file"`
96+
}
97+
98+
type checkstyleFile struct {
99+
Name string `xml:"name,attr"`
100+
Errors []*checkstyleError `xml:"error"`
101+
}
102+
103+
type checkstyleError struct {
104+
Column int `xml:"column,attr"`
105+
Line int `xml:"line,attr"`
106+
Message string `xml:"message,attr"`
107+
Severity string `xml:"severity,attr"`
108+
Source string `xml:"source,attr"`
109+
}

pkg/printers/checkstyle_test.go

+55-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
1111

12+
"github.com/golangci/golangci-lint/pkg/logutils"
1213
"github.com/golangci/golangci-lint/pkg/result"
1314
)
1415

@@ -41,15 +42,67 @@ func TestCheckstyle_Print(t *testing.T) {
4142
Column: 9,
4243
},
4344
},
45+
{
46+
FromLinter: "linter-c",
47+
Severity: "",
48+
Text: "without severity",
49+
SourceLines: []string{
50+
"func foo() {",
51+
"\tfmt.Println(\"bar\")",
52+
"}",
53+
},
54+
Pos: token.Position{
55+
Filename: "path/to/filec.go",
56+
Offset: 5,
57+
Line: 300,
58+
Column: 10,
59+
},
60+
},
61+
{
62+
FromLinter: "linter-d",
63+
Severity: "foo",
64+
Text: "unknown severity",
65+
SourceLines: []string{
66+
"func foo() {",
67+
"\tfmt.Println(\"bar\")",
68+
"}",
69+
},
70+
Pos: token.Position{
71+
Filename: "path/to/filed.go",
72+
Offset: 5,
73+
Line: 300,
74+
Column: 11,
75+
},
76+
},
4477
}
4578

4679
buf := new(bytes.Buffer)
47-
printer := NewCheckstyle(buf)
80+
81+
log := logutils.NewStderrLog(logutils.DebugKeyEmpty)
82+
log.SetLevel(logutils.LogLevelDebug)
83+
84+
printer := NewCheckstyle(log, buf)
4885

4986
err := printer.Print(issues)
5087
require.NoError(t, err)
5188

52-
expected := "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<checkstyle version=\"5.0\">\n <file name=\"path/to/filea.go\">\n <error column=\"4\" line=\"10\" message=\"some issue\" severity=\"warning\" source=\"linter-a\"></error>\n </file>\n <file name=\"path/to/fileb.go\">\n <error column=\"9\" line=\"300\" message=\"another issue\" severity=\"error\" source=\"linter-b\"></error>\n </file>\n</checkstyle>\n"
89+
expected := `<?xml version="1.0" encoding="UTF-8"?>
90+
91+
<checkstyle version="5.0">
92+
<file name="path/to/filea.go">
93+
<error column="4" line="10" message="some issue" severity="warning" source="linter-a"></error>
94+
</file>
95+
<file name="path/to/fileb.go">
96+
<error column="9" line="300" message="another issue" severity="error" source="linter-b"></error>
97+
</file>
98+
<file name="path/to/filec.go">
99+
<error column="10" line="300" message="without severity" severity="error" source="linter-c"></error>
100+
</file>
101+
<file name="path/to/filed.go">
102+
<error column="11" line="300" message="unknown severity" severity="error" source="linter-d"></error>
103+
</file>
104+
</checkstyle>
105+
`
53106

54107
assert.Equal(t, expected, strings.ReplaceAll(buf.String(), "\r", ""))
55108
}

pkg/printers/codeclimate.go

+39-31
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,71 @@ package printers
33
import (
44
"encoding/json"
55
"io"
6-
"slices"
76

7+
"github.com/golangci/golangci-lint/pkg/logutils"
88
"github.com/golangci/golangci-lint/pkg/result"
99
)
1010

1111
const defaultCodeClimateSeverity = "critical"
1212

13-
// CodeClimateIssue is a subset of the Code Climate spec.
14-
// https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types
15-
// It is just enough to support GitLab CI Code Quality.
16-
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#code-quality-report-format
17-
type CodeClimateIssue struct {
18-
Description string `json:"description"`
19-
CheckName string `json:"check_name"`
20-
Severity string `json:"severity,omitempty"`
21-
Fingerprint string `json:"fingerprint"`
22-
Location struct {
23-
Path string `json:"path"`
24-
Lines struct {
25-
Begin int `json:"begin"`
26-
} `json:"lines"`
27-
} `json:"location"`
28-
}
29-
13+
// CodeClimate prints issues in the Code Climate format.
14+
// https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md
3015
type CodeClimate struct {
31-
w io.Writer
32-
33-
allowedSeverities []string
16+
log logutils.Log
17+
w io.Writer
18+
sanitizer severitySanitizer
3419
}
3520

36-
func NewCodeClimate(w io.Writer) *CodeClimate {
21+
func NewCodeClimate(log logutils.Log, w io.Writer) *CodeClimate {
3722
return &CodeClimate{
38-
w: w,
39-
allowedSeverities: []string{"info", "minor", "major", defaultCodeClimateSeverity, "blocker"},
23+
log: log.Child(logutils.DebugKeyCodeClimatePrinter),
24+
w: w,
25+
sanitizer: severitySanitizer{
26+
// https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types
27+
allowedSeverities: []string{"info", "minor", "major", defaultCodeClimateSeverity, "blocker"},
28+
defaultSeverity: defaultCodeClimateSeverity,
29+
},
4030
}
4131
}
4232

43-
func (p CodeClimate) Print(issues []result.Issue) error {
33+
func (p *CodeClimate) Print(issues []result.Issue) error {
4434
codeClimateIssues := make([]CodeClimateIssue, 0, len(issues))
4535

4636
for i := range issues {
47-
issue := &issues[i]
37+
issue := issues[i]
4838

4939
codeClimateIssue := CodeClimateIssue{}
5040
codeClimateIssue.Description = issue.Description()
5141
codeClimateIssue.CheckName = issue.FromLinter
5242
codeClimateIssue.Location.Path = issue.Pos.Filename
5343
codeClimateIssue.Location.Lines.Begin = issue.Pos.Line
5444
codeClimateIssue.Fingerprint = issue.Fingerprint()
55-
codeClimateIssue.Severity = defaultCodeClimateSeverity
56-
57-
if slices.Contains(p.allowedSeverities, issue.Severity) {
58-
codeClimateIssue.Severity = issue.Severity
59-
}
45+
codeClimateIssue.Severity = p.sanitizer.Sanitize(issue.Severity)
6046

6147
codeClimateIssues = append(codeClimateIssues, codeClimateIssue)
6248
}
6349

50+
err := p.sanitizer.Err()
51+
if err != nil {
52+
p.log.Infof("%v", err)
53+
}
54+
6455
return json.NewEncoder(p.w).Encode(codeClimateIssues)
6556
}
57+
58+
// CodeClimateIssue is a subset of the Code Climate spec.
59+
// https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types
60+
// It is just enough to support GitLab CI Code Quality.
61+
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#code-quality-report-format
62+
type CodeClimateIssue struct {
63+
Description string `json:"description"`
64+
CheckName string `json:"check_name"`
65+
Severity string `json:"severity,omitempty"`
66+
Fingerprint string `json:"fingerprint"`
67+
Location struct {
68+
Path string `json:"path"`
69+
Lines struct {
70+
Begin int `json:"begin"`
71+
} `json:"lines"`
72+
} `json:"location"`
73+
}

pkg/printers/codeclimate_test.go

+30-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import (
88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
1010

11+
"github.com/golangci/golangci-lint/pkg/logutils"
1112
"github.com/golangci/golangci-lint/pkg/result"
1213
)
1314

1415
func TestCodeClimate_Print(t *testing.T) {
1516
issues := []result.Issue{
1617
{
1718
FromLinter: "linter-a",
18-
Severity: "warning",
19+
Severity: "minor",
1920
Text: "some issue",
2021
Pos: token.Position{
2122
Filename: "path/to/filea.go",
@@ -42,28 +43,49 @@ func TestCodeClimate_Print(t *testing.T) {
4243
},
4344
{
4445
FromLinter: "linter-c",
45-
Text: "issue c",
46+
Severity: "",
47+
Text: "without severity",
4648
SourceLines: []string{
4749
"func foo() {",
48-
"\tfmt.Println(\"ccc\")",
50+
"\tfmt.Println(\"bar\")",
4951
"}",
5052
},
5153
Pos: token.Position{
5254
Filename: "path/to/filec.go",
53-
Offset: 6,
54-
Line: 200,
55-
Column: 2,
55+
Offset: 5,
56+
Line: 300,
57+
Column: 10,
58+
},
59+
},
60+
{
61+
FromLinter: "linter-d",
62+
Severity: "foo",
63+
Text: "unknown severity",
64+
SourceLines: []string{
65+
"func foo() {",
66+
"\tfmt.Println(\"bar\")",
67+
"}",
68+
},
69+
Pos: token.Position{
70+
Filename: "path/to/filed.go",
71+
Offset: 5,
72+
Line: 300,
73+
Column: 11,
5674
},
5775
},
5876
}
5977

6078
buf := new(bytes.Buffer)
61-
printer := NewCodeClimate(buf)
79+
80+
log := logutils.NewStderrLog(logutils.DebugKeyEmpty)
81+
log.SetLevel(logutils.LogLevelDebug)
82+
83+
printer := NewCodeClimate(log, buf)
6284

6385
err := printer.Print(issues)
6486
require.NoError(t, err)
6587

66-
expected := `[{"description":"linter-a: some issue","check_name":"linter-a","severity":"critical","fingerprint":"BA73C5DF4A6FD8462FFF1D3140235777","location":{"path":"path/to/filea.go","lines":{"begin":10}}},{"description":"linter-b: another issue","check_name":"linter-b","severity":"major","fingerprint":"0777B4FE60242BD8B2E9B7E92C4B9521","location":{"path":"path/to/fileb.go","lines":{"begin":300}}},{"description":"linter-c: issue c","check_name":"linter-c","severity":"critical","fingerprint":"BEE6E9FBB6BFA4B7DB9FB036697FB036","location":{"path":"path/to/filec.go","lines":{"begin":200}}}]
88+
expected := `[{"description":"linter-a: some issue","check_name":"linter-a","severity":"minor","fingerprint":"BA73C5DF4A6FD8462FFF1D3140235777","location":{"path":"path/to/filea.go","lines":{"begin":10}}},{"description":"linter-b: another issue","check_name":"linter-b","severity":"major","fingerprint":"0777B4FE60242BD8B2E9B7E92C4B9521","location":{"path":"path/to/fileb.go","lines":{"begin":300}}},{"description":"linter-c: without severity","check_name":"linter-c","severity":"critical","fingerprint":"84F89700554F16CCEB6C0BB481B44AD2","location":{"path":"path/to/filec.go","lines":{"begin":300}}},{"description":"linter-d: unknown severity","check_name":"linter-d","severity":"critical","fingerprint":"340BB02E7B583B9727D73765CB64F56F","location":{"path":"path/to/filed.go","lines":{"begin":300}}}]
6789
`
6890

6991
assert.Equal(t, expected, buf.String())

pkg/printers/html.go

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ type htmlIssue struct {
122122
Code string
123123
}
124124

125+
// HTML prints issues in an HTML page.
126+
// It uses the Cloudflare CDN (cdnjs) and React.
125127
type HTML struct {
126128
w io.Writer
127129
}

0 commit comments

Comments
 (0)