Repository navigation
fix(sync): an import a barrel passes on under another name relinks when its module gains it - #2665
Merged
Merged
Conversation
…en its module gains it
A sync that gives a module a definition retries the failed references
waiting for the names the module declares, and rebinds the edges into the
names that changed (CG-33). An importer that reaches the module through a
barrel passing it on under another name waits under neither: matx's
`export { default as MatxLoading } from './MatxLoading'` hands
MatxLoading.jsx to `import { MatxLoading } from 'app/components'`, a name
the file never declares. That import and the calls through it stayed
failed, and where a name match had taken the name elsewhere while the
module lacked it (vben's `alert`, which its alert barrel passes on from
`vbenAlert`, onto the `alert.vue` component), the edge stayed there, until
the importer changed or the project was indexed again.
reopenReferencesThroughBarrels now records each importer below a barrel
with the names the barrel passes the changed files on as, and re-opens that
importer's failed references through those names and, for the names the
files never declare, its edges bound before the sync resolved anything, as
the added-file path does. A declared name stays the retry's and the
rebind's, which cap a common one. The per-importer lookup replaces the
repo-wide retry by the passed-on names, whose per-name ceiling skips a name
as common as `alert`; the barrels' module keys are still retried for lazy
routes. passedOn, which the added-file path shares, now also reads an
import a barrel exports again, with the names it exports it as (`import
Card from './Card'; export { Card as AppCard }`), which only the lazy-route
walk followed before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
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.
Summary
codegraph syncand the file watcher now relink an import that reaches a module through a barrel passing it on under another name, once an edit gives the module what is imported. matx'sapp/components/index.jssaysexport { default as MatxLoading } from './MatxLoading', andMatxSuspense.jsximports{ MatxLoading }from that barrel. That was the one edge #2649's sync check left behind (its "Follow-up" section).Before, after an edit gave
MatxLoading.jsxitsexport default function Loading, the import and the calls through it stayed failed. If a name match had taken the name somewhere else while the module lacked it, the edge stayed there instead. In vben,alertstayed on thealert.vuecomponent, though the alert barrel passes it on fromvbenAlert. Only a change to the importer or a fresh index fixed it.Cause
An edit's sync re-opens two things:
An importer below a renamed re-export waits under neither:
MatxLoadingandalertare names the changed files never declare.reopenReferencesThroughBarrelswalked the barrels, but only to retry what waits under their module keys, which is the lazy-route case from #2560. An added file was already covered:reopenReferencesThroughAddedFilesre-opens its importers' bindings through barrels, which is why only edits failed.Fix (
src/resolution/module-importers.ts)reopenReferencesThroughBarrelsrecords each importer below a barrel with the names the barrel passes the changed files on as. It then re-opens:getFailedReferencesThrough);reopenEdgesup toedgesBeforeResolution), as the added-file path does.export { Button } from './Button'doesn't re-open every importer'sButtonedges on each edit.alert. The barrels' module keys are still retried for lazy routes.passedOnis shared with the added-file path. It now also reads an import a barrel exports again, with the names it is exported as (import Card from './Card'; export { Card as AppCard }). Before, only the lazy-route walk followed that form.ImportReader.exportedBindingsbecomesexportedAs, which maps each local to the names it is exported under.Validation
The two arms are engine builds of main
0d33925aand this branch. The change is sync-only, so I checked that the fresh indexes are byte-identical on all 10 repos (scripts/dump-graph.mjs).Sync convergence. For each repo, 20 files were picked from the main arm's fresh index in two ways:
Each pick ran two scenarios: indexed without the files then added back (add), and indexed with a stub in their place then edited back (edit). After
codegraph sync, each graph was compared with its own arm's fresh index. The table counts differing lines in the edges and unresolved-refs sections, main → this branch:¹ What's left on both arms is only heuristic
jsx-render/vue-handleredges from the restored.vuecomponents; see "Not in this PR".Where the edit lines went:
MatxSuspense.jsx'sMatxLoadingimport was failed and is now linked toMatxLoading.jsx'sLoading(fix(js,ts): an import written from a jsconfig or tsconfig baseUrl links the file it names #2649's leftover).alert-builder.test.ts, thealertimport and its 4 calls move from thealert.vuecomponent (exact-match0.5) tovbenAlert(import0.9).confirm's import and call were failed and now linkvbenConfirm.{ RichCodeBlock } from '../blocks', re-exported asdefault as RichCodeBlock, had bound by name to the same function while the module was a stub. Its 6 edges inMessageBubble.tsxandStreamingMessage.tsxare back toimport, as in a fresh index.Cost. I timed the edit scenario's
codegraph syncon both arms, interleaved, usingsync-barrel-modulesfromCODEGRAPH_SYNTH_TIMINGS=1. The barrel phase grows by a few milliseconds, and sync wall time doesn't move beyond run-to-run noise:Excalidraw's ~150 ms is main's existing walk. Its convergence row is unchanged, so its extra re-opened references resolve as they did before. Edges through names the changed files declare stay with CG-33's capped rebind. That keeps a plain
export { Button } from './Button'from re-resolving every importer'sButtonon each edit.Tests. I ran the full suite with
--maxWorkers=4 --minWorkers=1on Windows. On the rebased head7cb48b4b(main90d6501b): 7,042 passed, 407 skipped, and 1 failure. That failure isoversize-file-not-read's "400 MB sparse fixture" check, which hit its 5 s limit under full-suite load and passes alone (2 of 2, under 700 ms). Before the rebase, on0d33925a, it was 7,027 passed, 0 failed.Tests
__tests__/sync-barrel-renamed-reexport.test.tshas a matx-shaped fixture: a barrel withdefault as MatxLoading,Spinner as AppSpinnerand an unrenamedCard. It runs four scenarios, each compared with a fresh index of the same files:MatxLoading,AppSpinnerstay failed)src/legacy/*)Each half of the fix is load-bearing:
The existing barrel and sync suites pass:
sync-nested-barrel-route,sync-module-importers,sync-import-*,sync-route-module-retry,sync-lazy-route-value,sync-jsx-import-forwarding,sync-python-import-binding-retry, 108 tests in all.Not in this PR
.vuefiles, 28-30jsx-render/vue-handleredges from those components are missing. One example isalert.vue's@click="handleCancel"handler in its own script. The loss is identical on main and on this branch, in every scenario, and has nothing to do with barrels. A two-file Vue project converges, so the cause is specific to the larger repo. It's filed as a separate task.import Card from './Card'; export { Card as AppCard }).passedOnnow walks it, but a fresh index doesn't linkimport { AppCard }through it either. That's a resolver gap, which open fix(react): a JSX tag renders what the file imports under that name #2451'sclauseImportscovers. Until then, the retry re-opens those references and they fail again, as in a fresh index..vuefile a barrel passes on under another name (export { default as VbenButton } from './button.vue') seeds nothing. Its component node exists whatever the file holds, so that import binds in both states.🤖 Generated with Claude Code