mirror of
https://github.com/nushell/nushell.git
synced 2025-05-19 17:30:45 +02:00
fix parsing of bare word string interpolations that start with a sub expression (#15735)
- fixes #15731 # Description Existing bare word string interpolation only works if the string doesn't start with a subxpression. ```nushell echo fork(2) # => fork2 echo (2)fork # => Error: nu::parser::unclosed_delimiter # => # => × Unclosed delimiter. # => ╭─[entry #25:1:13] # => 1 │ echo (2)fork # => ╰──── ``` This PR lifts that restriction. ```nushell echo fork(2) # => fork2 echo (2)fork # => 2fork ``` This was first brought to my attention on discord with the following command failing to parse. ```nushell docker run -u (id -u):(id -g) ``` It now works. # User-Facing Changes # Tests + Formatting No existing test broke or required tweaking. Additional tests covering this case was added. - 🟢 toolkit fmt - 🟢 toolkit clippy - 🟢 toolkit test - 🟢 toolkit test stdlib # After Submitting --------- Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
parent
e0eb29f161
commit
36c30ade3a
@ -455,8 +455,10 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External
|
|||||||
fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||||
let contents = working_set.get_span_contents(span);
|
let contents = working_set.get_span_contents(span);
|
||||||
|
|
||||||
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
if contents.starts_with(b"$") {
|
||||||
parse_dollar_expr(working_set, span)
|
parse_dollar_expr(working_set, span)
|
||||||
|
} else if contents.starts_with(b"(") {
|
||||||
|
parse_paren_expr(working_set, span, &SyntaxShape::Any)
|
||||||
} else if contents.starts_with(b"[") {
|
} else if contents.starts_with(b"[") {
|
||||||
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
||||||
} else {
|
} else {
|
||||||
@ -1977,15 +1979,33 @@ pub fn parse_paren_expr(
|
|||||||
let starting_error_count = working_set.parse_errors.len();
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
|
|
||||||
if let Some(expr) = parse_range(working_set, span) {
|
if let Some(expr) = parse_range(working_set, span) {
|
||||||
expr
|
return expr;
|
||||||
} else {
|
}
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
|
||||||
|
|
||||||
if matches!(shape, SyntaxShape::Signature) {
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
parse_signature(working_set, span)
|
|
||||||
|
if matches!(shape, SyntaxShape::Signature) {
|
||||||
|
return parse_signature(working_set, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fcp_expr = parse_full_cell_path(working_set, None, span);
|
||||||
|
let fcp_error_count = working_set.parse_errors.len();
|
||||||
|
if fcp_error_count > starting_error_count {
|
||||||
|
let malformed_subexpr = working_set.parse_errors[starting_error_count..]
|
||||||
|
.iter()
|
||||||
|
.any(|e| match e {
|
||||||
|
ParseError::Unclosed(right, _) if right == ")" => true,
|
||||||
|
ParseError::Unbalanced(left, right, _) if left == "(" && right == ")" => true,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
if malformed_subexpr {
|
||||||
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
|
parse_string(working_set, span)
|
||||||
} else {
|
} else {
|
||||||
parse_full_cell_path(working_set, None, span)
|
fcp_expr
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fcp_expr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1576,6 +1576,78 @@ mod string {
|
|||||||
assert!(matches!(subexprs[3], &Expr::FullCellPath(..)));
|
assert!(matches!(subexprs[3], &Expr::FullCellPath(..)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn parse_string_interpolation_bare_starting_subexpr() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let block = parse(
|
||||||
|
&mut working_set,
|
||||||
|
None,
|
||||||
|
b"\"\" ++ (1 + 3)foo(7 - 5)bar",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(working_set.parse_errors.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(block.len(), 1);
|
||||||
|
let pipeline = &block.pipelines[0];
|
||||||
|
assert_eq!(pipeline.len(), 1);
|
||||||
|
let element = &pipeline.elements[0];
|
||||||
|
assert!(element.redirection.is_none());
|
||||||
|
|
||||||
|
let subexprs: Vec<&Expr> = match &element.expr.expr {
|
||||||
|
Expr::BinaryOp(_, _, rhs) => match &rhs.expr {
|
||||||
|
Expr::StringInterpolation(expressions) => {
|
||||||
|
expressions.iter().map(|e| &e.expr).collect()
|
||||||
|
}
|
||||||
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
||||||
|
},
|
||||||
|
_ => panic!("Expected an `Expr::BinaryOp`"),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(subexprs.len(), 4);
|
||||||
|
|
||||||
|
assert!(matches!(subexprs[0], &Expr::FullCellPath(..)));
|
||||||
|
assert_eq!(subexprs[1], &Expr::String("foo".to_string()));
|
||||||
|
assert!(matches!(subexprs[2], &Expr::FullCellPath(..)));
|
||||||
|
assert_eq!(subexprs[3], &Expr::String("bar".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn parse_string_interpolation_bare_starting_subexpr_external_arg() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let block = parse(&mut working_set, None, b"^echo ($nu.home-path)/path", true);
|
||||||
|
|
||||||
|
assert!(working_set.parse_errors.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(block.len(), 1);
|
||||||
|
let pipeline = &block.pipelines[0];
|
||||||
|
assert_eq!(pipeline.len(), 1);
|
||||||
|
let element = &pipeline.elements[0];
|
||||||
|
assert!(element.redirection.is_none());
|
||||||
|
|
||||||
|
let subexprs: Vec<&Expr> = match &element.expr.expr {
|
||||||
|
Expr::ExternalCall(_, args) => match &args[0] {
|
||||||
|
ExternalArgument::Regular(expression) => match &expression.expr {
|
||||||
|
Expr::StringInterpolation(expressions) => {
|
||||||
|
expressions.iter().map(|e| &e.expr).collect()
|
||||||
|
}
|
||||||
|
_ => panic!("Expected an `ExternalArgument::Regular`"),
|
||||||
|
},
|
||||||
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
||||||
|
},
|
||||||
|
_ => panic!("Expected an `Expr::BinaryOp`"),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(subexprs.len(), 2);
|
||||||
|
|
||||||
|
assert!(matches!(subexprs[0], &Expr::FullCellPath(..)));
|
||||||
|
assert_eq!(subexprs[1], &Expr::String("/path".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn parse_nested_expressions() {
|
pub fn parse_nested_expressions() {
|
||||||
let engine_state = EngineState::new();
|
let engine_state = EngineState::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user