Skip to content

fix(module_graph): properly index and resolve barrel exports#9267

Merged
ematipico merged 4 commits into
mainfrom
fix/module-graph-resolution
Mar 2, 2026
Merged

fix(module_graph): properly index and resolve barrel exports#9267
ematipico merged 4 commits into
mainfrom
fix/module-graph-resolution

Conversation

@ematipico

@ematipico ematipico commented Feb 27, 2026

Copy link
Copy Markdown
Member

Summary

Closes #9143
Closes #8849

This was done with an AI agent, carefully steered towards a proper solution. I also made sure to add proper type inference for the new exports.

Our module graph didn't follow blanked exports and blanked re-exports properly when they were defined as namespaces.

I carefully reviwed all the code.

Test Plan

Manually tested against the libraries. Added many tests

Docs

N/A

@changeset-bot

changeset-bot Bot commented Feb 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5372f16

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ematipico ematipico requested review from a team February 27, 2026 17:53
@github-actions github-actions Bot added A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Resolver Area: resolver labels Feb 27, 2026
@codspeed-hq

codspeed-hq Bot commented Feb 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing fix/module-graph-resolution (5372f16) with main (2d0b8e6)

Open in CodSpeed

Footnotes

  1. 156 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

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

Updates the no_unresolved_imports lint and module-graph to reduce false positives: accept node: Node.js built-ins as resolved; prefer types over typings when both appear in package.json; resolve named imports through aliased re-export chains; and treat export * as Ns from "..." as an own namespace export. Changes touch linter logic, module-graph types and resolution, formatters, and tests that exercise these cases.

Possibly related PRs

  • PR #7017: Edits the same no_unresolved_imports implementation and import-resolution logic—directly related to the lint fixes.
  • PR #7121: Adjusts re-export and alias resolution in the resolver, overlapping the aliased re-export handling here.
  • PR #8564: Modifies module-graph export/reexport representation and resolution (js_module_info/collector/module_graph), closely related to namespace-export changes.

Suggested labels

A-Resolver, A-Type-Inference

Suggested reviewers

  • arendjr
  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(module_graph): properly index and resolve barrel exports' accurately describes the main change—improving module graph handling of blanked exports and re-exports defined as namespaces.
Description check ✅ Passed The description directly relates to the changeset, detailing fixes for issues #9143 and #8849, and explains the core improvement: proper handling of blanked exports and re-exports with namespace definitions.
Linked Issues check ✅ Passed The PR addresses all primary coding objectives: Node.js built-in support via node: prefix [#8849, #9143], namespace re-export resolution [#9143], and aliased re-export handling [#9143] are all implemented.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: type inference enhancements, module graph updates, lint rule fixes, test additions, and documentation updates for development guidance are directly scoped to barrel export resolution.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/module-graph-resolution

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
crates/biome_resolver/src/lib.rs (1)

610-615: Please align resolve_types docs with the new fallback behaviour.

The implementation now falls back to main when types/typings is absent, but the ResolveOptions::resolve_types docs still say main is ignored.

Doc tweak suggestion
-    /// - The `package.json`'s `main` field will be ignored.
+    /// - The `package.json`'s `types`/`typings` field is preferred.
+    ///   If neither is present, the `main` field is used as a fallback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_resolver/src/lib.rs` around lines 610 - 615, Update the
ResolveOptions::resolve_types documentation to reflect that when resolve_types
is true the resolver prefers package_json.types/typings but will fall back to
package_json.main if neither types nor typings is present; change any wording
that currently states "main is ignored" to indicate that main is used as a
fallback and mention the exact precedence (types/typings first, then main).
Ensure references to options.resolve_types and the behavior in the resolver (the
if branch using package_json.types.as_ref().or(package_json.main.as_ref())) are
consistent with the doc text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/skills/testing-codegen/SKILL.md:
- Line 306: The ordered list item "3. **Description**: What changed (used in
CHANGELOG)" is incorrectly numbered and fails markdownlint MD029; change the
leading "3." to "1." so the list restarts correctly (edit the line in
.claude/skills/testing-codegen/SKILL.md where the Description list item appears
and replace "3." with "1.").
- Around line 185-187: Fix the broken bold markdown spans in SKILL.md by merging
the split "**" markers so each bolded phrase is contained on a single line;
specifically update the line that currently breaks "Every JS/TS test spec file
**must ** begin..." (around the referenced block) and the similar broken span
near line 223–224 so the opening and closing "**" are on the same line and the
bold text renders correctly.

