Skip to content

Commit f40b25d

Browse files
committed
fix TeamCity printer
Changes: - print SEVERITY; - populate inspectionType from linter's Name and Desc; - test escaping; - extend .golangci.reference.yml with TeamCity; - minor documentation fixes.
1 parent 97befd1 commit f40b25d

File tree

4 files changed

+89
-57
lines changed

4 files changed

+89
-57
lines changed

.golangci.reference.yml

+1
Original file line numberDiff line numberDiff line change
@@ -2339,6 +2339,7 @@ severity:
23392339
# - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
23402340
# - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel
23412341
# - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
2342+
# - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
23422343
#
23432344
# Default value is an empty string.
23442345
default-severity: error

pkg/commands/run.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer,
495495
case config.OutFormatGithubActions:
496496
p = printers.NewGithub(w)
497497
case config.OutFormatTeamCity:
498-
p = printers.NewTeamCity(w)
498+
p = printers.NewTeamCity(w, e.DBManager)
499499
default:
500500
return nil, fmt.Errorf("unknown output format %s", format)
501501
}

pkg/printers/teamcity.go

+45-44
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"unicode/utf8"
99

10+
"github.com/golangci/golangci-lint/pkg/lint/linter"
1011
"github.com/golangci/golangci-lint/pkg/result"
1112
)
1213

@@ -16,16 +17,23 @@ const (
1617
largeLimit = 4000
1718
)
1819

20+
// Configer used for accessing linter.Config by its name for printing instanceType values.
21+
type Configer interface {
22+
GetLinterConfigs(name string) []*linter.Config
23+
}
24+
1925
// TeamCity printer for TeamCity format.
2026
type TeamCity struct {
2127
w io.Writer
28+
conf Configer
2229
escaper *strings.Replacer
2330
}
2431

