revset: add parsing function that rejects expression other than symbol name

This commit is contained in:
Yuya Nishihara 2025-02-17 15:55:00 +09:00
parent 683ee9287e
commit 59ca16705e
3 changed files with 43 additions and 0 deletions

View file

@ -122,6 +122,8 @@ program = _{
SOI ~ whitespace* ~ (program_modifier ~ whitespace*)? ~ expression ~ whitespace* ~ EOI
}
symbol_name = _{ SOI ~ symbol ~ EOI }
function_alias_declaration = {
function_name ~ "(" ~ whitespace* ~ formal_parameters ~ whitespace* ~ ")"
}

View file

@ -52,6 +52,7 @@ use crate::repo::RepoLoaderError;
use crate::repo_path::RepoPathUiConverter;
use crate::revset_parser;
pub use crate::revset_parser::expect_literal;
pub use crate::revset_parser::parse_symbol;
pub use crate::revset_parser::BinaryOp;
pub use crate::revset_parser::ExpressionKind;
pub use crate::revset_parser::ExpressionNode;

View file

@ -130,6 +130,7 @@ impl Rule {
Rule::expression => None,
Rule::program_modifier => None,
Rule::program => None,
Rule::symbol_name => None,
Rule::function_alias_declaration => None,
Rule::alias_declaration => None,
}
@ -689,6 +690,22 @@ pub fn is_identifier(text: &str) -> bool {
}
}
/// Parses the text as a revset symbol, rejects empty string.
pub fn parse_symbol(text: &str) -> Result<String, RevsetParseError> {
let mut pairs = RevsetParser::parse(Rule::symbol_name, text)?;
let first = pairs.next().unwrap();
let span = first.as_span();
let name = parse_as_string_literal(first);
if name.is_empty() {
Err(RevsetParseError::expression(
"Expected non-empty string",
span,
))
} else {
Ok(name)
}
}
pub type RevsetAliasesMap = AliasesMap<RevsetAliasParser, String>;
#[derive(Clone, Debug, Default)]
@ -1287,6 +1304,29 @@ mod tests {
);
}
#[test]
fn test_parse_symbol_explicitly() {
assert_matches!(parse_symbol("").as_deref(), Err(_));
// empty string could be a valid ref name, but it would be super
// confusing if identifier was empty.
assert_matches!(parse_symbol("''").as_deref(), Err(_));
assert_matches!(parse_symbol("foo.bar").as_deref(), Ok("foo.bar"));
assert_matches!(parse_symbol("foo@bar").as_deref(), Err(_));
assert_matches!(parse_symbol("foo bar").as_deref(), Err(_));
assert_matches!(parse_symbol("'foo bar'").as_deref(), Ok("foo bar"));
assert_matches!(parse_symbol(r#""foo\tbar""#).as_deref(), Ok("foo\tbar"));
// leading/trailing whitespace is NOT ignored.
assert_matches!(parse_symbol(" foo").as_deref(), Err(_));
assert_matches!(parse_symbol("foo ").as_deref(), Err(_));
// (foo) could be parsed as a symbol "foo", but is rejected because user
// might expect a literal "(foo)".
assert_matches!(parse_symbol("(foo)").as_deref(), Err(_));
}
#[test]
fn parse_at_workspace_and_remote_symbol() {
// Parse "@" (the current working copy)