In `@crates/biome_package/src/node_js_package/package_json.rs`:
- Around line 309-313: The current match arm for "types" | "typings" lets the
first-seen key win, so if "typings" arrives before "types" it can incorrectly
block the canonical "types"; change the logic to inspect the actual key name:
when key_text == "types" always assign result.types = Some(value) (overwriting
any prior), but when key_text == "typings" only assign if
result.types.is_none(); keep using Deserializable::deserialize(...) to obtain
the value before assignment and reference the existing result.types and key_text
symbols to locate where to implement this conditional.

---

Nitpick comments:
In `@crates/biome_resolver/src/lib.rs`:
- Around line 610-615: Update the ResolveOptions::resolve_types documentation to
reflect that when resolve_types is true the resolver prefers
package_json.types/typings but will fall back to package_json.main if neither
types nor typings is present; change any wording that currently states "main is
ignored" to indicate that main is used as a fallback and mention the exact
precedence (types/typings first, then main). Ensure references to
options.resolve_types and the behavior in the resolver (the if branch using
package_json.types.as_ref().or(package_json.main.as_ref())) are consistent with
the doc text.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84935a4 and 5fcdcd8.

⛔ Files ignored due to path filters (9)
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-barrel.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-source.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-barrel.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-source.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/validRegressions.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_module_graph/tests/snapshots/test_namespace_reexport_type_inference.snap is excluded by !**/*.snap and included by **
  • crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (20)
  • .changeset/fix-no-unresolved-imports-false-positives.md
  • .claude/skills/lint-rule-development/SKILL.md
  • .claude/skills/testing-codegen/SKILL.md
  • crates/biome_js_analyze/src/lint/correctness/no_unresolved_imports.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-barrel.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-source.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/invalid.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-barrel.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-source.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/valid.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/validRegressions.js
  • crates/biome_module_graph/src/format_module_graph.rs
  • crates/biome_module_graph/src/js_module_info.rs
  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_module_graph/src/js_module_info/module_resolver.rs
  • crates/biome_module_graph/src/module_graph.rs
  • crates/biome_module_graph/tests/snap/mod.rs
  • crates/biome_module_graph/tests/spec_tests.rs
  • crates/biome_package/src/node_js_package/package_json.rs
  • crates/biome_resolver/src/lib.rs

Comment thread .claude/skills/testing-codegen/SKILL.md Outdated
Comment thread .claude/skills/testing-codegen/SKILL.md
Comment thread crates/biome_package/src/node_js_package/package_json.rs Outdated
@ematipico ematipico force-pushed the fix/module-graph-resolution branch from 5fcdcd8 to b66f95f Compare February 27, 2026 18:09
@github-actions github-actions Bot removed the A-Resolver Area: resolver label Feb 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
.claude/skills/testing-codegen/SKILL.md (1)

304-304: ⚠️ Potential issue | 🟡 Minor

Fix CI blocker: use consistent "1." numbering for ordered list items.

The markdownlint check (MD029) enforces "1/1/1" style where every ordered list item should be numbered "1." and the renderer handles auto-numbering. Change line 304 from "3." to "1." to match the style.

🔧 Proposed fix
-3. **Description**: What changed (used in CHANGELOG)
+1. **Description**: What changed (used in CHANGELOG)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/testing-codegen/SKILL.md at line 304, The ordered list item
"3. **Description**: What changed (used in CHANGELOG)" should use the
markdownlint MD029 "1." style; update that list marker from "3." to "1." (and
verify other nearby ordered items also use "1." so the list is consistently
auto-numbered), leaving the rest of the line text unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.claude/skills/testing-codegen/SKILL.md:
- Line 304: The ordered list item "3. **Description**: What changed (used in
CHANGELOG)" should use the markdownlint MD029 "1." style; update that list
marker from "3." to "1." (and verify other nearby ordered items also use "1." so
the list is consistently auto-numbered), leaving the rest of the line text
unchanged.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fcdcd8 and b66f95f.

⛔ Files ignored due to path filters (9)
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-barrel.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-source.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-barrel.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-source.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/validRegressions.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_module_graph/tests/snapshots/test_namespace_reexport_type_inference.snap is excluded by !**/*.snap and included by **
  • crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (19)
  • .changeset/fix-no-unresolved-imports-false-positives.md
  • .claude/skills/lint-rule-development/SKILL.md
  • .claude/skills/testing-codegen/SKILL.md
  • crates/biome_js_analyze/src/lint/correctness/no_unresolved_imports.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-barrel.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/aliased-reexport-source.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/invalid.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-barrel.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/namespace-reexport-source.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/valid.js
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/validRegressions.js
  • crates/biome_module_graph/src/format_module_graph.rs
  • crates/biome_module_graph/src/js_module_info.rs
  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_module_graph/src/js_module_info/module_resolver.rs
  • crates/biome_module_graph/src/module_graph.rs
  • crates/biome_module_graph/tests/snap/mod.rs
  • crates/biome_module_graph/tests/spec_tests.rs
  • crates/biome_package/src/node_js_package/package_json.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/validRegressions.js
  • crates/biome_module_graph/tests/snap/mod.rs
  • crates/biome_module_graph/src/js_module_info.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/invalid.js
  • .claude/skills/lint-rule-development/SKILL.md
  • crates/biome_module_graph/src/js_module_info/module_resolver.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnresolvedImports/valid.js

