redirect should have a target (#10835)

# 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
   ╰────
```
This commit is contained in:
WindSoilder 2023-10-25 17:19:35 +08:00 committed by GitHub
parent c6016d7659
commit f043a8a8ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 11 deletions

View File

@ -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
);
}
}

View File

@ -235,12 +235,14 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
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<ParseError>) {
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<ParseError>) {
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<ParseError>) {
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<ParseError>) {
}
}
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<ParseError>) {
}
}
/// 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<Span>,
) {
) -> Option<ParseError> {
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,
}
}
}