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 commit is contained in:
co-authored by
Claude Opus 4.8
parent
bdfd55e69c
commit
629d8472b1
@@ -3918,6 +3918,32 @@ export default {
|
||||
expect(calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should extract component usages from the Vue template (PascalCase + kebab, skipping built-ins) (#629)', () => {
|
||||
const code = `<template>
|
||||
<div class="wrap">
|
||||
<UserCard :user="u" />
|
||||
<my-button>Click</my-button>
|
||||
<Transition><span>x</span></Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UserCard from './UserCard.vue';
|
||||
import MyButton from './MyButton.vue';
|
||||
</script>
|
||||
`;
|
||||
const result = extractFromSource('Host.vue', code);
|
||||
const refs = result.unresolvedReferences
|
||||
.filter((r) => r.referenceKind === 'references')
|
||||
.map((r) => r.referenceName);
|
||||
|
||||
expect(refs).toContain('UserCard'); // PascalCase tag
|
||||
expect(refs).toContain('MyButton'); // kebab <my-button> → MyButton
|
||||
expect(refs).not.toContain('Transition'); // Vue built-in skipped
|
||||
expect(refs).not.toContain('Div'); // native HTML element skipped
|
||||
expect(refs).not.toContain('Span');
|
||||
});
|
||||
|
||||
it('should extract from both <script> and <script setup> blocks', () => {
|
||||
const code = `<template>
|
||||
<div>{{ msg }}</div>
|
||||
|
||||
@@ -1389,6 +1389,37 @@ func main() {
|
||||
const callers = cg.getCallers(runNode!.id);
|
||||
expect(callers.some((c) => c.node.filePath === 'src/App.vue')).toBe(true);
|
||||
});
|
||||
|
||||
it('follows a Vue component used in a <template> through a default re-export barrel (#629)', async () => {
|
||||
// End-to-end Vue analogue of the Svelte case: the leaf is a `.vue`
|
||||
// component re-exported under an alias (`Thing`) that differs from its
|
||||
// real name (`Widget`), and the consumer uses it ONLY in markup
|
||||
// (`<Thing />`). Requires both the new template-tag extraction AND the
|
||||
// barrel default-export chase to connect the edge.
|
||||
fs.mkdirSync(path.join(tempDir, 'src/lib'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'src/lib/Widget.vue'),
|
||||
`<script setup lang="ts">\ndefineProps<{ label?: string }>();\n</script>\n<template><button>x</button></template>\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'src/lib/index.ts'),
|
||||
`export { default as Thing } from './Widget.vue';\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'src/App.vue'),
|
||||
`<script setup lang="ts">\nimport { Thing } from './lib';\n</script>\n<template>\n <Thing />\n</template>\n`
|
||||
);
|
||||
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
cg.resolveReferences();
|
||||
|
||||
const widgetNode = cg
|
||||
.getNodesByKind('component')
|
||||
.find((n) => n.name === 'Widget' && n.filePath === 'src/lib/Widget.vue');
|
||||
expect(widgetNode).toBeDefined();
|
||||
const callers = cg.getCallers(widgetNode!.id);
|
||||
expect(callers.some((c) => c.node.filePath === 'src/App.vue')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ Import Resolution', () => {
|
||||
|
||||
Reference in New Issue
Block a user