Comment thread .changeset/fix-no-unresolved-imports-false-positives.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@crates/biome_package/src/node_js_package/package_json.rs`:
- Around line 308-317: The code uses result.types.is_none() to decide if the
legacy "typings" key should override, but that reflects deserialization success
not presence; add a boolean flag (e.g., types_seen) in the parsing scope/struct
and set it in the "types" match arm (when handling "types" — where
Deserializable::deserialize(ctx, &value, &key_text) is called) to true
regardless of whether deserialization returned Some/None, then change the
"typings" guard to check !types_seen (instead of result.types.is_none()) so
"typings" only takes effect when "types" was not present at all.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b66f95f and 735ace8.

📒 Files selected for processing (2)
  • .changeset/fix-no-unresolved-imports-false-positives.md
  • crates/biome_package/src/node_js_package/package_json.rs

Comment thread crates/biome_package/src/node_js_package/package_json.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/biome_package/src/node_js_package/package_json.rs (1)

313-316: ⚠️ Potential issue | 🟡 Minor

typings can still win when types is present but invalid

On Line 314, the guard checks deserialisation success (result.types.is_none()), not whether "types" was present.
So "types": 123 followed by valid "typings" still lets the alias override the canonical field.

Consider tracking presence explicitly (e.g. seen_types_field) and gating "typings" on that flag.

Suggested fix
-        let mut result = Self::Output::default();
+        let mut result = Self::Output::default();
+        let mut seen_types_field = false;
@@
                 "types" => {
+                    seen_types_field = true;
                     result.types = Deserializable::deserialize(ctx, &value, &key_text);
                 }
@@
                 "typings" => {
-                    if result.types.is_none()
+                    if !seen_types_field
                         && let Some(value) = Deserializable::deserialize(ctx, &value, &key_text)
                     {
                         result.types = Some(value);
                     }
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_package/src/node_js_package/package_json.rs` around lines 313 -
316, The "typings" branch currently checks result.types.is_none() which only
tests deserialization, not whether the "types" field was present, allowing
invalid "types" values to be overridden by "typings"; modify the parsing logic
to track explicit presence of the "types" field (e.g. add a seen_types_field
boolean set when handling the "types" key) and change the guard in the "typings"
arm to only allow aliasing when seen_types_field is false and
Deserializable::deserialize(ctx, &value, &key_text) succeeds; update the code
paths that set result.types to also set the seen flag so
Deserializable::deserialize and the "typings" branch both consult that flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_package/src/node_js_package/package_json.rs`:
- Around line 313-316: The "typings" branch currently checks
result.types.is_none() which only tests deserialization, not whether the "types"
field was present, allowing invalid "types" values to be overridden by
"typings"; modify the parsing logic to track explicit presence of the "types"
field (e.g. add a seen_types_field boolean set when handling the "types" key)
and change the guard in the "typings" arm to only allow aliasing when
seen_types_field is false and Deserializable::deserialize(ctx, &value,
&key_text) succeeds; update the code paths that set result.types to also set the
seen flag so Deserializable::deserialize and the "typings" branch both consult
that flag.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 735ace8 and 1aac784.

📒 Files selected for processing (1)
  • crates/biome_package/src/node_js_package/package_json.rs

@ematipico ematipico force-pushed the fix/module-graph-resolution branch from 6d03dc6 to 5372f16 Compare March 2, 2026 12:00
@ematipico ematipico merged commit 2c2e060 into main Mar 2, 2026
20 checks passed
@ematipico ematipico deleted the fix/module-graph-resolution branch March 2, 2026 12:07
@github-actions github-actions Bot mentioned this pull request Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 noUnresolvedImports: multiple categories of false positives 💅 noUnresolvedImports emits diagnostics for node.js builtins

2 participants