Skip to content

fix: generic angle bracket parsing#341

Merged
pi0 merged 8 commits intounjs:mainfrom
Flo0806:fix/generic-angle-bracket-parsing
Mar 19, 2026
Merged

fix: generic angle bracket parsing#341
pi0 merged 8 commits intounjs:mainfrom
Flo0806:fix/generic-angle-bracket-parsing

Conversation

@Flo0806
Copy link
Copy Markdown
Contributor

@Flo0806 Flo0806 commented Mar 19, 2026

Fixes: nuxt/nuxt#34644


Hi!

findExports incorrectly extracts type parameters from generic expressions as export names.

For export const APP_MODE_MAP = new Map<string, IAppMode>(), the result was names: ["APP_MODE_MAP", "IAppMode"] instead of just ["APP_MODE_MAP"].

The comma inside <string, IAppMode> was treated as a declaration separator because angle bracket tracking only kicked in after a : (type annotation). Generics in value position (new Map<...>, foo<T>(...)) were never tracked.

The fix: also track < as a generic opener when preceded by an identifier character ([A-Za-z_$]), which distinguishes Map<T> from x < y.

This caused, as linked, real issues downstream - in Nuxt, scanDirExports from unimport picked up IAppMode as a value export, which broke type checking when multiple imports.dirs were configured.

Tests

  • All tests are green
  • Added 3 new tests for this scenario

Summary by CodeRabbit

  • Bug Fixes

    • More robust handling of angle-bracket generics: top-level identifier-based < now treated as generic openings and > ignores >=-like sequences, fixing comma/classification errors in declaration extraction.
  • Tests

    • Added coverage for varied export patterns: generic types and calls, multi-declarator exports, map/set initializers, and ambiguous angle-bracket sequences.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aad46b25-acf8-4aea-b999-33eeb6923345

📥 Commits

Reviewing files that changed from the base of the PR and between 6d44292 and 3ce1b69.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/exports.test.ts
  • src/analyze.ts

📝 Walkthrough

Walkthrough

Refined generic angle-bracket tracking in _extractExtraNames() to treat < as generic-opening at top-level when preceded by an identifier-like token and to avoid >/>= miscounts; added tests covering various export const patterns with generics and multi-declarator exports.

Changes

Cohort / File(s) Summary
Parser Logic
src/analyze.ts
Treat < as starting generic depth at depth === 0 when preceded by an identifier-like token (new helper _isIdentifierBeforeAngleBracket(str, i)); only decrement angleDepth on > when angleDepth > 0 and the preceding char isn't = (prevents >= from altering generic depth). Adjusts comma handling for extra-name extraction accordingly.
Tests
test/exports.test.ts
Added findExports fixtures covering export const with generic initializers, generic call syntax (foo<...>(...)), multi-declarator exports, Map/Set-style initializers with nested generics, and edge-case 1<...> sequences to validate declaration extraction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • pi0

Poem

🐰 Brisk paws tap the parser’s way,
Angle-brackets learn to behave today,
Generics now leap where they belong,
Exports clear their names and song,
Hop, debug, and type-check hooray!

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: generic angle bracket parsing' directly describes the main change: fixing how angle brackets in generic types are parsed during export extraction.
Linked Issues check ✅ Passed The PR successfully addresses the root cause of #34644 by fixing angle bracket tracking in generic expressions, preventing type parameters from being incorrectly extracted as export names.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing generic angle bracket parsing in the _extractExtraNames() function and adding corresponding test coverage, directly addressing the linked issue.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

🧹 Nitpick comments (2)
test/exports.test.ts (1)

148-161: Good test coverage for the primary fix scenarios.

The new tests properly validate the core issue where generics in value expressions were incorrectly parsed. Consider adding a test for nested generics to ensure complete coverage:

