fix(parse): enforce double_dash="required" for positional args - #762
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughThe parser now routes required separator arguments after ChangesRequired separator argument support
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant CLI
participant Parser
participant ParseOutput
participant PositionalCompletion
CLI->>Parser: parse partial command line
Parser->>ParseOutput: record next_arg and double_dash_seen
ParseOutput-->>CLI: return completion state
CLI->>PositionalCompletion: complete current positional token
PositionalCompletion-->>CLI: offer `--` or positional values
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR enforces
Confidence Score: 5/5The PR appears safe to merge, with no eligible blocking failure remaining. No blocking failure remains. Important Files Changed
Reviews (3): Last reviewed commit: "fix(parse)!: enforce double_dash="requir..." | Re-trigger Greptile |
7d82ea0 to
bf9bb63
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/src/parse.rs (1)
749-757: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider documenting the no-rewind case for an earlier required arg.
The search finds the first unfilled
double_dash="required"argument anywhere inout.cmd.args, but the jump applies only whentarget > next_arg_idx. If such an argument is declared before the current cursor and the cursor already moved past it, the--does not route to it. The argument is then reported asMissingArg.This shape is unusual, because
double_dash="required"mirrors clapArg::last(true), which is the final positional. The behavior is safe, but the reason for the>guard is not stated in the comment. Add a sentence that records this decision.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/parse.rs` around lines 749 - 757, Add a concise comment beside the `target > next_arg_idx` guard explaining that required double-dash arguments declared before the current cursor are intentionally not revisited, preserving forward-only routing and allowing them to remain reported as `MissingArg`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/src/cli/complete_word.rs`:
- Around line 146-158: Centralize positional completion in a helper that applies
the `parsed.next_arg` and `parsed.double_dash_seen` rules, then use it from the
flag branch, `after_restart_token` path, and default-subcommand fallback instead
of calling `complete_arg` directly. When `double_dash_seen` is true, do not
classify dash-prefixed tokens as flags; when the next argument requires a
separator and it has not been seen, offer only `--` for an empty token and
suppress value/file completion. Add regressions covering dash-prefixed values
after `--`, `restart_token`, and `default_subcommand`.
---
Nitpick comments:
In `@lib/src/parse.rs`:
- Around line 749-757: Add a concise comment beside the `target > next_arg_idx`
guard explaining that required double-dash arguments declared before the current
cursor are intentionally not revisited, preserving forward-only routing and
allowing them to remain reported as `MissingArg`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: e7144fcd-3818-4ed2-9de2-0952e680467a
⛔ Files ignored due to path filters (1)
mise.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
cli/src/cli/complete_word.rscli/tests/complete_word.rsdocs/spec/reference/arg.mdexamples/double-dash.usage.kdllib/src/error.rslib/src/lib.rslib/src/parse.rslib/tests/parse.rs
bf9bb63 to
f4f00c7
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/src/cli/complete_word.rs`:
- Around line 178-186: Preserve and propagate the constrained result returned by
complete_positional in this branch instead of discarding it. Update the
surrounding completion flow so a constrained default-subcommand argument that
matches no choices after -- remains constrained, preventing the fallback at the
later choices handling from offering file paths.
- Around line 122-124: Update the positional-token completion logic near
complete_path so dash-prefixed tokens after parsed.double_dash_seen still use
file completion when no explicit choices are configured. Keep flag completion
disabled after -- while removing the unconditional ctoken.starts_with('-')
exclusion from the positional file fallback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 6829058e-75bf-4b83-b091-c19a9cbcf3ab
📒 Files selected for processing (9)
cli/src/cli/complete_word.rscli/tests/complete_word.rsdocs/spec/reference/arg.mdexamples/double-dash-default-subcommand.usage.kdlexamples/double-dash.usage.kdllib/src/error.rslib/src/lib.rslib/src/parse.rslib/tests/parse.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- lib/src/lib.rs
- lib/tests/parse.rs
- lib/src/error.rs
- docs/spec/reference/arg.md
- lib/src/parse.rs
c5f8b7d to
22273ee
Compare
|
This PR currently has failing checks. If this continues for 7 days, it will be closed automatically. This is warning day 1 of 7. Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it. This comment was generated by an automated workflow. |
a1c1383 to
ce82199
Compare
The parser never looked at `SpecDoubleDashChoices::Required`. Seeing a `--`
only disabled flag parsing, so an arg declared
arg "[file]" double_dash="required"
was filled whether or not the separator was given, and an arg sitting behind a
greedy variadic was never filled at all — even after a `--`. Reported against
mise in jdx/mise#5759, where `mise run echo test` and `mise run echo -- test`
both succeed.
Both halves of clap's `Arg::last(true)` are now implemented, which is what
`double_dash="required"` is generated from:
- A word offered to such an arg before an explicit `--` is rejected with
`ArgRequiresDoubleDash` and not assigned. A variadic arg reports this once
rather than once per word, and an arg that is both `required` and
`double_dash="required"` no longer also collects `MissingArg`.
- An explicit `--` moves the positional cursor onto the arg that requires it,
past earlier args including a greedy variadic.
Routing can leave an earlier arg empty, so "next arg" and "how many args are
filled" no longer coincide. The cursor becomes an explicit index and the two
loops that used `skip(out.args.len())` to find unfilled args — the `MissingArg`
check and the default/env application in `Parser::parse` — now ask `out.args`
by key instead of counting.
A `--` that `double_dash="preserve"` keeps as a value stays a value: it does not
count as a separator and does not unlock a required arg. `restart_token` resets
the separator, so each invocation after it needs its own `--`.
`ParseOutput` gains `next_arg` and `double_dash_seen` so completions follow the
same cursor as the parser. Every path that completes a positional — the one at
the cursor, the first arg after a `restart_token`, and a default subcommand's —
goes through one helper, so the rule cannot be honoured in one and forgotten in
another. Past a `--` the completion also stops offering flags, since the parser
reads everything after it as a positional value.
BREAKING CHANGE: specs that declare `double_dash="required"` now reject values
given before the separator instead of silently accepting them, so a CLI relying
on the old behaviour will start reporting an error. Two source-level breaks come
with it, both flagged by `cargo semver-checks`: `UsageErr` gains an
`ArgRequiresDoubleDash` variant, and `ParseOutput` gains the `next_arg` and
`double_dash_seen` fields.
ce82199 to
6f2edb1
Compare
|
CI is in. Everything passes except Those are exactly the two breaks declared in the commit's Why each break is needed
Why I am not bumping the version hereI did try that, then backed it out. if ! echo "$released_versions" | grep -q "^v$cur_version$"; then
cargo publish -p usage-lib
...
git tag "v$cur_version"which is the publish half of the release-PR flow — Marking the commit Two things for your call
This comment was generated by Claude Code. |
|
Heads-up on the version, and this one is my mistake. The squash commit came out as The So
Simplest fix is to set the version to 5.0.0 on the release PR when it appears. Happy to open a follow-up PR if you would rather have it handled here — just say which. This comment was generated by Claude Code. |
Fixes the root cause behind jdx/mise#5759.
Problem
The parser never looked at
SpecDoubleDashChoices::Required. Seeing a--only setenable_flags = false; nothing consulteddouble_dashwhen assigning positionals. So forboth of these succeeded:
The reverse half was missing too: an arg requiring
--that sits behind a greedy variadic was never reachable, even with a separator. Inexamples/mise.usage.kdlthat is[-- TASK_ARGS_LAST]...,[-- ARGS_LAST]...andexec's[-- COMMAND]...— all declared, none ever filled.Fix
double_dash="required"is generated from clap'sArg::last(true)(lib/src/spec/arg.rs,is_last_set() → Required), so both halves of that semantic are now implemented:--is reported asArgRequiresDoubleDashand not assigned. A variadic arg reports this once rather than once per word, and an arg that is bothrequiredanddouble_dash="required"no longer also collectsMissingArg. The check runs ahead ofvalidate_choicesso a discarded word is not additionally reported as an invalid choice.--moves the positional cursor onto the arg that requires it, past earlier args including a greedy variadic.Routing can leave an earlier arg empty, so "next arg" and "how many args are filled" no longer coincide. The cursor became an explicit index, and the two loops that used
skip(out.args.len())to find unfilled args — theMissingArgcheck and the default/env application inParser::parse— now askout.argsby key instead of counting. Without that, a skipped arg would silently lose its default and its missing-arg diagnostic.Interactions, both covered by tests:
--thatdouble_dash="preserve"keeps as a value stays a value. It does not count as a separator and does not unlock a required arg — one token cannot be both.restart_tokenresets the separator, so each invocation after it needs its own--.Specs with no
double_dash="required"arg are bit-for-bit unaffected: the cursor jump looks for such an arg and finds none.Completions
ParseOutputgainsnext_arg(where the cursor stopped) anddouble_dash_seen.complete-word's localnext_arg_for_completionre-derived the next arg fromargsand knew nothing about--, so with routing in place it would have disagreed with the parser; it now reads the parser's own cursor. While an arg is still locked behind a separator, the completion offers--itself rather than values the parser would reject.Versioning —
lint:semveris red on purposeThis is a breaking change and
cargo semver-checks check-release -p usage-libsays so. Two source-level breaks, both intentional:UsageErrgains anArgRequiresDoubleDashvariant (enum_variant_added)ParseOutputgainsnext_arganddouble_dash_seen(constructible_struct_adds_field); the only construction site is insideparse.rsI briefly cleared the check by bumping the workspace to 5.0.0 in this PR, then backed that out — it would have caused an unplanned release.
tasks/release-plzopens withwhich is the publish half of the release-PR flow:
releasebumps the version, it merges to main, main is briefly a version with no tag, and this branch publishes it. A hand-written bump in a feature PR is indistinguishable from that, so merging one would publish to crates.io on the merge push — with no CHANGELOG entry, no release PR, and outside the 7-day cadence guard inauto-merge-release.yml.So the commit is marked
fix(parse)!:with aBREAKING CHANGE:footer instead. Withbreaking_always_bump_major = trueincliff.toml, git-cliff computes the major bump at release time and the version lands through the normal release PR.lint:semverstays red until then, which seemed like the honest state — it is reporting a real breaking change rather than a mistake.Happy to add
#[non_exhaustive]toParseOutputin the same commit if you would rather close that off now, or to reshape the marker if you want the release to go differently.Behavior changes
Measured against
examples/mise.usage.kdl:tasks add <TASK>+[-- RUN]...<TASK>(non-var)mise tasks add t echo hiis now an error — clap already rejects it, so this closes a divergence[-- TASK_ARGS_LAST]...[TASK_ARGS]...(greedy)--values move fromTASK_ARGStoTASK_ARGS_LASTtasks run [-- ARGS_LAST]...[ARGS]...(greedy)--values move fromARGStoARGS_LASTexec [-- COMMAND]...[TOOL@VERSION]...(greedy)--values move fromTOOL@VERSIONtoCOMMANDThe last three are the args finally doing what they were declared to do, but they change which
usage_*variable a consumer reads. @jdx — worth confirming this is what mise expects before release, since mise is the main consumer of these specs. Spec authors who want the old permissiveness can drop back todouble_dash="optional"(the default).double_dash="automatic"remains unimplemented in the parser; this PR does not change that, anddocs/spec/reference/arg.mdnow says so explicitly.Tests
33 new tests, no existing test modified:
lib/tests/parse.rs— 17 end-to-end cases over the three spellings (double_dash="required",arg "[-- x]",arg "<-- x>"), covering rejection, routing past a greedy variadic, single-error guarantees, choices non-interference, andoptionalstaying untouched.lib/src/parse.rs— 10 unit tests assertingParseOutputinternals: error counts and kinds, the gap left by routing (defaults andMissingArgstill correct),restart_tokenin both directions,preservenon-interference, andparse_partialstayingOkwhereparseerrors.cli/tests/complete_word.rs— 6 tests on two new choices-only fixtures,examples/double-dash.usage.kdlandexamples/double-dash-default-subcommand.usage.kdl. Choices rather than acompleterunner, so they resolve in-process and run on every platform.cargo test --all --all-featuresis green on Linux across the workspace. Three failures on my machine are a local gap rather than this change:test_usage_double_slash_execution{,_old}andtest_typescript_sdk_typechecksneednode, which is not installed there.This pull request was generated by Claude Code.
Summary by CodeRabbit
New Features
--separator, including shorthand syntax.--are routed correctly with variadic arguments, restart tokens, and default subcommands.Bug Fixes
--, suppress flags afterward, and support dash-prefixed positional values.Documentation