1
1
//! Models and APIs for handling findings and their locations.
2
2
3
- use std:: borrow:: Cow ;
3
+ use std:: { borrow:: Cow , sync :: LazyLock } ;
4
4
5
5
use anyhow:: Result ;
6
6
use clap:: ValueEnum ;
7
7
use locate:: Locator ;
8
+ use regex:: Regex ;
8
9
use serde:: Serialize ;
9
10
use terminal_link:: Link ;
10
11
@@ -186,6 +187,29 @@ impl From<&yamlpath::Location> for ConcreteLocation {
186
187
}
187
188
}
188
189
190
+ static IGNORE_EXPR : LazyLock < Regex > =
191
+ LazyLock :: new ( || Regex :: new ( r"^# zizmor: ignore\[(.+)\]\s*$" ) . unwrap ( ) ) ;
192
+
193
+ /// Represents a single source comment.
194
+ #[ derive( Serialize ) ]
195
+ #[ serde( transparent) ]
196
+ pub ( crate ) struct Comment < ' w > ( & ' w str ) ;
197
+
198
+ impl < ' w > Comment < ' w > {
199
+ fn ignores ( & self , rule_id : & str ) -> bool {
200
+ // Extracts foo,bar from `# zizmor: ignore[foo,bar]`
201
+ let Some ( caps) = IGNORE_EXPR . captures ( self . 0 ) else {
202
+ return false ;
203
+ } ;
204
+
205
+ caps. get ( 1 )
206
+ . unwrap ( )
207
+ . as_str ( )
208
+ . split ( "," )
209
+ . any ( |r| r. trim ( ) == rule_id)
210
+ }
211
+ }
212
+
189
213
/// An extracted feature, along with its concrete location.
190
214
#[ derive( Serialize ) ]
191
215
pub ( crate ) struct Feature < ' w > {
@@ -200,6 +224,9 @@ pub(crate) struct Feature<'w> {
200
224
/// The feature's textual content.
201
225
pub ( crate ) feature : & ' w str ,
202
226
227
+ /// Any comments within the feature's span.
228
+ pub ( crate ) comments : Vec < Comment < ' w > > ,
229
+
203
230
/// The feature's parent's textual content.
204
231
pub ( crate ) parent_feature : & ' w str ,
205
232
}
@@ -273,7 +300,7 @@ impl<'w> FindingBuilder<'w> {
273
300
. map ( |l| l. clone ( ) . concretize ( workflow) )
274
301
. collect :: < Result < Vec < _ > > > ( ) ?;
275
302
276
- let should_ignore = self . ignored_from_inlined_comment ( workflow , & locations, self . ident ) ;
303
+ let should_ignore = self . ignored_from_inlined_comment ( & locations, self . ident ) ;
277
304
278
305
Ok ( Finding {
279
306
ident : self . ident ,
@@ -288,34 +315,54 @@ impl<'w> FindingBuilder<'w> {
288
315
} )
289
316
}
290
317
291
- fn ignored_from_inlined_comment (
292
- & self ,
293
- workflow : & Workflow ,
294
- locations : & [ Location ] ,
295
- id : & str ,
296
- ) -> bool {
297
- let document_lines = & workflow. document . source ( ) . lines ( ) . collect :: < Vec < _ > > ( ) ;
298
- let line_ranges = locations
318
+ fn ignored_from_inlined_comment ( & self , locations : & [ Location ] , id : & str ) -> bool {
319
+ locations
299
320
. iter ( )
300
- . map ( |l| {
301
- (
302
- l. concrete . location . start_point . row ,
303
- l. concrete . location . end_point . row ,
304
- )
305
- } )
306
- . collect :: < Vec < _ > > ( ) ;
307
-
308
- let inlined_ignore = format ! ( "# zizmor: ignore[{}]" , id) ;
309
- for ( start, end) in line_ranges {
310
- for document_line in start..( end + 1 ) {
311
- if let Some ( line) = document_lines. get ( document_line) {
312
- if line. rfind ( & inlined_ignore) . is_some ( ) {
313
- return true ;
314
- }
315
- }
316
- }
317
- }
321
+ . flat_map ( |l| & l. concrete . comments )
322
+ . any ( |c| c. ignores ( id) )
323
+ }
324
+ }
318
325
319
- false
326
+ #[ cfg( test) ]
327
+ mod tests {
328
+ use crate :: finding:: Comment ;
329
+
330
+ #[ test]
331
+ fn test_comment_ignores ( ) {
332
+ let cases = & [
333
+ // Trivial cases.
334
+ ( "# zizmor: ignore[foo]" , "foo" , true ) ,
335
+ ( "# zizmor: ignore[foo,bar]" , "foo" , true ) ,
336
+ // Dashes are OK.
337
+ ( "# zizmor: ignore[foo,bar,foo-bar]" , "foo-bar" , true ) ,
338
+ // Spaces are OK.
339
+ ( "# zizmor: ignore[foo, bar, foo-bar]" , "foo-bar" , true ) ,
340
+ // Extra commas and duplicates are nonsense but OK.
341
+ ( "# zizmor: ignore[foo,foo,,foo,,,,foo,]" , "foo" , true ) ,
342
+ // Valid ignore, but not a match.
343
+ ( "# zizmor: ignore[foo,bar]" , "baz" , false ) ,
344
+ // Invalid ignore: empty rule list.
345
+ ( "# zizmor: ignore[]" , "" , false ) ,
346
+ ( "# zizmor: ignore[]" , "foo" , false ) ,
347
+ // Invalid ignore: no commas.
348
+ ( "# zizmor: ignore[foo bar]" , "foo" , false ) ,
349
+ // Invalid ignore: missing opening and/or closing [].
350
+ ( "# zizmor: ignore[foo" , "foo" , false ) ,
351
+ ( "# zizmor: ignore foo" , "foo" , false ) ,
352
+ ( "# zizmor: ignore foo]" , "foo" , false ) ,
353
+ // Invalid ignore: space after # and : is mandatory and fixed.
354
+ ( "# zizmor:ignore[foo]" , "foo" , false ) ,
355
+ ( "#zizmor: ignore[foo]" , "foo" , false ) ,
356
+ ( "# zizmor: ignore[foo]" , "foo" , false ) ,
357
+ ( "# zizmor: ignore[foo]" , "foo" , false ) ,
358
+ ] ;
359
+
360
+ for ( comment, rule, ignores) in cases {
361
+ assert_eq ! (
362
+ Comment ( comment) . ignores( rule) ,
363
+ * ignores,
364
+ "{comment} does not ignore {rule}"
365
+ )
366
+ }
320
367
}
321
368
}
0 commit comments