💡 Optional: Additional test case for nested generics
"export const nested = new Map<string, Handler<A, B>>()": {
  type: "declaration",
  name: "nested",
  names: ["nested"],
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/exports.test.ts` around lines 148 - 161, Add a test that covers nested
generics so the parser won't mis-handle inner generic angle brackets;
specifically add a declaration case for a symbol like "nested" using a nested
generic value expression such as new Map<string, Handler<A, B>>() (refer to
existing test patterns like APP_MODE_MAP, result, and a/b) to ensure the test
verifies the parser correctly recognizes "nested" and its names array contains
["nested"] and the type is "declaration".
src/analyze.ts (1)

439-451: Pattern change is optional for robustness; edge case does not occur in the current codebase.

The fix correctly distinguishes generic syntax from comparison operators by checking if < is preceded by an identifier character. However, the regex /[A-Za-z_$]/ excludes digits, which would theoretically prevent recognition of generics on identifiers ending in numbers (e.g., Handler2<A, B>, ES6Map<K, V>). A search of the codebase found no existing instances of this pattern, so this is not a practical issue. That said, extending the regex to /[\w$]/ or /[A-Za-z0-9_$]/ would be a defensive improvement for future-proofing the logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/analyze.ts` around lines 439 - 451, The check that decides whether a '<'
starts a generic uses the regex /[A-Za-z_$]/ which ignores digits and thus fails
for identifiers ending in numbers; update the condition used where
inTypeAnnotation and char === "<" and depth === 0 ... (the block manipulating
angleDepth, inTypeAnnotation, char, depth, extraNamesStr) to use a character
class that includes digits (e.g., use \w or explicitly A-Za-z0-9_$) so
identifiers like Handler2<A,B> or ES6Map<K,V> are recognized as generics and
angleDepth is incremented correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/analyze.ts`:
- Around line 439-451: The check that decides whether a '<' starts a generic
uses the regex /[A-Za-z_$]/ which ignores digits and thus fails for identifiers
ending in numbers; update the condition used where inTypeAnnotation and char ===
"<" and depth === 0 ... (the block manipulating angleDepth, inTypeAnnotation,
char, depth, extraNamesStr) to use a character class that includes digits (e.g.,
use \w or explicitly A-Za-z0-9_$) so identifiers like Handler2<A,B> or
ES6Map<K,V> are recognized as generics and angleDepth is incremented correctly.

In `@test/exports.test.ts`:
- Around line 148-161: Add a test that covers nested generics so the parser
won't mis-handle inner generic angle brackets; specifically add a declaration
case for a symbol like "nested" using a nested generic value expression such as
new Map<string, Handler<A, B>>() (refer to existing test patterns like
APP_MODE_MAP, result, and a/b) to ensure the test verifies the parser correctly
recognizes "nested" and its names array contains ["nested"] and the type is
"declaration".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69a2b080-83f4-4c72-8e3b-240459988f18

📥 Commits

Reviewing files that changed from the base of the PR and between ed17783 and aa22b1e.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts

autofix-ci bot and others added 3 commits March 19, 2026 21:01
Use `\w$` instead of `[A-Za-z_$]` so identifiers ending in digits
(e.g. `Map2<T>`) are recognized as generics. Add tests for nested
generics and digit-ending identifiers.
Copy link
Copy Markdown
Member

@pi0 pi0 left a comment

Choose a reason for hiding this comment

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

Thnx ❤️

@Flo0806
Copy link
Copy Markdown
Contributor Author

Flo0806 commented Mar 19, 2026

@pi0 Important! I'm sure you know that, but \w also matches digits — so 1<2 would be treated as a generic instead of a comparison!

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/analyze.ts`:
- Around line 454-457: The bug is that the '>' in an arrow token '=>' is
decrementing angleDepth; update the block that handles char === ">" (where
angleDepth is decremented) to ignore '>' when it is part of an arrow token by
checking the previous character (skip decrement if the previous char is '='
indicating '=>'). Locate the code that updates angleDepth (references: variable
angleDepth and the char === ">" branch) and add a guard (e.g., if (prevChar ===
'=') continue) so '=>' does not change angleDepth; keep existing logic for all
other '>' cases unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f408cf5-ee16-4fbb-a787-067d8abd2d1c

📥 Commits

Reviewing files that changed from the base of the PR and between aa22b1e and 05be803.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/exports.test.ts

…perands

`\w` and `[A-Za-z0-9_$]` both match bare digits, so `1<2` was
incorrectly treated as a generic. Walk back to the start of the word
and verify it begins with a letter/underscore/$ (valid JS identifier).
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/exports.test.ts (1)

148-175: Optional: add _/$ generic opener fixtures to fully lock the heuristic.

Since the opener rule allows identifier starts [A-Za-z_$], consider adding fixtures like export const x = _Factory<T>() and export const y = $Factory<T>() to guard those branches explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/exports.test.ts` around lines 148 - 175, Add explicit test fixtures that
cover generic-openers beginning with '_' and '$' to lock the heuristic: create
declarations like variables x and y that call generic factories whose names
start with '_' and '$' (e.g., use identifiers x and y and factory names _Factory
and $Factory with a generic parameter, such as x = _Factory<T>() and y =
$Factory<T>()), and add them alongside the existing export const cases
(reference symbols APP_MODE_MAP, result, nested, m) in the same test file so the
parser branches for '_' and '$' are exercised.
🤖 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/exports.test.ts`:
- Around line 148-175: Add explicit test fixtures that cover generic-openers
beginning with '_' and '$' to lock the heuristic: create declarations like
variables x and y that call generic factories whose names start with '_' and '$'
(e.g., use identifiers x and y and factory names _Factory and $Factory with a
generic parameter, such as x = _Factory<T>() and y = $Factory<T>()), and add
them alongside the existing export const cases (reference symbols APP_MODE_MAP,
result, nested, m) in the same test file so the parser branches for '_' and '$'
are exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65be45c6-f28a-4134-ae59-1fedfcd4c0a7

📥 Commits

Reviewing files that changed from the base of the PR and between 05be803 and 6d44292.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/analyze.ts

pi0 and others added 2 commits March 19, 2026 22:14
The `>` in `=>` inside generic type annotations was decrementing
angleDepth, causing commas after the arrow to be treated as declaration
separators (e.g. `Map<(x: string) => void, string>`).
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.64%. Comparing base (226a47b) to head (3ce1b69).
⚠️ Report is 111 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #341      +/-   ##
==========================================
- Coverage   88.04%   83.64%   -4.41%     
==========================================
  Files           8        7       -1     
  Lines        1062      489     -573     
  Branches      188      151      -37     
==========================================
- Hits          935      409     -526     
+ Misses        127       73      -54     
- Partials        0        7       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pi0
Copy link
Copy Markdown
Member

pi0 commented Mar 19, 2026

Sorry my clanker's fault! @Flo0806 updated can you double check?

(PS we should really soon migrate to ast aware utils in next major!)

@Flo0806
Copy link
Copy Markdown
Contributor Author

Flo0806 commented Mar 19, 2026

Looks good for me. The linked Nuxt issue is also fixed with this ❤️

@pi0 pi0 merged commit abef19c into unjs:main Mar 19, 2026
6 of 7 checks passed
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.

Typecheck fails when using multiple imports.dirs in Nuxt 4 / monorepo / turbo

2 participants