fix(resolution): resolve Svelte/Vue component barrels & workspace imports (#629)#657
Merged
Merged
Conversation
…orts (#629) Component barrels (`export { default as X } from './X.svelte'`) and monorepo workspace imports (`@scope/ui/widgets`) left the consumer↔component edge uncreated, so live components showed a false `0 callers` — the canonical dead-code signal — risking deletion of live code. The Svelte default-barrel case broke at FOUR layers, each of which alone left it unresolved: - findExportedSymbol matched only function/class for a default export, never `component` (Svelte/Vue SFCs are kind 'component'). - extractImportMappings had no svelte/vue branch, so SFC consumers produced zero import mappings and resolveViaImport never ran. - EXTENSION_RESOLUTION had no svelte/vue entry, so relative imports from an SFC (`./lib` -> `/index.ts`) resolved to nothing. - getReExports parsed the barrel in the CONSUMER's threaded language, so a .svelte consumer made extractReExports bail on a .ts index barrel. Workspace package-subpath barrels get a new workspace-packages module (mirrors go-module/path-aliases): reads package.json `workspaces` (npm/yarn/bun) + pnpm-workspace.yaml, maps member name->dir, resolves `@scope/ui/widgets` -> `packages/ui/widgets`. Gated behind the workspaces field so single-package repos are unaffected. Bare `./`/`.` directory imports already resolved; covered with a regression test. Verified both directions (callers/impact AND callees) for Svelte; Vue script-level imports also resolve. 4 new tests; full suite green (1126). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
colbymchenry
added a commit
that referenced
this pull request
Jun 3, 2026
…p) (#659) Vue's extractor parsed only the <script> block, so a component used solely in another component's <template> (`<MyButton />`) produced no reference — and thus showed a false 0 callers, even after the barrel-resolution fix in PR #657. This is the Vue analogue of Svelte's extractTemplateComponents. extractTemplateComponents() now scans the template (everything outside the <script>/<style> blocks, which also handles nested <template> tags for v-if/slots) for component tags: - PascalCase tags (`<MyButton/>`) — captured as-is. - kebab-case tags (`<my-button/>`) — converted to PascalCase so they match the imported component's name. Safe: an unmatched name creates no edge during resolution, so native custom elements just don't resolve. - Native HTML elements (lowercase, no hyphen) and Vue built-ins (Transition, KeepAlive, …) are skipped. Adds no nodes — only `references` — so node counts stay stable. With this plus #657, a Vue component re-exported through a barrel and used only in a template now resolves end-to-end (callers/impact/callees). Co-authored-by: Claude Opus 4.8 (1M context) <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.
Closes #629.
Problem
Component barrels —
export { default as Foo } from './Foo.svelte're-exported from anindex.tsand imported elsewhere — left the consumer↔component edge uncreated, so live Svelte/Vue components showed a false0 callers. Because0 callersis the canonical dead-code signal, this risks deleting live code (the reporter only caught it via an external grep). The issue listed three still-missed forms after #130.What the reproduction revealed
The naive fixture passed on
mainbecause the name-matcher masked the bug. Using fixtures where the re-export alias ≠ the component's real name (which is what the reporter's real "0 callers" requires) exposed that the Svelte default-barrel case breaks at four layers — each alone leaves it unresolved:findExportedSymbolmatched onlyfunction/classfor a default export, nevercomponent(SFCs arekind: 'component').extractImportMappingshad nosvelte/vuebranch → SFC consumers produced zero import mappings, soresolveViaImportnever ran.EXTENSION_RESOLUTIONhad nosvelte/vueentry → relative imports from an SFC (./lib→/index.ts) resolved to nothing.getReExportsparsed the barrel in the consumer's threaded language → a.svelteconsumer madeextractReExportsbail on a.tsindex. Now keyed on the barrel's own extension.Coverage of the three forms
export { default as X } from './X.svelte'callers/impactandcallees).//.directory import@scope/ui/widgetsNew
src/resolution/workspace-packages.ts(mirrorsgo-module.ts/path-aliases.ts): readspackage.jsonworkspaces(npm/yarn/bun) +pnpm-workspace.yaml, maps membername→dir, resolves@scope/ui/widgets→packages/ui/widgets. Gated behind theworkspacesfield, so single-package repos getnulland see zero behavior change.Scope boundaries (deliberate)
.vue(script imports resolve — validated), but Vue's extractor doesn't capture<Tag/>template usages as references (Svelte's does). That's a separate pre-existing extraction gap, tracked as follow-up.*globs (packages/*,apps/*) and resolves subpaths by directory; it does not yet read a member'sexportsmap.Validation
re-export chain following: Svelte default barrel, bare./, workspace subpath, Vue SFC<script>import.callers/impact/calleesall show the resolved edge.@scope/externalstays external, no behavior change.🤖 Generated with Claude Code