25-
// NewTeamCity output format outputs issues according to TeamCity service message format
26-
func NewTeamCity(w io.Writer) *TeamCity {
32+
// NewTeamCity output format outputs issues according to TeamCity service message format.
33+
func NewTeamCity(w io.Writer, conf Configer) *TeamCity {
2734
return &TeamCity{
28-
w: w,
35+
w: w,
36+
conf: conf,
2937
// https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values
3038
escaper: strings.NewReplacer(
3139
"'", "|'",
@@ -46,27 +54,30 @@ func (p *TeamCity) Print(_ context.Context, issues []result.Issue) error {
4654

4755
_, ok := uniqLinters[issue.FromLinter]
4856
if !ok {
49-
inspectionType := InspectionType{
50-
id: issue.FromLinter,
51-
name: issue.FromLinter,
52-
description: issue.FromLinter,
53-
category: "Golangci-lint reports",
54-
}
55-
56-
_, err := inspectionType.Print(p.w, p.escaper)
57-
if err != nil {
58-
return err
57+
linterConfigs := p.conf.GetLinterConfigs(issue.FromLinter)
58+
for _, config := range linterConfigs {
59+
inspectionType := inspectionType{
60+
id: config.Linter.Name(),
61+
name: config.Linter.Name(),
62+
description: config.Linter.Desc(),
63+
category: "Golangci-lint reports",
64+
}
65+
66+
_, err := inspectionType.Print(p.w, p.escaper)
67+
if err != nil {
68+
return err
69+
}
5970
}
6071

6172
uniqLinters[issue.FromLinter] = struct{}{}
6273
}
6374

64-
instance := InspectionInstance{
65-
typeID: issue.FromLinter,
66-
message: issue.Text,
67-
file: issue.FilePath(),
68-
line: issue.Line(),
69-
additionalAttribute: strings.TrimSpace(issue.Severity),
75+
instance := inspectionInstance{
76+
typeID: issue.FromLinter,
77+
message: issue.Text,
78+
file: issue.FilePath(),
79+
line: issue.Line(),
80+
severity: issue.Severity,
7081
}
7182

7283
_, err := instance.Print(p.w, p.escaper)
@@ -78,46 +89,36 @@ func (p *TeamCity) Print(_ context.Context, issues []result.Issue) error {
7889
return nil
7990
}
8091

81-
// InspectionType Each specific warning or an error in code (inspection instance) has an inspection type.
92+
// inspectionType is the unique description of the conducted inspection. Each specific warning or
93+
// an error in code (inspection instance) has an inspection type.
8294
// https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Type
83-
type InspectionType struct {
95+
type inspectionType struct {
8496
id string // (mandatory) limited by 255 characters.
8597
name string // (mandatory) limited by 255 characters.
8698
description string // (mandatory) limited by 255 characters.
8799
category string // (mandatory) limited by 4000 characters.
88100
}
89101

90-
func (i InspectionType) Print(w io.Writer, escaper *strings.Replacer) (int, error) {
102+
func (i inspectionType) Print(w io.Writer, escaper *strings.Replacer) (int, error) {
91103
return fmt.Fprintf(w, "##teamcity[inspectionType id='%s' name='%s' description='%s' category='%s']\n",
92104
limit(i.id, smallLimit), limit(i.name, smallLimit), limit(escaper.Replace(i.description), largeLimit), limit(i.category, smallLimit))
93105
}
94106

95-
// InspectionInstance Reports a specific defect, warning, error message.
107+
// inspectionInstance reports a specific defect, warning, error message.
96108
// Includes location, description, and various optional and custom attributes.
97109
// https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
98-
type InspectionInstance struct {
99-
typeID string // (mandatory) limited by 255 characters.
100-
message string // (optional) limited by 4000 characters.
101-
file string // (mandatory) file path limited by 4000 characters.
102-
line int // (optional) line of the file, integer.
103-
additionalAttribute string // (optional) can be any attribute.
110+
type inspectionInstance struct {
111+
typeID string // (mandatory) limited by 255 characters.
112+
message string // (optional) limited by 4000 characters.
113+
file string // (mandatory) file path limited by 4000 characters.
114+
line int // (optional) line of the file.
115+
severity string // (optional) severity attribute: INFO, ERROR, WARNING, WEAK WARNING.
104116
}
105117

106-
func (i InspectionInstance) Print(w io.Writer, replacer *strings.Replacer) (int, error) {
107-
_, err := fmt.Fprintf(w, "##teamcity[inspection typeId='%s' message='%s' file='%s' line='%d'",
108-
limit(i.typeID, smallLimit), limit(replacer.Replace(i.message), largeLimit), limit(i.file, largeLimit), i.line)
109-
if err != nil {
110-
return 0, err
111-
}
112-
113-
if i.additionalAttribute != "" {
114-
_, err = fmt.Fprintf(w, " additional attribute='%s'", i.additionalAttribute)
115-
if err != nil {
116-
return 0, err
117-
}
118-
}
119-
120-
return fmt.Fprintln(w, "]")
118+
func (i inspectionInstance) Print(w io.Writer, replacer *strings.Replacer) (int, error) {
119+
return fmt.Fprintf(w, "##teamcity[inspection typeId='%s' message='%s' file='%s' line='%d' SEVERITY='%s']\n",
120+
limit(i.typeID, smallLimit), limit(replacer.Replace(i.message), largeLimit), limit(i.file, largeLimit), i.line,
121+
strings.ToUpper(i.severity))
121122
}
122123

123124
func limit(s string, max int) string {

pkg/printers/teamcity_test.go

+42-12
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ import (
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
1111

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

1516
func TestTeamCity_Print(t *testing.T) {
1617
issues := []result.Issue{
1718
{
1819
FromLinter: "linter-a",
19-
Severity: "error",
20-
Text: "some issue",
20+
Severity: "warning",
21+
Text: "warning issue",
2122
Pos: token.Position{
2223
Filename: "path/to/filea.go",
2324
Offset: 2,
@@ -28,7 +29,7 @@ func TestTeamCity_Print(t *testing.T) {
2829
{
2930
FromLinter: "linter-a",
3031
Severity: "error",
31-
Text: "some issue 2",
32+
Text: "error issue",
3233
Pos: token.Position{
3334
Filename: "path/to/filea.go",
3435
Offset: 2,
@@ -37,8 +38,8 @@ func TestTeamCity_Print(t *testing.T) {
3738
},
3839
{
3940
FromLinter: "linter-b",
40-
Severity: "error",
41-
Text: "another issue",
41+
Severity: "info",
42+
Text: "info issue",
4243
SourceLines: []string{
4344
"func foo() {",
4445
"\tfmt.Println(\"bar\")",
@@ -54,22 +55,35 @@ func TestTeamCity_Print(t *testing.T) {
5455
}
5556

5657
buf := new(bytes.Buffer)
57-
printer := NewTeamCity(buf)
58+
printer := NewTeamCity(buf, configerMock(
59+
map[string][]*linter.Config{
60+
"linter-a": {
61+
{
62+
Linter: &linterMock{name: "linter-a", desc: "description for linter-a"},
63+
},
64+
},
65+
"linter-b": {
66+
{
67+
Linter: &linterMock{name: "linter-b", desc: "description for linter-b with escape '\n\r|[] characters"},
68+
},
69+
},
70+
},
71+
))
5872

5973
err := printer.Print(context.Background(), issues)
6074
require.NoError(t, err)
6175

62-
expected := `##teamcity[inspectionType id='linter-a' name='linter-a' description='linter-a' category='Golangci-lint reports']
63-
##teamcity[inspection typeId='linter-a' message='some issue' file='path/to/filea.go' line='10' additional attribute='error']
64-
##teamcity[inspection typeId='linter-a' message='some issue 2' file='path/to/filea.go' line='10' additional attribute='error']
65-
##teamcity[inspectionType id='linter-b' name='linter-b' description='linter-b' category='Golangci-lint reports']
66-
##teamcity[inspection typeId='linter-b' message='another issue' file='path/to/fileb.go' line='300' additional attribute='error']
76+
expected := `##teamcity[inspectionType id='linter-a' name='linter-a' description='description for linter-a' category='Golangci-lint reports']
77+
##teamcity[inspection typeId='linter-a' message='warning issue' file='path/to/filea.go' line='10' SEVERITY='WARNING']
78+
##teamcity[inspection typeId='linter-a' message='error issue' file='path/to/filea.go' line='10' SEVERITY='ERROR']
79+
##teamcity[inspectionType id='linter-b' name='linter-b' description='description for linter-b with escape |'|n|r|||[|] characters' category='Golangci-lint reports']
80+
##teamcity[inspection typeId='linter-b' message='info issue' file='path/to/fileb.go' line='300' SEVERITY='INFO']
6781
`
6882

6983
assert.Equal(t, expected, buf.String())
7084
}
7185

72-
func TestLimit(t *testing.T) {
86+
func TestTeamCity_limit(t *testing.T) {
7387
tests := []struct {
7488
input string
7589
max int
@@ -106,3 +120,19 @@ func TestLimit(t *testing.T) {
106120
require.Equal(t, tc.expected, limit(tc.input, tc.max))
107121
}
108122
}
123+
124+
type configerMock map[string][]*linter.Config
125+
126+
func (c configerMock) GetLinterConfigs(name string) []*linter.Config {
127+
return c[name]
128+
}
129+
130+
type linterMock struct {
131+
linter.Noop
132+
name string
133+
desc string
134+
}
135+
136+
func (l linterMock) Name() string { return l.name }
137+
138+
func (l linterMock) Desc() string { return l.desc }

0 commit comments

Comments
 (0)