From df0a1748026be33244f770c38daed9c6b0b09d54 Mon Sep 17 00:00:00 2001 From: Solomon Date: Fri, 11 Oct 2024 12:53:39 +0000 Subject: [PATCH] fix unknown_command when parsing certain strings with equal signs (#14053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Prevents errors when `=` is used before the end of: - strings in lists/records (with a symbol adjacent to the quotes) - raw strings ``` > ["=a"] Error: nu::parser::unknown_command × Unknown command. ╭─[entry #9:1:1] 1 │ ["=a"] · ───┬── · ╰── unknown command ╰──── ``` ``` > r#'=a'# Error: nu::parser::unknown_command × Unknown command. ╭─[entry #5:1:1] 1 │ r#'=a'# · ───┬─── · ╰── unknown command ╰──── ``` Closes #13902, closes #13901, closes #9879, closes #6401, closes #5806 # User-Facing Changes Variable names in environment shorthand assignments must satisfy `is_identifier`. --- crates/nu-parser/src/parser.rs | 14 +++++++++---- tests/repl/test_parser.rs | 37 +++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5a35c95e72..232fc50b3d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2943,6 +2943,10 @@ pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Exp working_set.error(ParseError::Unclosed("\'".into(), span)); return garbage(working_set, span); } + if bytes.starts_with(b"r#") && (bytes.len() == 1 || !bytes.ends_with(b"#")) { + working_set.error(ParseError::Unclosed("r#".into(), span)); + return garbage(working_set, span); + } } let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) @@ -5396,10 +5400,12 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex let starting_error_count = working_set.parse_errors.len(); - let lhs = parse_string_strict( - working_set, - Span::new(spans[pos].start, spans[pos].start + point - 1), - ); + let lhs_span = Span::new(spans[pos].start, spans[pos].start + point - 1); + if !is_identifier(working_set.get_span_contents(lhs_span)) { + break; + } + + let lhs = parse_string_strict(working_set, lhs_span); let rhs = if spans[pos].start + point < spans[pos].end { let rhs_span = Span::new(spans[pos].start + point, spans[pos].end); diff --git a/tests/repl/test_parser.rs b/tests/repl/test_parser.rs index 8ccc15080a..f77d431110 100644 --- a/tests/repl/test_parser.rs +++ b/tests/repl/test_parser.rs @@ -176,7 +176,14 @@ fn bad_var_name() -> TestResult { #[test] fn bad_var_name2() -> TestResult { - fail_test(r#"let $foo-bar = 4"#, "valid variable") + fail_test(r#"let $foo-bar = 4"#, "valid variable")?; + fail_test(r#"foo-bar=4 true"#, "Command `foo-bar=4` not found") +} + +#[test] +fn bad_var_name3() -> TestResult { + fail_test(r#"let $=foo = 4"#, "valid variable")?; + fail_test(r#"=foo=4 true"#, "Command `=foo=4` not found") } #[test] @@ -267,6 +274,34 @@ fn string_interp_with_equals() -> TestResult { ) } +#[test] +fn raw_string_with_equals() -> TestResult { + run_test( + r#"let query_prefix = r#'https://api.github.com/search/issues?q=repo:nushell/'#; $query_prefix"#, + "https://api.github.com/search/issues?q=repo:nushell/", + ) +} + +#[test] +fn list_quotes_with_equals() -> TestResult { + run_test( + r#"["https://api.github.com/search/issues?q=repo:nushell/"] | get 0"#, + "https://api.github.com/search/issues?q=repo:nushell/", + ) +} + +#[test] +fn record_quotes_with_equals() -> TestResult { + run_test(r#"{"a=":b} | get a="#, "b")?; + run_test(r#"{"=a":b} | get =a"#, "b")?; + + run_test(r#"{a:"=b"} | get a"#, "=b")?; + run_test(r#"{a:"b="} | get a"#, "b=")?; + + run_test(r#"{a:b,"=c":d} | get =c"#, "d")?; + run_test(r#"{a:b,"c=":d} | get c="#, "d") +} + #[test] fn recursive_parse() -> TestResult { run_test(r#"def c [] { c }; echo done"#, "done")