diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/components/Child.vue b/graph/test/typescript/cases/81-vue-component-tag/src/components/Child.vue
new file mode 100644
index 000000000..52cc63efd
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/components/Child.vue
@@ -0,0 +1,4 @@
+
+{{ childLocal() }}
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/components/Legacy.vue b/graph/test/typescript/cases/81-vue-component-tag/src/components/Legacy.vue
new file mode 100644
index 000000000..30c1e158b
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/components/Legacy.vue
@@ -0,0 +1,5 @@
+
+legacy
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/components/MyWidget.vue b/graph/test/typescript/cases/81-vue-component-tag/src/components/MyWidget.vue
new file mode 100644
index 000000000..a705d4550
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/components/MyWidget.vue
@@ -0,0 +1,5 @@
+
+{{ widgetLabel() }}
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/components/badge.ts b/graph/test/typescript/cases/81-vue-component-tag/src/components/badge.ts
new file mode 100644
index 000000000..7dba71a89
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/components/badge.ts
@@ -0,0 +1 @@
+export function Badge(): string { return 'b'; }
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/components/store.ts b/graph/test/typescript/cases/81-vue-component-tag/src/components/store.ts
new file mode 100644
index 000000000..c29d0438b
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/components/store.ts
@@ -0,0 +1,2 @@
+function storeInit(): number { return 1; }
+export default { value: storeInit() };
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/main.ts b/graph/test/typescript/cases/81-vue-component-tag/src/main.ts
new file mode 100644
index 000000000..5283f2d18
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/main.ts
@@ -0,0 +1,5 @@
+import Parent from './views/Parent.vue';
+
+export function mount(): unknown {
+ return Parent;
+}
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/shims-vue.d.ts b/graph/test/typescript/cases/81-vue-component-tag/src/shims-vue.d.ts
new file mode 100644
index 000000000..821cca508
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/shims-vue.d.ts
@@ -0,0 +1,4 @@
+declare module '*.vue' {
+ const component: unknown;
+ export default component;
+}
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/tsconfig.json b/graph/test/typescript/cases/81-vue-component-tag/src/tsconfig.json
new file mode 100644
index 000000000..98ff2e003
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noEmit": true
+ },
+ "include": ["**/*.ts", "**/*.vue"]
+}
diff --git a/graph/test/typescript/cases/81-vue-component-tag/src/views/Parent.vue b/graph/test/typescript/cases/81-vue-component-tag/src/views/Parent.vue
new file mode 100644
index 000000000..40e92d4a2
--- /dev/null
+++ b/graph/test/typescript/cases/81-vue-component-tag/src/views/Parent.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ {{ Store }}
+
diff --git a/graph/test/typescript/expected/80-vue-sfc.edges b/graph/test/typescript/expected/80-vue-sfc.edges
index 716281dd0..f0d4508ac 100644
--- a/graph/test/typescript/expected/80-vue-sfc.edges
+++ b/graph/test/typescript/expected/80-vue-sfc.edges
@@ -1,8 +1,8 @@
ambiguous_unknown METHOD_CALL child#format(number) @L10 -> -
known_edge FUNCTION_CALL Comp.vue#() @L10 -> Comp.vue#sfcHelper()
-known_edge FUNCTION_CALL Comp.vue#() @L10 -> child#Child({ msg: string })
known_edge FUNCTION_CALL Comp.vue#() @L11 -> Comp.vue#onSave(number)
known_edge FUNCTION_CALL Comp.vue#() @L11 -> child#format(number)
known_edge FUNCTION_CALL TemplateFirst.vue#() @L3 -> TemplateFirst.vue#shown()
-known_edge FUNCTION_CALL TemplateFirst.vue#() @L3 -> child#MyBadge()
known_edge FUNCTION_CALL TemplateFirst.vue#() @L5 -> TemplateFirst.vue#onInput()
+known_edge JSX_COMPONENT_CALL Comp.vue#() @L10 -> child#Child({ msg: string })
+known_edge JSX_COMPONENT_CALL TemplateFirst.vue#() @L3 -> child#MyBadge()
diff --git a/graph/test/typescript/expected/81-vue-component-tag.edges b/graph/test/typescript/expected/81-vue-component-tag.edges
new file mode 100644
index 000000000..2d88b0898
--- /dev/null
+++ b/graph/test/typescript/expected/81-vue-component-tag.edges
@@ -0,0 +1,10 @@
+ambiguous_unknown FUNCTION_CALL components/MyWidget.vue#() @L2 -> -
+ambiguous_unknown JSX_COMPONENT_CALL views/Parent.vue#() @L13 -> -
+known_edge FUNCTION_CALL components/Child.vue#() @L4 -> components/Child.vue#childLocal()
+known_edge FUNCTION_CALL components/Legacy.vue#data() @L3 -> components/Legacy.vue#legacyInit()
+known_edge FUNCTION_CALL components/MyWidget.vue#() @L5 -> components/MyWidget.vue#widgetLabel()
+known_edge FUNCTION_CALL components/store#() @L2 -> components/store#storeInit()
+known_edge JSX_COMPONENT_CALL views/Parent.vue#() @L10 -> components/MyWidget.vue#()
+known_edge JSX_COMPONENT_CALL views/Parent.vue#() @L11 -> components/Legacy.vue#()
+known_edge JSX_COMPONENT_CALL views/Parent.vue#() @L12 -> components/badge#Badge()
+known_edge JSX_COMPONENT_CALL views/Parent.vue#() @L9 -> components/Child.vue#()
diff --git a/graph/test/typescript/expected/81-vue-component-tag.entries b/graph/test/typescript/expected/81-vue-component-tag.entries
new file mode 100644
index 000000000..44defd181
--- /dev/null
+++ b/graph/test/typescript/expected/81-vue-component-tag.entries
@@ -0,0 +1,4 @@
+── entry_point (3) ──
+ exported_from_entry_module main#mount main.ts:3
+ unimported_module main# main.ts:1
+ unimported_module shims-vue# shims-vue.d.ts:1
diff --git a/graph/test/typescript/expected/81-vue-component-tag.fields b/graph/test/typescript/expected/81-vue-component-tag.fields
new file mode 100644
index 000000000..e69de29bb
diff --git a/graph/test/typescript/expected/81-vue-component-tag.fields-oracle b/graph/test/typescript/expected/81-vue-component-tag.fields-oracle
new file mode 100644
index 000000000..7a65896ba
--- /dev/null
+++ b/graph/test/typescript/expected/81-vue-component-tag.fields-oracle
@@ -0,0 +1,7 @@
+81-vue-component-tag [fields]
+ precision 0.0000 (0 correct, 0 wrong)
+ recall 0.0000 (0 of 0 the compiler resolved)
+ sites 0 resolved 0
+ tiers
+ access
+ not scored: 0 rows whose target is not a client declaration
diff --git a/graph/test/typescript/expected/81-vue-component-tag.oracle b/graph/test/typescript/expected/81-vue-component-tag.oracle
new file mode 100644
index 000000000..c5bc7b402
--- /dev/null
+++ b/graph/test/typescript/expected/81-vue-component-tag.oracle
@@ -0,0 +1,8 @@
+oracle=1 engine=8 agree=1 missing=0 (known 0, NEW 0) extra=7
+ extra components/Child.vue#() -> components/Child.vue#childLocal()
+ extra components/Legacy.vue#data() -> components/Legacy.vue#legacyInit()
+ extra components/MyWidget.vue#() -> components/MyWidget.vue#widgetLabel()
+ extra views/Parent.vue#() -> components/Child.vue#()
+ extra views/Parent.vue#() -> components/Legacy.vue#()
+ extra views/Parent.vue#() -> components/MyWidget.vue#()
+ extra views/Parent.vue#() -> components/badge#Badge()
diff --git a/graph/test/typescript/expected/81-vue-component-tag.type-use b/graph/test/typescript/expected/81-vue-component-tag.type-use
new file mode 100644
index 000000000..e69de29bb
diff --git a/graph/test/typescript/expected/81-vue-component-tag.types-oracle b/graph/test/typescript/expected/81-vue-component-tag.types-oracle
new file mode 100644
index 000000000..cf012188c
--- /dev/null
+++ b/graph/test/typescript/expected/81-vue-component-tag.types-oracle
@@ -0,0 +1,7 @@
+81-vue-component-tag [types]
+ precision 0.0000 (0 correct, 0 wrong)
+ recall 0.0000 (0 of 0 the compiler resolved)
+ sites 0 resolved 0
+ tiers
+ contexts
+ not scored: 0 rows whose target is not a client declaration
diff --git a/graph/typescript/engine/expression-resolution/callee-resolution.dl b/graph/typescript/engine/expression-resolution/callee-resolution.dl
index 1c0385665..db0a0bd17 100644
--- a/graph/typescript/engine/expression-resolution/callee-resolution.dl
+++ b/graph/typescript/engine/expression-resolution/callee-resolution.dl
@@ -706,6 +706,24 @@ expr_call_candidate(ce, m) :- call_site("client", "JSX_COMPONENT_CALL", cn, _, _
import_binding("client", "DEFAULT", cn, _, mod, ih),
import_target_module(ih, "client", mod2),
jsx_default_export_renders(mod2, m).
+// A Vue single-file component, `import Child from "./Child.vue"` rendered as ``
+// (a tag of a `.vue` template, or JSX). Its script is the module's top-level code — a
+// `