1
1
package printers
2
2
3
3
import (
4
+ "encoding/json"
4
5
"fmt"
5
6
"io"
7
+ "os"
6
8
"path/filepath"
7
9
8
10
"github.com/golangci/golangci-lint/pkg/result"
9
11
)
10
12
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"`
22
+ }
23
+
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
+ }
60
+
11
61
type GitHub struct {
12
62
w io.Writer
13
63
}
14
64
15
- const defaultGithubSeverity = "error"
16
-
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
65
+ // NewGitHub output format outputs issues according to GitHub actions the problem matcher regexp.
19
66
func NewGitHub (w io.Writer ) * GitHub {
20
67
return & GitHub {w : w }
21
68
}
22
69
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
70
+ func (p * GitHub ) Print (issues []result.Issue ) error {
71
+ // Note: the file with the problem matcher definition should not be removed.
72
+ // A sleep can mitigate this problem but this will be flaky.
73
+ //
74
+ // Error: Unable to process command '::add-matcher::/tmp/golangci-lint-action-problem-matchers.json' successfully.
75
+ // Error: Could not find file '/tmp/golangci-lint-action-problem-matchers.json'.
76
+ //
77
+ filename , err := storeProblemMatcher ()
78
+ if err != nil {
79
+ return err
80
+ }
81
+
82
+ _ , _ = fmt .Fprintln (p .w , "::debug::problem matcher definition file: " + filename )
83
+
84
+ _ , _ = fmt .Fprintln (p .w , "::add-matcher::" + filename )
85
+
86
+ for ind := range issues {
87
+ _ , err := fmt .Fprintln (p .w , formatIssueAsGitHub (& issues [ind ]))
88
+ if err != nil {
89
+ return err
90
+ }
91
+ }
92
+
93
+ _ , _ = fmt .Fprintln (p .w , "::remove-matcher owner=golangci-lint-action::" )
94
+
95
+ return nil
96
+ }
97
+
98
+ func formatIssueAsGitHub (issue * result.Issue ) string {
99
+ severity := defaultGitHubSeverity
26
100
if issue .Severity != "" {
27
101
severity = issue .Severity
28
102
}
@@ -32,21 +106,49 @@ func formatIssueAsGithub(issue *result.Issue) string {
32
106
// Otherwise, GitHub won't be able to show the annotations pointing to the file path with backslashes.
33
107
file := filepath .ToSlash (issue .FilePath ())
34
108
35
- ret := fmt .Sprintf ("::%s file=%s,line=%d " , severity , file , issue .Line ())
109
+ ret := fmt .Sprintf ("%s \t %s:%d: " , severity , file , issue .Line ())
36
110
if issue .Pos .Column != 0 {
37
- ret += fmt .Sprintf (",col=%d " , issue .Pos .Column )
111
+ ret += fmt .Sprintf ("%d: " , issue .Pos .Column )
38
112
}
39
113
40
- ret += fmt .Sprintf (":: %s (%s)" , issue .Text , issue .FromLinter )
114
+ ret += fmt .Sprintf ("\t %s (%s)" , issue .Text , issue .FromLinter )
41
115
return ret
42
116
}
43
117
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
- }
118
+ func storeProblemMatcher () (string , error ) {
119
+ //nolint:gosec // To be able to clean the file during tests, we need a deterministic filepath.
120
+ file , err := os .Create (filepath .Join (os .TempDir (), filenameGitHubActionProblemMatchers ))
121
+ if err != nil {
122
+ return "" , err
123
+ }
124
+
125
+ defer file .Close ()
126
+
127
+ err = json .NewEncoder (file ).Encode (generateProblemMatcher ())
128
+ if err != nil {
129
+ return "" , err
130
+ }
131
+
132
+ return file .Name (), nil
133
+ }
134
+
135
+ func generateProblemMatcher () GitHubProblemMatchers {
136
+ return GitHubProblemMatchers {
137
+ Matchers : []GitHubMatcher {
138
+ {
139
+ Owner : "golangci-lint-action" ,
140
+ Severity : "error" ,
141
+ Pattern : []GitHubPattern {
142
+ {
143
+ Regexp : `^([^\s]+)\s+([^:]+):(\d+):(?:(\d+):)?\s+(.+)$` ,
144
+ Severity : 1 ,
145
+ File : 2 ,
146
+ Line : 3 ,
147
+ Column : 4 ,
148
+ Message : 5 ,
149
+ },
150
+ },
151
+ },
152
+ },
50
153
}
51
- return nil
52
154
}
0 commit comments