Skip to content

fix(parser): handle short alias with = syntax (-n=value)#240

Open
guoyangzhen wants to merge 1 commit intounjs:mainfrom
guoyangzhen:main
Open

fix(parser): handle short alias with = syntax (-n=value)#240
guoyangzhen wants to merge 1 commit intounjs:mainfrom
guoyangzhen:main

Conversation

@guoyangzhen
Copy link
Copy Markdown

@guoyangzhen guoyangzhen commented Mar 16, 2026

Resolves #237

Problem

-n=John with short alias n for name returns { n: "=John", name: "=John" } instead of { n: "John", name: "John" }.

Root cause: node:util parseArgs does not handle -<char>=<value> syntax for short aliases. It treats -n=John as -n with value =John.

Fix

Two-part fix in src/_parser.ts:

  1. Filter single-char aliases from allOptions — aliases like n should only be registered via the short property of their target option, not as standalone options. Otherwise parseArgs gets confused when n appears as both a full option and a short alias.

  2. Preprocess -<char>=<value> syntax — before passing args to parseArgs, expand -n=John to --name=John when n is a known alias for name. This bypasses the parseArgs limitation entirely.

Tests

  • Existing it.fails test now passes → changed to it
  • Added test: multiple short aliases with = syntax
  • Added test: unknown short flag with = passes through without crash
  • 19/19 tests pass

Rebased

Rebased on latest main to resolve merge conflicts. The parser changes from upstream (alias coercion, type handling) are preserved — this PR now only adds the = syntax handling.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Parser narrows registered options to long/multi-character names (booleans, strings, defaults, multi-char aliases), collects single-character short aliases separately, and adds type-aware coercion: "false" → false for booleans and boolean true → "" for string-declared options.

Changes

Cohort / File(s) Summary
Parser core
src/_parser.ts
Adds isStringType, computes shortAliasNames from 1-char alias entries, restricts allOptions to long/multi-character keys (booleans/strings/defaults/multi-char aliases), and applies value coercion after parsing (string "false" → boolean false for booleans; boolean true → "" for string options). Alias propagation and --no- handling remain.
Parser tests
test/parser.test.ts
Converts a previously failing alias test into a normal test; adds assertions for boolean coercion with --flag=true/false and for string-declared options receiving a bare --option (coerced to empty string, not boolean).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • pi0

Poem

🐰 I nibble dashes, hop through names,

Short hops point where long flags claim.
"false" unmasked, booleans set right,
Strings stay strings in moonlit night.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the parser to handle short aliases with the equals syntax (e.g., -n=value).
Linked Issues check ✅ Passed The PR successfully addresses issue #237 by excluding single-character alias keys from full option registration and ensuring values are correctly propagated to long options.
Out of Scope Changes check ✅ Passed All changes in the parser and tests directly relate to fixing the short alias with equals syntax issue; no out-of-scope changes detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/_parser.ts`:
- Around line 72-76: longOptions is being seeded with Object.keys(defaults)
which can include single-character short aliases (e.g., "n") and erroneously
promote them to full options; modify the longOptions initialization to only
include default keys that are longer than one character (e.g., filter
Object.keys(defaults).filter(k => k.length > 1)) so booleans and strings stay
unchanged and short aliases remain aliases; update the Set construction that
references longOptions accordingly (the line creating longOptions that spreads
booleans, strings, and Object.keys(defaults)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce3faaee-3d51-4498-a25e-d0c492938c36

📥 Commits

Reviewing files that changed from the base of the PR and between ea428c7 and 32da09a.

📒 Files selected for processing (1)
  • src/_parser.ts

@pi0
Copy link
Copy Markdown
Member

pi0 commented Mar 16, 2026

Can you please add tests?

@guoyangzhen
Copy link
Copy Markdown
Author

Done! Changed it.fails to it (the fix makes the existing test pass) and added two more edge case tests:

  • Short alias with = for boolean flags (-v=true)
  • Multiple short aliases in the same call

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/parser.test.ts (1)

59-74: Exercise both aliases (or rename the test) for clarity.

The case defines two short aliases but only validates -o. Either add a second assertion for -O or narrow the test title.

✅ Suggested test extension
   it("handles -<arg>=<value> with multiple short aliases", () => {
     const result = parseRawArgs(["-o=output.txt"], {
       string: ["output", "out"],
       alias: {
         o: ["output"],
         O: ["out"],
       },
     });

     expect(result).toEqual({
       _: [],
       o: "output.txt",
       output: "output.txt",
     });
+
+    const resultUpper = parseRawArgs(["-O=out.txt"], {
+      string: ["output", "out"],
+      alias: {
+        o: ["output"],
+        O: ["out"],
+      },
+    });
+
+    expect(resultUpper).toEqual({
+      _: [],
+      O: "out.txt",
+      out: "out.txt",
+    });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/parser.test.ts` around lines 59 - 74, The test "handles -<arg>=<value>
with multiple short aliases" in parser.test.ts defines two short aliases (o and
O) for parseRawArgs but only exercises -o; update the test to either also call
parseRawArgs(["-O=output.txt"], {...}) and add corresponding expectations for O
and out/output, or rename the test title to reflect that it only checks the -o
alias; locate the test case and modify the parseRawArgs invocation and expect
block (or the it() description) accordingly so both aliases are exercised or the
title accurately describes the single-alias check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/parser.test.ts`:
- Around line 59-74: The test "handles -<arg>=<value> with multiple short
aliases" in parser.test.ts defines two short aliases (o and O) for parseRawArgs
but only exercises -o; update the test to either also call
parseRawArgs(["-O=output.txt"], {...}) and add corresponding expectations for O
and out/output, or rename the test title to reflect that it only checks the -o
alias; locate the test case and modify the parseRawArgs invocation and expect
block (or the it() description) accordingly so both aliases are exercised or the
title accurately describes the single-alias check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f2505a0-6f73-45f0-be2e-eed6c9b89203

📥 Commits

Reviewing files that changed from the base of the PR and between 32da09a and d3745d4.

📒 Files selected for processing (1)
  • test/parser.test.ts

guoyangzhen pushed a commit to guoyangzhen/citty that referenced this pull request Mar 17, 2026
Follow-up to unjs#240. Filter single-char keys from defaults when building
longOptions to prevent them from being re-registered as full options.
Defaults for short aliases are still applied to their long-form counterparts.

Addresses CodeRabbit review feedback.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/_parser.ts (1)

81-116: ⚠️ Potential issue | 🟠 Major

Single-character defaults for real options are being dropped.

Object.keys(defaults).filter((k) => k.length > 1) removes defaults for legitimate one-character options (e.g. default: { x: "1" }) even when x is not a short alias. The remap block only restores keys found in aliasToMain, so non-aliased one-char defaults are lost.

Proposed fix
   const longOptions = new Set<string>([
     ...booleans,
     ...strings,
-    ...Object.keys(defaults).filter((k) => k.length > 1),
+    ...Object.keys(defaults).filter((k) => !shortAliasNames.has(k)),
   ]);
@@
-  for (const key of Object.keys(defaults)) {
-    if (key.length === 1) {
+  for (const key of Object.keys(defaults)) {
+    if (shortAliasNames.has(key)) {
       const longForm = aliasToMain.get(key);
       if (longForm && !longOptions.has(longForm)) {
         options[longForm] = {
           type: getType(longForm),
           default: defaults[key],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/_parser.ts` around lines 81 - 116, The code drops default values for
