@@ -42,16 +42,19 @@ import (
4242type lintFlagsCollection struct {
4343 WarningsAsErrors bool `default:"false" flag:"warnings-as-errors" info:"Treat warnings as errors"`
4444 BaseDir string `default:"" flag:"base-dir" info:"Directory to search for .cdc files (defaults to current directory)"`
45+ ShowIgnored bool `default:"false" flag:"show-ignored" info:"Show diagnostics suppressed by //lint:ignore <category> directives"`
4546}
4647
4748type fileResult struct {
48- FilePath string
49- Diagnostics []analysis.Diagnostic
49+ FilePath string
50+ Diagnostics []analysis.Diagnostic
51+ IgnoredDiagnostics []analysis.Diagnostic
5052}
5153
5254type lintResult struct {
53- Results []fileResult
54- exitCode int
55+ Results []fileResult
56+ exitCode int
57+ showIgnored bool
5558}
5659
5760var _ command.ResultWithExitCode = & lintResult {}
@@ -110,6 +113,7 @@ func lint(
110113 return nil , err
111114 }
112115
116+ result .showIgnored = lintFlags .ShowIgnored
113117 return result , nil
114118}
115119
@@ -139,24 +143,32 @@ func lintFiles(
139143 },
140144 }
141145 exitCode = 1
146+ sortDiagnostics (diagnostics )
147+ results = append (results , fileResult {
148+ FilePath : location ,
149+ Diagnostics : diagnostics ,
150+ })
151+ continue
142152 }
143153
144- // Sort for consistent output
145- sortDiagnostics (diagnostics )
154+ active , ignored := filterIgnoredDiagnostics (diagnostics , state , location )
155+ sortDiagnostics (active )
156+ sortDiagnostics (ignored )
146157 results = append (results , fileResult {
147- FilePath : location ,
148- Diagnostics : diagnostics ,
158+ FilePath : location ,
159+ Diagnostics : active ,
160+ IgnoredDiagnostics : ignored ,
149161 })
150162
151- // Set the exitCode to 1 if any of the diagnostics are error-level,
163+ // Set the exitCode to 1 if any of the active diagnostics are error-level,
152164 // or warning-level when warningsAsErrors is set.
153- for _ , diagnostic := range diagnostics {
154- severity := getDiagnosticSeverity (diagnostic )
155- if severity == errorSeverity {
165+ for _ , diagnostic := range active {
166+ sev := getDiagnosticSeverity (diagnostic )
167+ if sev == errorSeverity {
156168 exitCode = 1
157169 break
158170 }
159- if severity == warningSeverity && warningsAsErrors {
171+ if sev == warningSeverity && warningsAsErrors {
160172 exitCode = 1
161173 break
162174 }
@@ -169,6 +181,71 @@ func lintFiles(
169181 }, nil
170182}
171183
184+ // parseLintIgnoreDirectives returns a map of 1-indexed line number to the set of
185+ // categories ignored on that line via //lint:ignore <category> comments.
186+ func parseLintIgnoreDirectives (code string ) map [int ]map [string ]bool {
187+ directives := make (map [int ]map [string ]bool )
188+ for i , line := range strings .Split (code , "\n " ) {
189+ lineNum := i + 1
190+ _ , after , found := strings .Cut (line , "//lint:ignore " )
191+ if ! found {
192+ continue
193+ }
194+ category := strings .TrimSpace (after )
195+ if sp := strings .IndexByte (category , ' ' ); sp >= 0 {
196+ category = category [:sp ]
197+ }
198+ if category == "" {
199+ continue
200+ }
201+ if directives [lineNum ] == nil {
202+ directives [lineNum ] = make (map [string ]bool )
203+ }
204+ directives [lineNum ][category ] = true
205+ }
206+ return directives
207+ }
208+
209+ func isDiagnosticIgnored (d analysis.Diagnostic , directives map [int ]map [string ]bool ) bool {
210+ line := d .Range .StartPos .Line
211+ for _ , l := range []int {line , line - 1 } {
212+ if cats , ok := directives [l ]; ok && cats [d .Category ] {
213+ return true
214+ }
215+ }
216+ return false
217+ }
218+
219+ // filterIgnoredDiagnostics reads the source for location and splits diagnostics
220+ // into active and ignored based on //lint:ignore directives. If the source cannot
221+ // be read, all diagnostics are returned as active.
222+ func filterIgnoredDiagnostics (
223+ diagnostics []analysis.Diagnostic ,
224+ state * flowkit.State ,
225+ location string ,
226+ ) (active , ignored []analysis.Diagnostic ) {
227+ code , err := state .ReadFile (location )
228+ if err != nil {
229+ if diagnostics == nil {
230+ return []analysis.Diagnostic {}, nil
231+ }
232+ return diagnostics , nil
233+ }
234+
235+ directives := parseLintIgnoreDirectives (string (code ))
236+ for _ , d := range diagnostics {
237+ if isDiagnosticIgnored (d , directives ) {
238+ ignored = append (ignored , d )
239+ } else {
240+ active = append (active , d )
241+ }
242+ }
243+ if active == nil {
244+ active = []analysis.Diagnostic {}
245+ }
246+ return active , ignored
247+ }
248+
172249func getDiagnosticSeverity (
173250 diagnostic analysis.Diagnostic ,
174251) severity {
@@ -212,6 +289,18 @@ func renderDiagnostic(diagnostic analysis.Diagnostic) string {
212289 )
213290}
214291
292+ func renderIgnoredDiagnostic (diagnostic analysis.Diagnostic ) string {
293+ startPos := diagnostic .Range .StartPos
294+ locationText := fmt .Sprintf ("%s:%d:%d:" , diagnostic .Location .String (), startPos .Line , startPos .Column )
295+ categoryText := fmt .Sprintf ("%s:" , diagnostic .Category )
296+
297+ return fmt .Sprintf ("%s %s %s (ignored)" ,
298+ aurora .Gray (12 , locationText ).String (),
299+ aurora .Gray (12 , categoryText ).String (),
300+ aurora .Gray (12 , diagnostic .Message ).String (),
301+ )
302+ }
303+
215304func (r * lintResult ) countProblems () (int , int ) {
216305 numErrors := 0
217306 numWarnings := 0
@@ -227,36 +316,65 @@ func (r *lintResult) countProblems() (int, int) {
227316 return numErrors , numWarnings
228317}
229318
319+ func (r * lintResult ) countIgnored () int {
320+ n := 0
321+ for _ , result := range r .Results {
322+ n += len (result .IgnoredDiagnostics )
323+ }
324+ return n
325+ }
326+
230327func (r * lintResult ) String () string {
231328 var sb strings.Builder
232329
233330 for _ , result := range r .Results {
234331 for _ , diagnostic := range result .Diagnostics {
235- sb .WriteString (fmt .Sprintf ("%s\n \n " , renderDiagnostic (diagnostic )))
332+ fmt .Fprintf (& sb , "%s\n \n " , renderDiagnostic (diagnostic ))
333+ }
334+ if r .showIgnored {
335+ for _ , diagnostic := range result .IgnoredDiagnostics {
336+ fmt .Fprintf (& sb , "%s\n \n " , renderIgnoredDiagnostic (diagnostic ))
337+ }
236338 }
237339 }
238340
239- var color aurora.Color
240341 numErrors , numWarnings := r .countProblems ()
342+ numIgnored := r .countIgnored ()
343+ total := numErrors + numWarnings + numIgnored
344+
345+ var color aurora.Color
241346 if numErrors > 0 {
242347 color = aurora .RedFg
243348 } else if numWarnings > 0 {
244349 color = aurora .YellowFg
245350 }
246351
247- total := numErrors + numWarnings
248- if total > 0 {
352+ if total == 0 {
353+ sb .WriteString (aurora .Green ("Lint passed" ).String ())
354+ return sb .String ()
355+ }
356+
357+ if numIgnored > 0 {
249358 sb .WriteString (aurora .Colorize (fmt .Sprintf (
250- "%d %s (%d %s, %d %s)" ,
359+ "%d %s (%d %s, %d %s, %d ignored )" ,
251360 total ,
252361 util .Pluralize ("problem" , total ),
253362 numErrors ,
254363 util .Pluralize ("error" , numErrors ),
255364 numWarnings ,
256365 util .Pluralize ("warning" , numWarnings ),
366+ numIgnored ,
257367 ), color ).String ())
258368 } else {
259- sb .WriteString (aurora .Green ("Lint passed" ).String ())
369+ sb .WriteString (aurora .Colorize (fmt .Sprintf (
370+ "%d %s (%d %s, %d %s)" ,
371+ total ,
372+ util .Pluralize ("problem" , total ),
373+ numErrors ,
374+ util .Pluralize ("error" , numErrors ),
375+ numWarnings ,
376+ util .Pluralize ("warning" , numWarnings ),
377+ ), color ).String ())
260378 }
261379
262380 return sb .String ()
@@ -268,12 +386,27 @@ func (r *lintResult) JSON() any {
268386
269387func (r * lintResult ) Oneliner () string {
270388 numErrors , numWarnings := r .countProblems ()
271- total := numErrors + numWarnings
389+ numIgnored := r .countIgnored ()
390+ total := numErrors + numWarnings + numIgnored
272391
273- if total > 0 {
274- return fmt . Sprintf ( "%d %s (%d %s, %d %s)" , total , util . Pluralize ( "problem" , total ), numErrors , util . Pluralize ( "error" , numErrors ), numWarnings , util . Pluralize ( "warning" , numWarnings ))
392+ if total == 0 {
393+ return "Lint passed"
275394 }
276- return "Lint passed"
395+
396+ if numIgnored > 0 {
397+ return fmt .Sprintf ("%d %s (%d %s, %d %s, %d ignored)" ,
398+ total , util .Pluralize ("problem" , total ),
399+ numErrors , util .Pluralize ("error" , numErrors ),
400+ numWarnings , util .Pluralize ("warning" , numWarnings ),
401+ numIgnored ,
402+ )
403+ }
404+
405+ return fmt .Sprintf ("%d %s (%d %s, %d %s)" ,
406+ total , util .Pluralize ("problem" , total ),
407+ numErrors , util .Pluralize ("error" , numErrors ),
408+ numWarnings , util .Pluralize ("warning" , numWarnings ),
409+ )
277410}
278411
279412func (r * lintResult ) ExitCode () int {
0 commit comments