From 155934f783e15c6ffb3a42119105799a625f7bbc Mon Sep 17 00:00:00 2001 From: Wind Date: Wed, 15 May 2024 09:14:11 +0800 Subject: [PATCH] make better messages for incomplete string (#12868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #12795 The issue is caused by an empty position of `ParseError::UnexpectedEof`. So no detailed message is displayed. To fix the issue, I adjust the start of span to `span.end - 1`. In this way, we can make sure that it never points to an empty position. After lexing item, I also reorder the unclosed character checking . Now it will be checking unclosed opening delimiters first. # User-Facing Changes After this pr, it outputs detailed error message for incomplete string when running scripts. ## Before ``` ❯ nu -c "'ab" Error: nu::parser::unexpected_eof × Unexpected end of code. ╭─[source:1:4] 1 │ 'ab ╰──── > ./target/debug/nu -c "r#'ab" Error: nu::parser::unexpected_eof × Unexpected end of code. ╭─[source:1:6] 1 │ r#'ab ╰──── ``` ## After ``` > nu -c "'ab" Error: nu::parser::unexpected_eof × Unexpected end of code. ╭─[source:1:3] 1 │ 'ab · ┬ · ╰── expected closing ' ╰──── > ./target/debug/nu -c "r#'ab" Error: nu::parser::unexpected_eof × Unexpected end of code. ╭─[source:1:5] 1 │ r#'ab · ┬ · ╰── expected closing '# ╰──── ``` # Tests + Formatting Added some tests for incomplete string. --------- Co-authored-by: Ian Manske --- crates/nu-parser/src/lex.rs | 42 ++++++++++++++++++++----------------- tests/repl/test_strings.rs | 16 ++++++++++++-- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index f9e75d13f0..4639cae25d 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -129,7 +129,7 @@ pub fn lex_item( }, Some(ParseError::UnexpectedEof( (start as char).to_string(), - Span::new(span.end, span.end), + Span::new(span.end - 1, span.end), )), ); } @@ -247,21 +247,6 @@ pub fn lex_item( let span = Span::new(span_offset + token_start, span_offset + *curr_offset); - // If there is still unclosed opening delimiters, remember they were missing - if let Some(block) = block_level.last() { - let delim = block.closing(); - let cause = - ParseError::UnexpectedEof((delim as char).to_string(), Span::new(span.end, span.end)); - - return ( - Token { - contents: TokenContents::Item, - span, - }, - Some(cause), - ); - } - if let Some(delim) = quote_start { // The non-lite parse trims quotes on both sides, so we add the expected quote so that // anyone wanting to consume this partial parse (e.g., completions) will be able to get @@ -273,11 +258,28 @@ pub fn lex_item( }, Some(ParseError::UnexpectedEof( (delim as char).to_string(), - Span::new(span.end, span.end), + Span::new(span.end - 1, span.end), )), ); } + // If there is still unclosed opening delimiters, remember they were missing + if let Some(block) = block_level.last() { + let delim = block.closing(); + let cause = ParseError::UnexpectedEof( + (delim as char).to_string(), + Span::new(span.end - 1, span.end), + ); + + return ( + Token { + contents: TokenContents::Item, + span, + }, + Some(cause), + ); + } + // If we didn't accumulate any characters, it's an unexpected error. if *curr_offset - token_start == 0 { return ( @@ -395,9 +397,11 @@ fn lex_raw_string( *curr_offset += 1 } if !matches { + let mut expected = '\''.to_string(); + expected.push_str(&"#".repeat(prefix_sharp_cnt)); return Err(ParseError::UnexpectedEof( - "#".to_string(), - Span::new(span_offset + *curr_offset, span_offset + *curr_offset), + expected, + Span::new(span_offset + *curr_offset - 1, span_offset + *curr_offset), )); } Ok(()) diff --git a/tests/repl/test_strings.rs b/tests/repl/test_strings.rs index cafd6cc681..fa6c419e23 100644 --- a/tests/repl/test_strings.rs +++ b/tests/repl/test_strings.rs @@ -154,6 +154,18 @@ fn raw_string_inside_closure() -> TestResult { } #[test] -fn incomplete_raw_string() -> TestResult { - fail_test("r#abc", "expected '") +fn incomplete_string() -> TestResult { + fail_test("r#abc", "expected '")?; + fail_test("r#'bc", "expected closing '#")?; + fail_test("'ab\"", "expected closing '")?; + fail_test("\"ab'", "expected closing \"")?; + fail_test( + r#"def func [] { + { + "A": ""B" # <- the quote is bad + } +} +"#, + "expected closing \"", + ) }