Skip to content

fix export start reexports, add tests - #32

Merged
mohebifar merged 2 commits into
mainfrom
export-start-reexport
May 18, 2026
Merged

fix export start reexports, add tests#32
mohebifar merged 2 commits into
mainfrom
export-start-reexport

Conversation

@cdc1996

@cdc1996 cdc1996 commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a debarrel bug where consumer imports that reach the barrel through bare export * from "./y" re-exports were silently left pointing at the barrel.

Given a barrel like:

// src/lib/index.ts
export { AppError, isAppError } from "./appError";
export * from "./operations";   // ← thousands of generated types in real codebases

Imports of anything reached via the export * branch (here DEFAULT_CURRENCY, AddressFragment, CurrencyFragment) weren't being rewritten, defeating most of the point of running the codemod against barrels that re-export generated GraphQL types or similar.

Root cause

In this jssg runtime, localBinding.definition() does not chase through bare export * re-exports — it returns def.kind === "import" pointing back at the consumer's own import_specifier. The existing resolveSpecifier bailed early on anything that wasn't def.kind === "external", so those specifiers fell out of the rewrite loop entirely.

(The original issue writeup guessed the analyzer chased through the wildcard and landed in operations.ts. That turned out to be wrong for this runtime — the analyzer punts. The fix has to walk the chain manually.)

What changed

  • scripts/utils/exportStar.ts (new) — when the analyzer punts, walks the barrel's export * chain by reading files off disk and finds which file declares the name. Handles nested export * chains via a bounded recursive walk; ignores export * as Ns from (the analyzer already resolves those).
  • scripts/utils/specifiers.ts — routes def.kind !== "external" specifiers into the new walker instead of returning null. The metric's filePath still points at the barrel so cardinality matches the existing branches.
  • scripts/utils/paths.ts — adds resolveImportPath so the walker can resolve ./y-style import paths to absolute files (Node/TS file-vs-directory-index precedence).
  • scripts/codemod.ts + scripts/utils/imports.ts — preserves the top-level import type modifier when every specifier in a type-only import is rewritten. Without this, the rewritten code would break under --verbatimModuleSyntax since the resolved declarations are export type aliases.

Tests

New fixture tests/export-star-reexport/ mirrors the shape of the reported repro:

  • consumer with both a regular import and an import type { … } from "./lib"
  • barrel mixing one named re-export (export { … } from "./appError") and one bare wildcard (export * from "./operations")
  • appError.ts (control, named re-export path) and operations.ts (the wildcard target)

Verified failing on main and passing after the change. Full suite stays green (85/85).

Test plan

  • pnpm test in codemods/debarrel/
  • Spot-check the new fixture diff matches the expected output
  • Verify import type is still preserved on the rewritten line
  • Confirm no regressions in the existing namespace-reexport and partial-with-type-specifiers fixtures (closest neighbors)

@cdc1996
cdc1996 force-pushed the export-start-reexport branch from 7f1f1c2 to d7aad62 Compare May 14, 2026 21:08
@cdc1996
cdc1996 marked this pull request as ready for review May 14, 2026 22:49
@mohebifar
mohebifar requested a review from Copilot May 16, 2026 07:20

Copilot AI 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.

Pull request overview

This PR fixes a debarrel resolution gap where imports that reach a barrel through bare export * from "./x" re-exports were not rewritten, and it adds a regression fixture to cover the behavior (including preserving top-level import type across rewrites).

Changes:

  • Add a manual export * chain walker to resolve symbols when the semantic analyzer returns def.kind !== "external".
  • Extend path utilities with resolveImportPath() to resolve ./x to an on-disk module file (file vs index precedence).
  • Preserve top-level import type when rewriting an entire import statement, and add a new fixture test for export-star re-exports.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.changeset/export-star-rewrites.md Announces the export-star rewrite fix and import type preservation as a patch change.
codemods/debarrel/scripts/codemod.ts Passes importer path context into specifier resolution and preserves top-level import type during rewrites.
codemods/debarrel/scripts/utils/exportStar.ts New helper that walks export * from "./x" chains by reading files and finding which target exports a symbol.
codemods/debarrel/scripts/utils/imports.ts Adds support for emitting import type ... in generated import text.
codemods/debarrel/scripts/utils/paths.ts Adds resolveImportPath() for Node/TS-like file-vs-index resolution of relative imports.
codemods/debarrel/scripts/utils/specifiers.ts Routes analyzer “punt” cases into the manual export-star walker and computes rewrite targets/metrics.
codemods/debarrel/tests/export-star-reexport/** Adds a fixture validating rewrites through bare export * re-exports and import type preservation, plus metrics expectations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +173 to +178
const barrelDir = path.dirname(barrelFile);
let rel = path.relative(barrelDir, targetFile);
const ext = path.extname(rel);
if (ext) rel = rel.slice(0, -ext.length);
rel = rel.replace(/\/index$/, "") || ".";
const fromBarrel = rel.startsWith(".") ? rel : `./${rel}`;
Comment on lines 143 to 147
if (remainingSpecTexts.length > 0) {
const typeKeyword = isTypeOnlyImport ? "type " : "";
lines.push(
`import { ${remainingSpecTexts.join(", ")} } from ${quoteChar}${importPath}${quoteChar};`,
`import ${typeKeyword}{ ${remainingSpecTexts.join(", ")} } from ${quoteChar}${importPath}${quoteChar};`,
);
@mohebifar
mohebifar merged commit 06b0a00 into main May 18, 2026
1 check 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.

3 participants