Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions libs/cli_parser/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ fn parse_args(
let mut i = 0;
let mut positional_index = 0;
let mut trailing_mode = false;
let mut positional_only = false;
let mut positional_started = false;
let mut found_subcommand = skip_subcommand.is_none();
let mut passthrough_from: Option<usize> = None;
// Per-positional trailing: when set, ALL remaining args (including flags)
Expand Down Expand Up @@ -161,6 +163,42 @@ fn parse_args(
continue;
}

// After `--` before the first positional, consume positional values
// without interpreting leading hyphens as flags. Once the positional
// values are complete, the command's trailing arguments receive the rest.
if positional_only {
if let Some(pos_def) = positional_defs.get(positional_index) {
positional_started = true;
apply_value_with_delimiter(result, pos_def, arg)?;
i += 1;

match pos_def.num_args {
NumArgs::ZeroOrMore | NumArgs::OneOrMore => {}
_ => positional_index += 1,
}

if cmd_def.trailing_var_arg && positional_index >= positional_defs.len()
{
while i < args.len() {
result.trailing.push(args[i].clone());
i += 1;
}
}
continue;
}

if cmd_def.trailing_var_arg {
result.trailing.push(arg.clone());
i += 1;
continue;
}

return Err(CliError::new(
CliErrorKind::UnexpectedPositional,
format!("unexpected argument '{arg}'"),
));
}

// After `--`, everything is trailing
if trailing_mode {
result.trailing.push(arg.clone());
Expand All @@ -169,6 +207,12 @@ fn parse_args(
}

if arg == "--" {
if !positional_started && !positional_defs.is_empty() {
positional_only = true;
i += 1;
continue;
}

// Check if the next positional has `trailing: true` - if so,
// args after `--` go into that positional, not result.trailing.
if let Some(next_pos) = positional_defs.get(positional_index)
Expand Down Expand Up @@ -213,6 +257,7 @@ fn parse_args(
} else {
// Positional argument
if let Some(pos_def) = positional_defs.get(positional_index) {
positional_started = true;
apply_value_with_delimiter(result, pos_def, arg)?;

// If this positional has trailing: true, absorb everything
Expand Down
38 changes: 38 additions & 0 deletions libs/cli_parser/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,14 @@ fn run_double_dash_trailing() {
assert_eq!(r.trailing, vec!["--", "arg1", "--flag"]);
}

#[test]
fn run_double_dash_before_script() {
let r =
parse(&TEST_ROOT, &svec!["deno", "run", "--", "-echo.ts", "arg1"]).unwrap();
assert_eq!(r.get_one("script_arg"), Some("-echo.ts"));
assert_eq!(r.trailing, vec!["arg1"]);
}

#[test]
fn global_flags_before_subcommand() {
let r = parse(
Expand Down Expand Up @@ -1195,6 +1203,24 @@ fn eval_print() {
assert_eq!(r.get_one("code_arg"), Some("1+1"));
}

#[test]
fn eval_double_dash_before_code() {
let r = parse(
&TEST_ROOT,
&svec!["deno", "eval", "--", "-1; console.log(0)", "arg1"],
)
.unwrap();
assert_eq!(r.get_one("code_arg"), Some("-1; console.log(0)"));
assert_eq!(r.trailing, vec!["arg1"]);
}

#[test]
fn upgrade_double_dash_rejects_extra_positional() {
let err = parse(&TEST_ROOT, &svec!["deno", "upgrade", "--", "v1", "v2"])
.unwrap_err();
assert!(matches!(err.kind, CliErrorKind::UnexpectedPositional));
}

#[test]
fn unknown_flag_error() {
let err = parse(
Expand Down Expand Up @@ -1242,6 +1268,18 @@ fn default_subcommand_with_flags() {
assert_eq!(r.get_one("script_arg"), Some("script.ts"));
}

#[test]
fn default_subcommand_double_dash_before_script() {
let r = parse(
&TEST_ROOT,
&svec!["deno", "--", "-echo.ts", "--", "--debug"],
)
.unwrap();
assert_eq!(r.subcommand.as_deref(), None);
assert_eq!(r.get_one("script_arg"), Some("-echo.ts"));
assert_eq!(r.trailing, vec!["--", "--debug"]);
}

#[test]
fn default_subcommand_with_allow_read_values() {
let r = parse(&TEST_ROOT, &svec!["deno", "--allow-read=/tmp", "script.ts"])
Expand Down