legitimate single-character options because longOptions is initialized using
Object.keys(defaults).filter(k => k.length > 1) and the restore only handles
keys present in aliasToMain; fix by ensuring single-char defaults that are not
aliases are registered: when iterating Object.keys(defaults) if key.length === 1
and aliasToMain.get(key) is falsy, create an options entry for that single-char
key (use getType(key) and defaults[key]) and (optionally) add it to longOptions
or another tracking set so its default isn't lost; keep the existing
alias-handling branch that maps single-char aliases to their longForm when
aliasToMain has a value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/_parser.ts`:
- Around line 81-116: The code drops default values for legitimate
single-character options because longOptions is initialized using
Object.keys(defaults).filter(k => k.length > 1) and the restore only handles
keys present in aliasToMain; fix by ensuring single-char defaults that are not
aliases are registered: when iterating Object.keys(defaults) if key.length === 1
and aliasToMain.get(key) is falsy, create an options entry for that single-char
key (use getType(key) and defaults[key]) and (optionally) add it to longOptions
or another tracking set so its default isn't lost; keep the existing
alias-handling branch that maps single-char aliases to their longForm when
aliasToMain has a value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16d1e94b-807a-450f-99c8-3c7bc433c884

📥 Commits

Reviewing files that changed from the base of the PR and between 823affe and 653b15a.

📒 Files selected for processing (1)
  • src/_parser.ts

@guoyangzhen
Copy link
Copy Markdown
Author

Updated to latest upstream code. The parser fix filters single-char aliases from allOptions so they are only registered via short property. Test changed from it.fails to it.

…--key=val (unjs#237)

- Filter single-char alias names from allOptions so they are only
  registered via node:util parseArgs  property
- Add preprocessing to expand -<char>=<value> to --<main>=<value>
  since node:util parseArgs does not handle = syntax for short aliases
- Fix from 653b15a was already upstreamed; this is the remaining piece
- 17→19 tests (added multiple short aliases + unknown flag edge cases)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@guoyangzhen
Copy link
Copy Markdown
Author

@pi0 Done! I rebased onto the latest main — the it.fails now passes, and I added two more edge case tests:

  1. Multiple short aliases with = syntax (-n=John -a=admin)
  2. Unknown short flag with = — passes through without crash

19/19 tests pass. Also squashed the previous messy commit history into a clean single commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support alias value with = (-foo=bar)

3 participants