From b115602e7faeb3e4cf03290d90c7ff01c8a4b8f7 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Mon, 28 Sep 2026 22:48:22 -0700 Subject: [PATCH] javascript: a JSX tag bound to a Vue defineComponent renders its setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `` where `const JsVue = defineComponent({ setup() {…} })` (or the module's default export) was an ambiguous_unknown row in a .jsx file: the JavaScript rules followed memo/forwardRef/lazy but not a Vue definer. The tag now renders the options object's setup, its render when there is no setup, or argument 0 in the function form, as the TypeScript engine does. The same object handed to any other call stays unresolved. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../engine/call-edge-generation/jsx.dl | 19 +++++++++++ graph/javascript/souffle/decls_all.dl | 3 ++ .../71-jsx-vue-define-component/src/app.jsx | 34 +++++++++++++++++++ .../71-jsx-vue-define-component/src/def.jsx | 6 ++++ .../expected/71-jsx-vue-define-component.diag | 10 ++++++ .../71-jsx-vue-define-component.edges | 25 ++++++++++++++ .../71-jsx-vue-define-component.oracle | 7 ++++ 7 files changed, 104 insertions(+) create mode 100644 graph/test/javascript/cases/71-jsx-vue-define-component/src/app.jsx create mode 100644 graph/test/javascript/cases/71-jsx-vue-define-component/src/def.jsx create mode 100644 graph/test/javascript/expected/71-jsx-vue-define-component.diag create mode 100644 graph/test/javascript/expected/71-jsx-vue-define-component.edges create mode 100644 graph/test/javascript/expected/71-jsx-vue-define-component.oracle diff --git a/graph/javascript/engine/call-edge-generation/jsx.dl b/graph/javascript/engine/call-edge-generation/jsx.dl index f3c31375a..7359d5189 100644 --- a/graph/javascript/engine/call-edge-generation/jsx.dl +++ b/graph/javascript/engine/call-edge-generation/jsx.dl @@ -59,12 +59,21 @@ jsx_loader_name("lazy"). jsx_loader_name("dynamic"). jsx_loader_name("loadable"). jsx_component_loader(w) :- expr_kind(_, "CALL", _, w), call_site("client", _, n, _, _, _, w, _, _), jsx_loader_name(n). +// A Vue DEFINER — `defineComponent({ setup() {…} })` (Nuxt: `defineNuxtComponent`): the +// tag invokes the options object's `setup` with its props, or its `render` when there is +// no setup; a render function setup returns is reached from setup's body. The function +// form, `defineComponent((props) => () => …)`, is argument 0 itself, as for memo. Named by +// the definer, so the same object handed to any other call stays out. +jsx_vue_definer_name("defineComponent"). +jsx_vue_definer_name("defineNuxtComponent"). +jsx_vue_definer(w) :- expr_kind(_, "CALL", _, w), call_site("client", _, n, _, _, _, w, _, _), jsx_vue_definer_name(n). // jsx_holds(Expr, Call) — the wrapper or loader call an expression holds, followed the // ways a component reaches a tag: the call itself, a const initialised with it, a named // or default import of an export that holds it, and a re-export of one. jsx_holds(w, w) :- jsx_component_wrapper(w). jsx_holds(w, w) :- jsx_component_loader(w). +jsx_holds(w, w) :- jsx_vue_definer(w). jsx_holds(e, w) :- expr_binding(_, v, e), jsx_var_holds(v, w). jsx_var_holds(v, w) :- var_init(_, _, e, v), !var_binding_form(_, "OBJECT_PATTERN", v), !var_binding_form(_, "ARRAY_PATTERN", v), jsx_holds(e, w). @@ -97,6 +106,16 @@ jsx_call_renders(w, k, i) :- jsx_loader_settles(w, "obj", o), prop_value("obj", jsx_call_renders(w, k, i) :- jsx_loader_settles(w, k, i), jsx_component_kind(k). jsx_component_kind("func"). jsx_component_kind("ctor"). +// A Vue definer: the options object's `setup`, else its `render` — one target, not both. +// "Has a setup" is read off the literal's own keys, not the value layer, which this +// relation feeds. +jsx_call_renders(w, "func", m) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "obj", o), + prop_value("obj", o, "setup", "func", m). +jsx_call_renders(w, "func", m) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "obj", o), + !jsx_vue_has_setup(o), prop_value("obj", o, "render", "func", m). +jsx_call_renders(w, "func", f) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "func", f). +jsx_vue_has_setup(o) :- expr_kind(_, "OBJECT_LITERAL", _, o), expr_child(_, o, "PROPERTY_KEY", _, key), expr_name(_, "setup", key). +jsx_vue_has_setup(o) :- literal_owns_method(o, m), method_decl(_, "setup", _, _, _, _, _, m). // ── jsx_renders(Element, Component) — the callables the element runs ─────── // A function component (a constructor FUNCTION is callable_value's "ctor" half). diff --git a/graph/javascript/souffle/decls_all.dl b/graph/javascript/souffle/decls_all.dl index e876f7b24..a126c63c0 100644 --- a/graph/javascript/souffle/decls_all.dl +++ b/graph/javascript/souffle/decls_all.dl @@ -55,6 +55,9 @@ .decl jsx_component_wrapper(c0:symbol) .decl jsx_loader_name(c0:symbol) .decl jsx_component_loader(c0:symbol) +.decl jsx_vue_definer_name(c0:symbol) +.decl jsx_vue_definer(c0:symbol) +.decl jsx_vue_has_setup(c0:symbol) .decl jsx_holds(c0:symbol, c1:symbol) .decl jsx_var_holds(c0:symbol, c1:symbol) .decl jsx_import_holds(c0:symbol, c1:symbol) diff --git a/graph/test/javascript/cases/71-jsx-vue-define-component/src/app.jsx b/graph/test/javascript/cases/71-jsx-vue-define-component/src/app.jsx new file mode 100644 index 000000000..f169d6409 --- /dev/null +++ b/graph/test/javascript/cases/71-jsx-vue-define-component/src/app.jsx @@ -0,0 +1,34 @@ +import { defineComponent } from "vue"; +import { makeStore } from "store-kit"; +import DefVue from "./def"; + +export function leaf() { return 1; } +export function renderLeaf() { return 2; } +export function fnLeaf() { return 3; } +export function bothLeaf() { return 4; } +export function storeLeaf() { return 5; } + +// A tag bound to a Vue definer renders the options object's setup… +export const SetupVue = defineComponent({ setup() { leaf(); return () => "v"; } }); +// …or its render when there is no setup… +export const RenderVue = defineComponent({ render() { renderLeaf(); return "r"; } }); +// …or, in the function form, argument 0 itself. +export const FnVue = defineComponent((props) => { fnLeaf(); return () => "f"; }); +// Setup and render both: setup is what the tag invokes; render is reached from it. +export const BothVue = defineComponent({ setup() { bothLeaf(); return () => "b"; }, render() { return "x"; } }); + +// CONTROL: the same options object handed to a call that is not a definer. +export const Store = makeStore({ setup() { storeLeaf(); return () => "s"; } }); + +export function App() { + return ( +
+ + + + + + +
+ ); +} diff --git a/graph/test/javascript/cases/71-jsx-vue-define-component/src/def.jsx b/graph/test/javascript/cases/71-jsx-vue-define-component/src/def.jsx new file mode 100644 index 000000000..ed821eba8 --- /dev/null +++ b/graph/test/javascript/cases/71-jsx-vue-define-component/src/def.jsx @@ -0,0 +1,6 @@ +import { defineComponent } from "vue"; + +export function defLeaf() { return 6; } + +// The module's default export, rendered through a default import. +export default defineComponent({ setup() { defLeaf(); return () => "d"; } }); diff --git a/graph/test/javascript/expected/71-jsx-vue-define-component.diag b/graph/test/javascript/expected/71-jsx-vue-define-component.diag new file mode 100644 index 000000000..31c98cac0 --- /dev/null +++ b/graph/test/javascript/expected/71-jsx-vue-define-component.diag @@ -0,0 +1,10 @@ +import_cause app.jsx:1:10 vue not_staged +import_cause app.jsx:2:10 store-kit not_staged +import_cause def.jsx:1:10 vue not_staged +package_entry @axiomcode/code-graph . [] MAIN dist/reason.js NOT_STAGED -> - +unresolved app.jsx:12:25 FUNCTION_CALL defineComponent callee_untyped +unresolved app.jsx:14:26 FUNCTION_CALL defineComponent callee_untyped +unresolved app.jsx:16:22 FUNCTION_CALL defineComponent callee_untyped +unresolved app.jsx:18:24 FUNCTION_CALL defineComponent callee_untyped +unresolved app.jsx:21:22 FUNCTION_CALL makeStore callee_untyped +unresolved def.jsx:6:16 FUNCTION_CALL defineComponent callee_untyped diff --git a/graph/test/javascript/expected/71-jsx-vue-define-component.edges b/graph/test/javascript/expected/71-jsx-vue-define-component.edges new file mode 100644 index 000000000..e26d823f1 --- /dev/null +++ b/graph/test/javascript/expected/71-jsx-vue-define-component.edges @@ -0,0 +1,25 @@ +app.jsx:12:25 FUNCTION_CALL defineComponent -> ambiguous_unknown - +app.jsx:12:25 FUNCTION_CALL defineComponent -> callback_registered app.jsx:12:43 setup +app.jsx:12:53 FUNCTION_CALL leaf -> known_edge app.jsx:5:1 leaf +app.jsx:14:26 FUNCTION_CALL defineComponent -> ambiguous_unknown - +app.jsx:14:26 FUNCTION_CALL defineComponent -> callback_registered app.jsx:14:44 render +app.jsx:14:55 FUNCTION_CALL renderLeaf -> known_edge app.jsx:6:1 renderLeaf +app.jsx:16:22 FUNCTION_CALL defineComponent -> ambiguous_unknown - +app.jsx:16:22 FUNCTION_CALL defineComponent -> callback_registered app.jsx:16:38 +app.jsx:16:51 FUNCTION_CALL fnLeaf -> known_edge app.jsx:7:1 fnLeaf +app.jsx:18:24 FUNCTION_CALL defineComponent -> ambiguous_unknown - +app.jsx:18:24 FUNCTION_CALL defineComponent -> callback_registered app.jsx:18:42 setup +app.jsx:18:24 FUNCTION_CALL defineComponent -> callback_registered app.jsx:18:85 render +app.jsx:18:52 FUNCTION_CALL bothLeaf -> known_edge app.jsx:8:1 bothLeaf +app.jsx:21:22 FUNCTION_CALL makeStore -> ambiguous_unknown - +app.jsx:21:22 FUNCTION_CALL makeStore -> callback_registered app.jsx:21:34 setup +app.jsx:21:44 FUNCTION_CALL storeLeaf -> known_edge app.jsx:9:1 storeLeaf +app.jsx:26:7 JSX_ELEMENT -> known_edge app.jsx:12:43 setup +app.jsx:27:7 JSX_ELEMENT -> known_edge app.jsx:14:44 render +app.jsx:28:7 JSX_ELEMENT -> known_edge app.jsx:16:38 +app.jsx:29:7 JSX_ELEMENT -> known_edge app.jsx:18:42 setup +app.jsx:30:7 JSX_ELEMENT -> known_edge def.jsx:6:34 setup +app.jsx:31:7 JSX_ELEMENT -> ambiguous_unknown - +def.jsx:6:16 FUNCTION_CALL defineComponent -> ambiguous_unknown - +def.jsx:6:16 FUNCTION_CALL defineComponent -> callback_registered def.jsx:6:34 setup +def.jsx:6:44 FUNCTION_CALL defLeaf -> known_edge def.jsx:3:1 defLeaf diff --git a/graph/test/javascript/expected/71-jsx-vue-define-component.oracle b/graph/test/javascript/expected/71-jsx-vue-define-component.oracle new file mode 100644 index 000000000..2bfd938ea --- /dev/null +++ b/graph/test/javascript/expected/71-jsx-vue-define-component.oracle @@ -0,0 +1,7 @@ +app.jsx:12:53 FUNCTION_CALL leaf EXACT app.jsx:5:1 +app.jsx:14:55 FUNCTION_CALL renderLeaf EXACT app.jsx:6:1 +app.jsx:16:51 FUNCTION_CALL fnLeaf EXACT app.jsx:7:1 +app.jsx:18:52 FUNCTION_CALL bothLeaf EXACT app.jsx:8:1 +app.jsx:21:44 FUNCTION_CALL storeLeaf EXACT app.jsx:9:1 +def.jsx:6:44 FUNCTION_CALL defLeaf EXACT def.jsx:3:1 +# defects: 0