Restrict strings beginning with quote should also ending with quote (#13131)

# Description
Closes: #13010

It adds an additional check inside `parse_string`, and returns
`unbalanced quote` if input string is unbalanced

# User-Facing Changes
After this pr, the following is no longer allowed:
```nushell
❯ "asdfasdf"asdfasdf
Error: nu::parser::extra_token_after_closing_delimiter

  × Invaild characters after closing delimiter
   ╭─[entry #1:1:11]
 1 │ "asdfasdf"asdfasdf
   ·           ────┬───
   ·               ╰── invalid characters
   ╰────
  help: Try removing them.
❯ 'asdfasd'adsfadf
Error: nu::parser::extra_token_after_closing_delimiter

  × Invaild characters after closing delimiter
   ╭─[entry #2:1:10]
 1 │ 'asdfasd'adsfadf
   ·          ───┬───
   ·             ╰── invalid characters
   ╰────
  help: Try removing them.
```

# Tests + Formatting
Added 1 test
This commit is contained in:
Wind 2024-06-28 09:47:12 +08:00 committed by GitHub
parent 1f1f581357
commit 57452337ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 0 deletions

View file

@ -2828,6 +2828,36 @@ pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression
if bytes[0] != b'\'' && bytes[0] != b'"' && bytes[0] != b'`' && bytes.contains(&b'(') {
return parse_string_interpolation(working_set, span);
}
// Check for unbalanced quotes:
{
if bytes.starts_with(b"\"")
&& (bytes.iter().filter(|ch| **ch == b'"').count() > 1 && !bytes.ends_with(b"\""))
{
let close_delimiter_index = bytes
.iter()
.skip(1)
.position(|ch| *ch == b'"')
.expect("Already check input bytes contains at least two double quotes");
// needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
let span = Span::new(span.start + close_delimiter_index + 2, span.end);
working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
return garbage(working_set, span);
}
if bytes.starts_with(b"\'")
&& (bytes.iter().filter(|ch| **ch == b'\'').count() > 1 && !bytes.ends_with(b"\'"))
{
let close_delimiter_index = bytes
.iter()
.skip(1)
.position(|ch| *ch == b'\'')
.expect("Already check input bytes contains at least two double quotes");
// needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
let span = Span::new(span.start + close_delimiter_index + 2, span.end);
working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
return garbage(working_set, span);
}
}
let (s, err) = unescape_unquote_string(bytes, span);
if let Some(err) = err {

View file

@ -17,6 +17,13 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::extra_tokens), help("Try removing them."))]
ExtraTokens(#[label = "extra tokens"] Span),
#[error("Invalid characters after closing delimiter")]
#[diagnostic(
code(nu::parser::extra_token_after_closing_delimiter),
help("Try removing them.")
)]
ExtraTokensAfterClosingDelimiter(#[label = "invalid characters"] Span),
#[error("Extra positional argument.")]
#[diagnostic(code(nu::parser::extra_positional), help("Usage: {0}"))]
ExtraPositional(String, #[label = "extra positional argument"] Span),
@ -577,6 +584,7 @@ impl ParseError {
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
ParseError::RedirectingBuiltinCommand(_, s, _) => *s,
ParseError::UnexpectedSpreadArg(_, s) => *s,
ParseError::ExtraTokensAfterClosingDelimiter(s) => *s,
}
}
}

View file

@ -36,6 +36,12 @@ fn non_string_in_record() -> TestResult {
)
}
#[test]
fn unbalance_string() -> TestResult {
fail_test(r#""aaaab"cc"#, "invalid characters")?;
fail_test(r#"'aaaab'cc"#, "invalid characters")
}
#[test]
fn string_in_valuestream() -> TestResult {
run_test(