Repository navigation
fix(ts): a re-export imports its module, and sync follows barrels of barrels - #2526
Merged
Merged
Conversation
…barrels An `export … from './x'` statement emitted one `imports` reference per re-exported name and nothing for the module itself; `export * from` emitted nothing at all. So a barrel made only of `export *` lines had no dependency on what it re-exports (no dependent for `affected`), and sync could not find an `index.ts` that re-exports another barrel: when the inner one was deleted or added, the outer one's importers kept their stale or name-matched edges. A re-export now imports its module the way `import … from` does, in both extractors (TS and the Rust kernel): an import node named by the specifier and a module reference that links the barrel to the file it names. Sync's importer discovery follows such modules to their own importers, carrying the names each passes on (`export *`, a named or renamed re-export, an `export * as ns`), as deep as the resolver chases a re-export; an ES namespace import reads whatever its module passes on. A re-export's import node does not count as a symbol for the "parse produced no symbols" warning, so a barrel with an unparseable line still reports it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
Merged
colbymchenry
added a commit
that referenced
this pull request
Oct 23, 2026
…parses (#2837) TypeScript 5.0's type-only re-export of a whole module, `export type * from './x'` and `export type * as ns from './x'`, has no rule in the vendored tree-sitter-typescript grammar (v0.23.2, the revision the kernel's crate compiles) or in upstream master: `type` parses as an ERROR inside the export statement. The graph was already right, since the import resolver reads the statement from the file (PRs #1955 and #2526), but the error tree meant a barrel of re-exports got the "parse produced no symbols" warning and a parse_error row, `codegraph status` listed it among the files it could not parse, and the native kernel deferred every file with such a line to wasm. The TypeScript extractor's preParse (.ts and .tsx) now blanks that `type` to four spaces where a statement starts (a line start, or after `;`, `{`, `}` or a block comment) when `*` is followed by `from` or `as`, so the line reads as the `export *` it is to the graph. Offsets are kept. The kernel route applies the preParse before calling the kernel, so both engines parse the same bytes and no Rust change is needed. A grammar patch would mean vendoring and regenerating the grammar for both dialects, two wasm rebuilds and a kernel off the crates.io crate, for a statement `export *` already models. A literal pre-check skips other files: 16 ms over strapi's 4,659 TS files. The only node change is an import node's stored statement text, which now reads `export * from './x'`. Import nodes are filtered from search, explore and the context builder, and nothing in resolution reads it. status-index-health.test.ts used `export type * from` as its unparseable file (issue #2336); it now uses a file with merge-conflict markers. Validation: - New ts-export-type-star.test.ts, 15 tests: 13 fail with main's typescript.ts, 11 with the blank unwired, all pass with the fix (kernel and wasm, .ts and .tsx, LF and CRLF). - A/B, kernel engine, main vs fix, dumps by natural keys: masto.js, kysely, TanStack/query, graffle and strapi keep every edge and ref (162,421 and 192,248 on strapi). The only changes are 883 import signatures, one per `export type *` statement, and 76 cleared warnings (masto.js 11, kysely 1, query 4, graffle 28, strapi 32). The Go and Python controls gin and requests are byte-identical. - Kernel parity, typescript and tsx: 0 diffs on every sweep. Deferrals drop masto.js 11 to 0, graffle 203 to 13, strapi 105 to 69, query 32 to 27, kysely 14 to 12; the rest are other grammar gaps. - Index time, median of 3 interleaved: graffle 12.7 s vs 13.1 s, query 11.2 s vs 11.4 s, within noise. Fixes #2474 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An
export … from './x'statement left almost nothing in the graph. Both extractors emitted oneimportsreference per re-exported name (export { A } from './x'→A). They emitted nothing for the module itself, and nothing at all forexport * from './x'. Two things followed:export *lines had no dependency on what it re-exports. A module's dependents, and socodegraph affected, missed the barrel.index.tswithexport * from './dropdown-menu'has no such edge. So when the inner barrel was deleted, the outer barrel's importers kept edges resolved through it. When the inner barrel was added, they kept the edges a name lookup had made in its absence. A fresh index of the same tree disagreed. This was fix(sync): imports through a re-exporting module follow it when it is deleted or added #2512's residue "Nested ES-module barrels".Fix
import … fromdoes, in both extractors:src/extraction/tree-sitter.tscallsextractImportfor anexport_statementwith asource.codegraph-kernel/src/tsjs/mod.rscallsextract_importfor one.'reka-ui','@scope/pkg') pass the resolver's name pre-filter, as an import statement's does.src/resolution/module-importers.ts):export *passes everything, a named or renamed re-export its exported name,export * as nsthe namespace.REEXPORT_MAX_DEPTH, as far as the resolver'sfindExportedSymbolchases a re-export.import * as z from './index') reads whatever its module passes on. Python keeps fix(sync): imports through a re-exporting module follow it when it is deleted or added #2512's rules.export type * from, TypeScript 'export type * from' makes the whole file parse to no symbols #2474) still reports it, asstatus-index-health.test.tspins.Fresh indexes change for JavaScript and TypeScript files with
export … fromstatements: one import node, itscontainsedge, and a module edge (or a failed reference) per statement. The CHANGELOG asks users to re-index.Validation
Tests
__tests__/extraction.test.ts:export *, a named re-export andexport * as;export *-only barrel is a dependent of its module.__tests__/sync-module-importers.test.ts, ES-module fixture: an outersrc/ui/index.ts(export *plus a renamed re-export), andkit.tspassing one name on, read through a namespace import. Deleted, restored and added late, each compared with a fresh index of the final tree in a second folder.kernel-tsjs-parity,kernel-node-id-parity,kernel-grammar-parity) passes, including real-source parity onsrc/mcp/tools.ts.git-index-currency(status.pendingChanges reports zero for a committed-but-unindexed file — the git fast path never hash-checks what git omits #1829, timing-based, run while the A/B loaded the machine), passed 13/13 twice in isolation.Fresh-index A/B (main and this PR, kernel on). The only differences are the expected ones:
containsedgesreka-ui,vue-i18n,zod) or workspace packages whoseimportstatements fail the same way on main (@excalidraw/element: 414 failed import refs, none resolved).Sync A/B. Each arm's synced graph is compared with its own fresh index of the same tree. Cells are differing graph lines, main → this PR. Barrels are every 2nd
index.{ts,tsx,js,jsx}; the rest are every 7th source file.Cost. Sync wall time, main and this PR, two interleaved rounds:
Fresh-index time is unchanged within noise (vben 11.6 → 11.1 s, excalidraw 11.5 → 11.4 s, zod 8.3 → 8.2 s).
Residues
importstatements, and for fix(sync): imports through a re-exporting module follow it when it is deleted or added #2512's single-level case. Two causes:import { debounce } from '@excalidraw/common'binds throughtsconfigpaths topackages/common/src/index.ts, butisJsPathSpecifierexcludes bare scoped names, so all 381 such module refs fail (TypeScript/pnpm: workspace member imports fail when the entry is declared via package.json "main"/"exports" (e.g. ./src/index.ts) — a same-name private function then hides real callers #2502 is related).isJsPathImportRefcovers onlyJS_MODULE_LANGUAGES(JS, JSX, TS, TSX, ArkTS), so vben'sdropdown-menu.vueimporting'../../ui'fails, as do all 357 relative module refs from.vuefiles there.router/index.tsis deleted, andNoteItem).Overlap
export … fromlines, which now gain an import node, acontainsedge and a module edge each. Whichever of the two lands second regenerates that golden.typemodifiers on imports and re-exports #1955 (open) change the re-export reader for type-only re-exports. This PR readsextractReExportsas it is, and only exportsREEXPORT_MAX_DEPTH.🤖 Generated with Claude Code