11package printers
22
33import (
4+ "encoding/json"
45 "fmt"
56 "io"
7+ "os"
68 "path/filepath"
79
810 "github.com/golangci/golangci-lint/pkg/result"
911)
1012
11- type GitHub struct {
12- w io.Writer
13+ const defaultGitHubSeverity = "error"
14+
15+ const filenameGitHubActionProblemMatchers = "golangci-lint-action-problem-matchers.json"
16+
17+ // GitHubProblemMatchers defines the root of problem matchers.
18+ // - https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md
19+ // - https://github.com/actions/toolkit/blob/main/docs/commands.md#problem-matchers
20+ type GitHubProblemMatchers struct {
21+ Matchers []GitHubMatcher `json:"problemMatcher,omitempty"`
1322}
1423
15- const defaultGithubSeverity = "error"
24+ // GitHubMatcher defines a problem matcher.
25+ type GitHubMatcher struct {
26+ // Owner an ID field that can be used to remove or replace the problem matcher.
27+ // **required**
28+ Owner string `json:"owner,omitempty"`
29+ // Severity indicates the default severity, either 'warning' or 'error' case-insensitive.
30+ // Defaults to 'error'.
31+ Severity string `json:"severity,omitempty"`
32+ Pattern []GitHubPattern `json:"pattern,omitempty"`
33+ }
34+
35+ // GitHubPattern defines a pattern for a problem matcher.
36+ type GitHubPattern struct {
37+ // Regexp the regexp pattern that provides the groups to match against.
38+ // **required**
39+ Regexp string `json:"regexp,omitempty"`
40+ // File a group number containing the file name.
41+ File int `json:"file,omitempty"`
42+ // FromPath a group number containing a filepath used to root the file (e.g. a project file).
43+ FromPath int `json:"fromPath,omitempty"`
44+ // Line a group number containing the line number.
45+ Line int `json:"line,omitempty"`
46+ // Column a group number containing the column information.
47+ Column int `json:"column,omitempty"`
48+ // Severity a group number containing either 'warning' or 'error' case-insensitive.
49+ // Defaults to `error`.
50+ Severity int `json:"severity,omitempty"`
51+ // Code a group number containing the error code.
52+ Code int `json:"code,omitempty"`
53+ // Message a group number containing the error message.
54+ // **required** at least one pattern must set the message.
55+ Message int `json:"message,omitempty"`
56+ // Loop whether to loop until a match is not found,
57+ // only valid on the last pattern of a multi-pattern matcher.
58+ Loop bool `json:"loop,omitempty"`
59+ }
1660
17- // NewGitHub output format outputs issues according to GitHub actions format:
18- // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
61+ type GitHub struct {
62+ tempPath string
63+ w io.Writer
64+ }
65+
66+ // NewGitHub output format outputs issues according to GitHub actions the problem matcher regexp.
1967func NewGitHub (w io.Writer ) * GitHub {
20- return & GitHub {w : w }
68+ return & GitHub {
69+ tempPath : filepath .Join (os .TempDir (), filenameGitHubActionProblemMatchers ),
70+ w : w ,
71+ }
72+ }
73+
74+ func (p * GitHub ) Print (issues []result.Issue ) error {
75+ // Note: the file with the problem matcher definition should not be removed.
76+ // A sleep can mitigate this problem but this will be flaky.
77+ //
78+ // Result if the file is removed prematurely:
79+ // Error: Unable to process command '::add-matcher::/tmp/golangci-lint-action-problem-matchers.json' successfully.
80+ // Error: Could not find file '/tmp/golangci-lint-action-problem-matchers.json'.
81+ filename , err := p .storeProblemMatcher ()
82+ if err != nil {
83+ return err
84+ }
85+
86+ _ , _ = fmt .Fprintln (p .w , "::debug::problem matcher definition file: " + filename )
87+
88+ _ , _ = fmt .Fprintln (p .w , "::add-matcher::" + filename )
89+
90+ for ind := range issues {
91+ _ , err := fmt .Fprintln (p .w , formatIssueAsGitHub (& issues [ind ]))
92+ if err != nil {
93+ return err
94+ }
95+ }
96+
97+ _ , _ = fmt .Fprintln (p .w , "::remove-matcher owner=golangci-lint-action::" )
98+
99+ return nil
100+ }
101+
102+ func (p * GitHub ) storeProblemMatcher () (string , error ) {
103+ file , err := os .Create (p .tempPath )
104+ if err != nil {
105+ return "" , err
106+ }
107+
108+ defer file .Close ()
109+
110+ err = json .NewEncoder (file ).Encode (generateProblemMatcher ())
111+ if err != nil {
112+ return "" , err
113+ }
114+
115+ return file .Name (), nil
116+ }
117+
118+ func generateProblemMatcher () GitHubProblemMatchers {
119+ return GitHubProblemMatchers {
120+ Matchers : []GitHubMatcher {
121+ {
122+ Owner : "golangci-lint-action" ,
123+ Severity : "error" ,
124+ Pattern : []GitHubPattern {
125+ {
126+ Regexp : `^([^\s]+)\s+([^:]+):(\d+):(?:(\d+):)?\s+(.+)$` ,
127+ Severity : 1 ,
128+ File : 2 ,
129+ Line : 3 ,
130+ Column : 4 ,
131+ Message : 5 ,
132+ },
133+ },
134+ },
135+ },
136+ }
21137}
22138
23- // print each line as: ::error file=app.js,line=10,col=15::Something went wrong
24- func formatIssueAsGithub (issue * result.Issue ) string {
25- severity := defaultGithubSeverity
139+ func formatIssueAsGitHub (issue * result.Issue ) string {
140+ severity := defaultGitHubSeverity
26141 if issue .Severity != "" {
27142 severity = issue .Severity
28143 }
@@ -32,21 +147,11 @@ func formatIssueAsGithub(issue *result.Issue) string {
32147 // Otherwise, GitHub won't be able to show the annotations pointing to the file path with backslashes.
33148 file := filepath .ToSlash (issue .FilePath ())
34149
35- ret := fmt .Sprintf ("::%s file=%s,line=%d " , severity , file , issue .Line ())
150+ ret := fmt .Sprintf ("%s \t %s:%d: " , severity , file , issue .Line ())
36151 if issue .Pos .Column != 0 {
37- ret += fmt .Sprintf (",col=%d " , issue .Pos .Column )
152+ ret += fmt .Sprintf ("%d: " , issue .Pos .Column )
38153 }
39154
40- ret += fmt .Sprintf (":: %s (%s)" , issue .Text , issue .FromLinter )
155+ ret += fmt .Sprintf ("\t %s (%s)" , issue .Text , issue .FromLinter )
41156 return ret
42157}
43-
44- func (p * GitHub ) Print (issues []result.Issue ) error {
45- for ind := range issues {
46- _ , err := fmt .Fprintln (p .w , formatIssueAsGithub (& issues [ind ]))
47- if err != nil {
48- return err
49- }
50- }
51- return nil
52- }
0 commit comments