-
Notifications
You must be signed in to change notification settings - Fork 0
fix(rules): match FlagWithValue patterns against =-joined tokens
#180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a8181a4
c62af86
6604b75
15e18ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,6 +197,7 @@ fn match_tokens_core<'a>( | |
| // Search for the flag anywhere in the remaining command tokens | ||
| // (order-independent matching) | ||
| for i in 0..cmd_tokens.len() { | ||
| // Case 1: space-separated flag and value (e.g. `--sort value`) | ||
| if aliases.iter().any(|a| a.as_str() == cmd_tokens[i]) | ||
| && i + 1 < cmd_tokens.len() | ||
| && match_single_token(value, cmd_tokens[i + 1], definitions) | ||
|
|
@@ -217,6 +218,31 @@ fn match_tokens_core<'a>( | |
| return true; | ||
| } | ||
| } | ||
|
|
||
| // Case 2: `=`-joined flag and value (e.g. `--sort=value`) | ||
| if let Some(eq_pos) = cmd_tokens[i].find('=') { | ||
| let flag_part = &cmd_tokens[i][..eq_pos]; | ||
| let value_part = &cmd_tokens[i][eq_pos + 1..]; | ||
| if flag_part.starts_with('-') | ||
| && aliases.iter().any(|a| a.as_str() == flag_part) | ||
| && match_single_token(value, value_part, definitions) | ||
| { | ||
| let remaining = remove_indices(cmd_tokens, &[i]); | ||
| if let Some(ref mut caps) = captures { | ||
| let saved_len = caps.len(); | ||
| if matches!(value.as_ref(), PatternToken::Wildcard) { | ||
| caps.push(value_part); | ||
| } | ||
| if match_tokens_core(rest, &remaining, definitions, steps, Some(*caps)) | ||
| { | ||
| return true; | ||
| } | ||
| caps.truncate(saved_len); | ||
| } else if match_tokens_core(rest, &remaining, definitions, steps, None) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
devin-ai-integration[bot] marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+222
to
+245
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The References
|
||
| } | ||
| false | ||
| } | ||
|
|
@@ -470,6 +496,7 @@ fn extract_placeholder_all<'a>( | |
|
|
||
| PatternToken::FlagWithValue { aliases, value } => { | ||
| for i in 0..cmd_tokens.len() { | ||
| // Case 1: space-separated flag and value | ||
| if aliases.iter().any(|a| a.as_str() == cmd_tokens[i]) | ||
| && i + 1 < cmd_tokens.len() | ||
| && match_single_token(value, cmd_tokens[i + 1], definitions) | ||
|
|
@@ -484,6 +511,26 @@ fn extract_placeholder_all<'a>( | |
| all_candidates, | ||
| )?; | ||
| } | ||
|
|
||
| // Case 2: `=`-joined flag and value | ||
| if let Some(eq_pos) = cmd_tokens[i].find('=') { | ||
| let flag_part = &cmd_tokens[i][..eq_pos]; | ||
| let value_part = &cmd_tokens[i][eq_pos + 1..]; | ||
| if flag_part.starts_with('-') | ||
| && aliases.iter().any(|a| a.as_str() == flag_part) | ||
| && match_single_token(value, value_part, definitions) | ||
| { | ||
| let remaining = remove_indices(cmd_tokens, &[i]); | ||
| extract_placeholder_all( | ||
| rest, | ||
| &remaining, | ||
| definitions, | ||
| steps, | ||
| captured, | ||
| all_candidates, | ||
| )?; | ||
| } | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
@@ -624,10 +671,22 @@ fn optional_flags_absent(optional_tokens: &[PatternToken], cmd_tokens: &[&str]) | |
| for token in optional_tokens { | ||
| match token { | ||
| PatternToken::FlagWithValue { aliases, .. } => { | ||
| if cmd_tokens | ||
| .iter() | ||
| .any(|t| aliases.iter().any(|a| a.as_str() == *t)) | ||
| { | ||
| if cmd_tokens.iter().any(|t| { | ||
| // Exact match (space-separated form) | ||
| if aliases.iter().any(|a| a.as_str() == *t) { | ||
| return true; | ||
| } | ||
| // `=`-joined form: check the flag portion before `=` | ||
| if let Some(eq_pos) = t.find('=') { | ||
| let flag_part = &t[..eq_pos]; | ||
| if flag_part.starts_with('-') | ||
| && aliases.iter().any(|a| a.as_str() == flag_part) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| false | ||
| }) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
@@ -1164,6 +1223,32 @@ mod tests { | |
| "curl -X POST https://example.com -s", | ||
| false | ||
| )] | ||
| // `=`-joined flag with value | ||
| #[case::optional_flag_with_value_equals_joined( | ||
| "git branch [--sort *]", | ||
| "git branch --sort=-committerdate", | ||
| true | ||
| )] | ||
| #[case::optional_flag_with_value_equals_joined_absent( | ||
| "git branch [--sort *]", | ||
| "git branch", | ||
| true | ||
| )] | ||
| #[case::optional_flag_with_value_equals_joined_with_other_flags( | ||
| "git branch [-a] [--sort *]", | ||
| "git branch -a --sort=-committerdate", | ||
| true | ||
| )] | ||
| #[case::optional_flag_with_value_equals_joined_specific_value( | ||
| "curl [-X|--request GET] *", | ||
| "curl -X=GET https://example.com", | ||
| true | ||
| )] | ||
| #[case::optional_flag_with_value_equals_joined_wrong_value( | ||
| "curl [-X|--request GET] *", | ||
| "curl -X=POST https://example.com", | ||
| false | ||
| )] | ||
| fn optional_matching( | ||
| #[case] pattern_str: &str, | ||
| #[case] command_str: &str, | ||
|
|
@@ -1567,13 +1652,13 @@ mod tests { | |
| } | ||
|
|
||
| #[rstest] | ||
| fn extract_placeholder_with_flag_with_value(empty_defs: Definitions) { | ||
| // FlagWithValue token before <cmd> | ||
| let result = check_extract( | ||
| "run -m|--mode debug <cmd>", | ||
| "run -m debug echo hello", | ||
| &empty_defs, | ||
| ); | ||
| #[case::space_separated("run -m debug echo hello")] | ||
| #[case::equals_joined("run -m=debug echo hello")] | ||
| fn extract_placeholder_with_flag_with_value( | ||
| #[case] command_str: &str, | ||
| empty_defs: Definitions, | ||
| ) { | ||
| let result = check_extract("run -m|--mode debug <cmd>", command_str, &empty_defs); | ||
| assert_eq!(result, vec![vec!["echo".to_string(), "hello".to_string()]]); | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.