diff --git a/lib/src/revset.pest b/lib/src/revset.pest index e6af7b2c9..a3a74c9b0 100644 --- a/lib/src/revset.pest +++ b/lib/src/revset.pest @@ -26,6 +26,7 @@ whitespace = _{ " " } parents_op = { "-" } children_op = { "+" } +compat_parents_op = { "^" } dag_range_op = { ":" } dag_range_pre_op = { ":" } @@ -61,7 +62,7 @@ primary = { | symbol } -neighbors_expression = _{ primary ~ (parents_op | children_op)* } +neighbors_expression = _{ primary ~ (parents_op | children_op | compat_parents_op)* } range_expression = _{ neighbors_expression ~ range_ops ~ neighbors_expression diff --git a/lib/src/revset.rs b/lib/src/revset.rs index fc5426e93..d275d6c10 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -215,6 +215,12 @@ pub struct RevsetParseError { pub enum RevsetParseErrorKind { #[error("Syntax error")] SyntaxError, + #[error("'{op}' is not a postfix operator (Did you mean '{similar_op}' for {description}?)")] + NotPostfixOperator { + op: String, + similar_op: String, + description: String, + }, #[error("'{op}' is not an infix operator (Did you mean '{similar_op}' for {description}?)")] NotInfixOperator { op: String, @@ -701,6 +707,21 @@ fn parse_expression_rule( pairs: Pairs, state: ParseState, ) -> Result, RevsetParseError> { + fn not_postfix_op( + op: &Pair, + similar_op: impl Into, + description: impl Into, + ) -> RevsetParseError { + RevsetParseError::with_span( + RevsetParseErrorKind::NotPostfixOperator { + op: op.as_str().to_owned(), + similar_op: similar_op.into(), + description: description.into(), + }, + op.as_span(), + ) + } + fn not_infix_op( op: &Pair, similar_op: impl Into, @@ -729,7 +750,9 @@ fn parse_expression_rule( .op(Op::prefix(Rule::dag_range_pre_op) | Op::prefix(Rule::range_pre_op)) .op(Op::postfix(Rule::dag_range_post_op) | Op::postfix(Rule::range_post_op)) // Neighbors - .op(Op::postfix(Rule::parents_op) | Op::postfix(Rule::children_op)) + .op(Op::postfix(Rule::parents_op) + | Op::postfix(Rule::children_op) + | Op::postfix(Rule::compat_parents_op)) }); PRATT .map_primary(|primary| parse_primary_rule(primary, state)) @@ -743,6 +766,7 @@ fn parse_expression_rule( Rule::range_post_op => Ok(lhs?.range(&RevsetExpression::visible_heads())), Rule::parents_op => Ok(lhs?.parents()), Rule::children_op => Ok(lhs?.children()), + Rule::compat_parents_op => Err(not_postfix_op(&op, "-", "parents")), r => panic!("unexpected postfix operator rule {r:?}"), }) .map_infix(|lhs, op, rhs| match op.as_rule() { @@ -2315,6 +2339,14 @@ mod tests { #[test] fn test_parse_revset_compat_operator() { + assert_eq!( + parse("foo^"), + Err(RevsetParseErrorKind::NotPostfixOperator { + op: "^".to_owned(), + similar_op: "-".to_owned(), + description: "parents".to_owned(), + }) + ); assert_eq!( parse("foo + bar"), Err(RevsetParseErrorKind::NotInfixOperator { diff --git a/tests/test_revset_output.rs b/tests/test_revset_output.rs index 40b62ea1a..95d193c9d 100644 --- a/tests/test_revset_output.rs +++ b/tests/test_revset_output.rs @@ -41,6 +41,16 @@ fn test_syntax_error() { | = '-' is not an infix operator (Did you mean '~' for difference?) "###); + + let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "HEAD^"]); + insta::assert_snapshot!(stderr, @r###" + Error: Failed to parse revset: --> 1:5 + | + 1 | HEAD^ + | ^ + | + = '^' is not a postfix operator (Did you mean '-' for parents?) + "###); } #[test]