-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: add TeamCity output format #3606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6d8d023
feat: add team city output format
ferhatelmas d404923
review
ldez e799af1
fix `additional attribute`: should not contain spaces
alexandear f9532b7
Revert "fix `additional attribute`: should not contain spaces"
ldez 283573c
review: trim and remove additional attribute if empty
ldez 7a49b50
review: tiny simplification for branching
ferhatelmas e52e279
review: add test for limit
ferhatelmas 748c5ba
review: add comment for additional attr
ferhatelmas 314b687
review: drop comment for additional attr
ferhatelmas 97befd1
Merge branch 'master' into teamcity-output
ferhatelmas f40b25d
fix TeamCity printer
alexandear f6b0cc2
Add nolint:lll
alexandear a4cc230
review: remove the call to the DBManager
ldez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package printers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"strings" | ||
"unicode/utf8" | ||
|
||
"github.com/golangci/golangci-lint/pkg/result" | ||
) | ||
|
||
// Field limits. | ||
const ( | ||
smallLimit = 255 | ||
largeLimit = 4000 | ||
) | ||
|
||
// TeamCity printer for TeamCity format. | ||
type TeamCity struct { | ||
w io.Writer | ||
escaper *strings.Replacer | ||
} | ||
|
||
// NewTeamCity output format outputs issues according to TeamCity service message format | ||
func NewTeamCity(w io.Writer) *TeamCity { | ||
return &TeamCity{ | ||
w: w, | ||
// https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values | ||
escaper: strings.NewReplacer( | ||
"'", "|'", | ||
"\n", "|n", | ||
"\r", "|r", | ||
"|", "||", | ||
"[", "|[", | ||
"]", "|]", | ||
), | ||
} | ||
} | ||
|
||
func (p *TeamCity) Print(_ context.Context, issues []result.Issue) error { | ||
uniqLinters := map[string]struct{}{} | ||
|
||
for i := range issues { | ||
issue := issues[i] | ||
|
||
_, ok := uniqLinters[issue.FromLinter] | ||
if !ok { | ||
inspectionType := InspectionType{ | ||
id: issue.FromLinter, | ||
name: issue.FromLinter, | ||
description: issue.FromLinter, | ||
category: "Golangci-lint reports", | ||
} | ||
|
||
_, err := inspectionType.Print(p.w, p.escaper) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
uniqLinters[issue.FromLinter] = struct{}{} | ||
} | ||
|
||
instance := InspectionInstance{ | ||
typeID: issue.FromLinter, | ||
message: issue.Text, | ||
file: issue.FilePath(), | ||
line: issue.Line(), | ||
additionalAttribute: strings.TrimSpace(issue.Severity), | ||
} | ||
|
||
_, err := instance.Print(p.w, p.escaper) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// InspectionType Each specific warning or an error in code (inspection instance) has an inspection type. | ||
// https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Type | ||
type InspectionType struct { | ||
id string // (mandatory) limited by 255 characters. | ||
name string // (mandatory) limited by 255 characters. | ||
description string // (mandatory) limited by 255 characters. | ||
category string // (mandatory) limited by 4000 characters. | ||
} | ||
|
||
func (i InspectionType) Print(w io.Writer, escaper *strings.Replacer) (int, error) { | ||
return fmt.Fprintf(w, "##teamcity[inspectionType id='%s' name='%s' description='%s' category='%s']\n", | ||
limit(i.id, smallLimit), limit(i.name, smallLimit), limit(escaper.Replace(i.description), largeLimit), limit(i.category, smallLimit)) | ||
} | ||
|
||
// InspectionInstance Reports a specific defect, warning, error message. | ||
// Includes location, description, and various optional and custom attributes. | ||
// https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance | ||
type InspectionInstance struct { | ||
typeID string // (mandatory) limited by 255 characters. | ||
message string // (optional) limited by 4000 characters. | ||
file string // (mandatory) file path limited by 4000 characters. | ||
line int // (optional) line of the file, integer. | ||
additionalAttribute string // (optional) severity of the inspection; debug, info, warning, error, etc. | ||
ferhatelmas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func (i InspectionInstance) Print(w io.Writer, replacer *strings.Replacer) (int, error) { | ||
_, err := fmt.Fprintf(w, "##teamcity[inspection typeId='%s' message='%s' file='%s' line='%d'", | ||
limit(i.typeID, smallLimit), limit(replacer.Replace(i.message), largeLimit), limit(i.file, largeLimit), i.line) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
if i.additionalAttribute != "" { | ||
_, err = fmt.Fprintf(w, " additional attribute='%s'", i.additionalAttribute) | ||
if err != nil { | ||
return 0, err | ||
} | ||
} | ||
|
||
return fmt.Fprintln(w, "]") | ||
} | ||
|
||
func limit(s string, max int) string { | ||
var size, count int | ||
for i := 0; i < max && count < len(s); i++ { | ||
_, size = utf8.DecodeRuneInString(s[count:]) | ||
count += size | ||
} | ||
|
||
return s[:count] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package printers | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"go/token" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/golangci/golangci-lint/pkg/result" | ||
) | ||
|
||
func TestTeamCity_Print(t *testing.T) { | ||
issues := []result.Issue{ | ||
{ | ||
FromLinter: "linter-a", | ||
Severity: "error", | ||
Text: "some issue", | ||
Pos: token.Position{ | ||
Filename: "path/to/filea.go", | ||
Offset: 2, | ||
Line: 10, | ||
Column: 4, | ||
}, | ||
}, | ||
{ | ||
FromLinter: "linter-a", | ||
Severity: "error", | ||
Text: "some issue 2", | ||
Pos: token.Position{ | ||
Filename: "path/to/filea.go", | ||
Offset: 2, | ||
Line: 10, | ||
}, | ||
}, | ||
{ | ||
FromLinter: "linter-b", | ||
Severity: "error", | ||
Text: "another issue", | ||
SourceLines: []string{ | ||
"func foo() {", | ||
"\tfmt.Println(\"bar\")", | ||
"}", | ||
}, | ||
Pos: token.Position{ | ||
Filename: "path/to/fileb.go", | ||
Offset: 5, | ||
Line: 300, | ||
Column: 9, | ||
}, | ||
}, | ||
} | ||
|
||
buf := new(bytes.Buffer) | ||
printer := NewTeamCity(buf) | ||
|
||
err := printer.Print(context.Background(), issues) | ||
require.NoError(t, err) | ||
|
||
expected := `##teamcity[inspectionType id='linter-a' name='linter-a' description='linter-a' category='Golangci-lint reports'] | ||
##teamcity[inspection typeId='linter-a' message='some issue' file='path/to/filea.go' line='10' additional attribute='error'] | ||
##teamcity[inspection typeId='linter-a' message='some issue 2' file='path/to/filea.go' line='10' additional attribute='error'] | ||
##teamcity[inspectionType id='linter-b' name='linter-b' description='linter-b' category='Golangci-lint reports'] | ||
##teamcity[inspection typeId='linter-b' message='another issue' file='path/to/fileb.go' line='300' additional attribute='error'] | ||
` | ||
|
||
assert.Equal(t, expected, buf.String()) | ||
} | ||
|
||
func TestLimit(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
max int | ||
expected string | ||
}{ | ||
{ | ||
input: "golangci-lint", | ||
max: 0, | ||
expected: "", | ||
}, | ||
{ | ||
input: "golangci-lint", | ||
max: 8, | ||
expected: "golangci", | ||
}, | ||
{ | ||
input: "golangci-lint", | ||
max: 13, | ||
expected: "golangci-lint", | ||
}, | ||
{ | ||
input: "golangci-lint", | ||
max: 15, | ||
expected: "golangci-lint", | ||
}, | ||
{ | ||
input: "こんにちは", | ||
max: 3, | ||
expected: "こんに", | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
require.Equal(t, tc.expected, limit(tc.input, tc.max)) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.