From c26fca74194ec9275542a7885f3493d8e8228e03 Mon Sep 17 00:00:00 2001 From: Eric Hodel Date: Sun, 19 Nov 2023 12:43:56 -0800 Subject: [PATCH] Add Argument::span() and Call::arguments_span() (#10983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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::shell::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 --- crates/nu-protocol/src/ast/call.rs | 103 +++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index de51617ba..31b0235f8 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -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, Option>, Option)> { @@ -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()); + } +}