1- use std:: sync:: LazyLock ;
2-
31use anyhow:: anyhow;
42use github_actions_models:: common:: Uses ;
5- use regex:: Regex ;
63use subfeature:: Subfeature ;
74use yamlpatch:: { Op , Patch } ;
85
@@ -15,6 +12,7 @@ use crate::{
1512 } ,
1613 github,
1714 models:: { StepCommon , action:: CompositeStep , uses:: RepositoryUsesExt , workflow:: Step } ,
15+ utils:: once:: static_regex,
1816} ;
1917
2018pub ( crate ) struct RefVersionMismatch {
@@ -27,17 +25,21 @@ audit_meta!(
2725 "action's hash pin has mismatched or missing version comment"
2826) ;
2927
30- #[ allow( clippy:: unwrap_used) ]
31- static VERSION_COMMENT_PATTERNS : LazyLock < Vec < Regex > > = LazyLock :: new ( || {
32- vec ! [
33- // Matches "# tag=v2.8.0", "# tag=v6-beta", or any non-whitespace tag token.
34- Regex :: new( r"#\s*tag\s*=\s*(\S+)" ) . unwrap( ) ,
35- // Matches "# v2.8.0" and prerelease forms like "# v1.2.3-rc.1", with or without the `v` suffix.
36- Regex :: new( r"#\s*(v?\d+(?:\.\d+)*(?:-?[\w.-]+)?)" ) . unwrap( ) ,
37- // More flexible: "# version: 2.8.0"
38- Regex :: new( r"#\s*(?:version|ver)\s*[:=]\s*(v?\d+(?:\.\d+)*(?:-?[\w.-]+)?)" ) . unwrap( ) ,
39- ]
40- } ) ;
28+ static_regex ! (
29+ VERSION_COMMENT_PATTERN ,
30+ r#"(?x) # verbose mode
31+ ^ # start of string
32+ \# # start of comment
33+ \s* # optional whitespace
34+ (?: # start non-capturing group for version prefix
35+ (?:tag|version|ver)\s*[:=]\s* # version prefix + `:` or `=`
36+ )? # end optional non-capturing group
37+ ( # start capturing group for version
38+ \S+ # one or more non-whitespace characters
39+ ) # end capturing group for version
40+ $ # end of string
41+ "#
42+ ) ;
4143
4244#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
4345enum CommentVersionState < ' doc > {
@@ -49,12 +51,10 @@ enum CommentVersionState<'doc> {
4951impl RefVersionMismatch {
5052 fn extract_version_from_comments < ' doc > ( comments : & ' doc [ Comment < ' doc > ] ) -> Option < & ' doc str > {
5153 for comment in comments {
52- for pattern in VERSION_COMMENT_PATTERNS . iter ( ) {
53- if let Some ( captures) = pattern. captures ( comment. as_ref ( ) )
54- && let Some ( version_match) = captures. get ( 1 )
55- {
56- return Some ( version_match. as_str ( ) ) ;
57- }
54+ if let Some ( captures) = VERSION_COMMENT_PATTERN . captures ( comment. as_ref ( ) )
55+ && let Some ( version_match) = captures. get ( 1 )
56+ {
57+ return Some ( version_match. as_str ( ) ) ;
5858 }
5959 }
6060 None
@@ -292,7 +292,7 @@ mod tests {
292292 }
293293
294294 #[ test]
295- fn test_version_comment_patterns ( ) {
295+ fn test_version_comment_pattern ( ) {
296296 let test_cases = vec ! [
297297 ( "# tag=v2.8.0" , Some ( "v2.8.0" ) ) ,
298298 ( "# tag=v6-beta" , Some ( "v6-beta" ) ) ,
@@ -316,21 +316,24 @@ mod tests {
316316 ( "# ver=1.0.0" , Some ( "1.0.0" ) ) ,
317317 ( "# visit the docs" , None ) ,
318318 ( "# some other comment" , None ) ,
319+ ( "# zizmor: ignore[ref-version-mismatch]" , None ) ,
319320 ] ;
320321
321322 for ( comment, expected) in test_cases {
322323 // Test the pattern matching directly
323- let comment_text = comment;
324- let mut found_version = None ;
325- for pattern in VERSION_COMMENT_PATTERNS . iter ( ) {
326- if let Some ( captures) = pattern. captures ( comment_text) {
327- if let Some ( version_match) = captures. get ( 1 ) {
328- found_version = Some ( version_match. as_str ( ) ) ;
329- break ;
330- }
324+ match ( VERSION_COMMENT_PATTERN . captures ( comment) , expected) {
325+ ( None , None ) => ( ) ,
326+ ( None , Some ( expected) ) => {
327+ assert ! (
328+ false ,
329+ "Got no match in '{comment}', but expected {expected}"
330+ )
331+ }
332+ ( Some ( caps) , None ) => {
333+ assert ! ( false , "Got unexpected match: {caps:?}" )
331334 }
335+ ( Some ( _) , Some ( _) ) => ( ) ,
332336 }
333- assert_eq ! ( found_version, expected, "Failed for comment: {}" , comment) ;
334337 }
335338 }
336339
0 commit comments