Add Argument::span() and Call::arguments_span() (#10983)

# Description

These make it easy to make a Span that covers an entire argument and the
span of all arguments in a Call.

Call::arguments_span() is useful for errors where a command may accept
arguments or the pipeline, but not both.

Argument::span() is useful for errors where an arguments is incompatible
with one or more other arguments.

In particular, I wish to use this to create an error for an
implementation of #9563 that either allows arguments to set limits:

```nushell
limits set RLIMIT_NOFILE --soft 255 --hard 1024
```

Or pipeline:

```nushell
{name: RLIMIT_NOFILE, soft: 255} | limits set
```

But not both:

```
❯ [{name: RLIMIT_NOFILE, soft: 255, hard: 1024}] | limits set AS --soft 5 --hard 5
Error: nu:🐚:incompatible_parameters

  × Incompatible parameters.
   ╭─[source:1:1]
 1 │ [{name: RLIMIT_NOFILE, soft: 255, hard: 1024}] | limits set AS --soft 5 --hard 5
   · ───────────────────────┬──────────────────────              ──────────┬─────────
   ·                        │                                              ╰── or arguments, not both
   ·                        ╰── Supply either pipeline
   ╰────
```

# User-Facing Changes

Only nushell Command API changes
This commit is contained in:
Eric Hodel 2023-11-19 12:43:56 -08:00 committed by GitHub
parent da59dfe7d2
commit c26fca7419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -12,6 +12,28 @@ pub enum Argument {
Unknown(Expression), // unknown argument used in "fall-through" signatures
}
impl Argument {
/// The span for an argument
pub fn span(&self) -> Span {
match self {
Argument::Positional(e) => e.span,
Argument::Named((named, short, expr)) => {
let start = named.span.start;
let end = if let Some(expr) = expr {
expr.span.end
} else if let Some(short) = short {
short.span.end
} else {
named.span.end
};
Span::new(start, end)
}
Argument::Unknown(e) => e.span,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Call {
/// identifier of the declaration to call
@ -36,6 +58,26 @@ impl Call {
}
}
/// The span encompassing the arguments
///
/// If there are no arguments the span covers where the first argument would exist
///
/// If there are one or more arguments the span encompasses the start of the first argument to
/// end of the last argument
pub fn arguments_span(&self) -> Span {
let past = self.head.past();
let start = self
.arguments
.first()
.map(|a| a.span())
.unwrap_or(past)
.start;
let end = self.arguments.last().map(|a| a.span()).unwrap_or(past).end;
Span::new(start, end)
}
pub fn named_iter(
&self,
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> {
@ -166,3 +208,64 @@ impl Call {
span
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn argument_span_named() {
let named = Spanned {
item: "named".to_string(),
span: Span::new(2, 3),
};
let short = Spanned {
item: "short".to_string(),
span: Span::new(5, 7),
};
let expr = Expression::garbage(Span::new(11, 13));
let arg = Argument::Named((named.clone(), None, None));
assert_eq!(Span::new(2, 3), arg.span());
let arg = Argument::Named((named.clone(), Some(short.clone()), None));
assert_eq!(Span::new(2, 7), arg.span());
let arg = Argument::Named((named.clone(), None, Some(expr.clone())));
assert_eq!(Span::new(2, 13), arg.span());
let arg = Argument::Named((named.clone(), Some(short.clone()), Some(expr.clone())));
assert_eq!(Span::new(2, 13), arg.span());
}
#[test]
fn argument_span_positional() {
let span = Span::new(2, 3);
let expr = Expression::garbage(span);
let arg = Argument::Positional(expr);
assert_eq!(span, arg.span());
}
#[test]
fn argument_span_unknown() {
let span = Span::new(2, 3);
let expr = Expression::garbage(span);
let arg = Argument::Unknown(expr);
assert_eq!(span, arg.span());
}
#[test]
fn call_arguments_span() {
let mut call = Call::new(Span::new(0, 1));
call.add_positional(Expression::garbage(Span::new(2, 3)));
call.add_positional(Expression::garbage(Span::new(5, 7)));
assert_eq!(Span::new(2, 7), call.arguments_span());
}
}