From f043a8a8fffd27c58f4fed92c747e9e4925cc420 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Wed, 25 Oct 2023 17:19:35 +0800 Subject: [PATCH] redirect should have a target (#10835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #10830 The issue happened during lite-parsing, when we want to put a `LiteElement` to a `LitePipeline`, we do nothing if relative redirection target is empty. So the command `echo aaa o> | ignore` will be interpreted to `echo aaa | ignore`. This pr is going to check and return an error if redirection target is empty. # User-Facing Changes ## Before ``` ❯ echo aaa o> | ignore # nothing happened ``` ## After ```nushell ❯ echo aaa o> | ignore Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #1:1:1] 1 │ echo aaa o> | ignore · ─┬ · ╰── expected redirection target ╰──── ``` --- .../nu-command/tests/commands/redirection.rs | 22 +++++++++ crates/nu-parser/src/lite_parser.rs | 47 ++++++++++++++----- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/tests/commands/redirection.rs b/crates/nu-command/tests/commands/redirection.rs index 1104262b1..8f121a2f1 100644 --- a/crates/nu-command/tests/commands/redirection.rs +++ b/crates/nu-command/tests/commands/redirection.rs @@ -264,3 +264,25 @@ fn separate_redirection_support_variable() { }, ) } + +#[test] +fn redirection_should_have_a_target() { + let scripts = [ + "echo asdf o+e>", + "echo asdf o>", + "echo asdf e>", + "echo asdf o> e>", + "echo asdf o> tmp.txt e>", + "echo asdf o> e> tmp.txt", + "echo asdf o> | ignore", + "echo asdf o>; echo asdf", + ]; + for code in scripts { + let actual = nu!(code); + assert!( + actual.err.contains("expected redirection target",), + "should be error, code: {}", + code + ); + } +} diff --git a/crates/nu-parser/src/lite_parser.rs b/crates/nu-parser/src/lite_parser.rs index 0bd7de587..1312951f8 100644 --- a/crates/nu-parser/src/lite_parser.rs +++ b/crates/nu-parser/src/lite_parser.rs @@ -235,12 +235,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { TokenContents::OutGreaterThan | TokenContents::ErrGreaterThan | TokenContents::OutErrGreaterThan => { - push_command_to( + if let Some(err) = push_command_to( &mut curr_pipeline, curr_command, last_connector, last_connector_span, - ); + ) { + error = Some(err); + } curr_command = LiteCommand::new(); last_token = token.contents; @@ -248,12 +250,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { last_connector_span = Some(token.span); } TokenContents::Pipe => { - push_command_to( + if let Some(err) = push_command_to( &mut curr_pipeline, curr_command, last_connector, last_connector_span, - ); + ) { + error = Some(err); + } curr_command = LiteCommand::new(); last_token = TokenContents::Pipe; @@ -269,12 +273,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { if actual_token != Some(TokenContents::Pipe) && actual_token != Some(TokenContents::OutGreaterThan) { - push_command_to( + if let Some(err) = push_command_to( &mut curr_pipeline, curr_command, last_connector, last_connector_span, - ); + ) { + error = Some(err); + } curr_command = LiteCommand::new(); if !curr_pipeline.is_empty() { @@ -294,12 +300,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { last_token = TokenContents::Eol; } TokenContents::Semicolon => { - push_command_to( + if let Some(err) = push_command_to( &mut curr_pipeline, curr_command, last_connector, last_connector_span, - ); + ) { + error = Some(err); + } curr_command = LiteCommand::new(); if !curr_pipeline.is_empty() { @@ -331,12 +339,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } } - push_command_to( + if let Some(err) = push_command_to( &mut curr_pipeline, curr_command, last_connector, last_connector_span, - ); + ) { + error = Some(err); + } if !curr_pipeline.is_empty() { block.push(curr_pipeline); } @@ -354,12 +364,16 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } } +/// push a `command` to `pipeline` +/// +/// It will return Some(err) if `command` is empty and we want to push a +/// redirection command. fn push_command_to( pipeline: &mut LitePipeline, command: LiteCommand, last_connector: TokenContents, last_connector_span: Option, -) { +) -> Option { if !command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { @@ -390,5 +404,16 @@ fn push_command_to( pipeline.push(LiteElement::Command(last_connector_span, command)); } } + None + } else { + match last_connector { + TokenContents::OutGreaterThan + | TokenContents::ErrGreaterThan + | TokenContents::OutErrGreaterThan => Some(ParseError::Expected( + "redirection target", + last_connector_span.expect("internal error: redirection missing span information"), + )), + _ => None, + } } }