diff --git a/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/app.tsx b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/app.tsx
new file mode 100644
index 000000000..30e5eab06
--- /dev/null
+++ b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/app.tsx
@@ -0,0 +1,29 @@
+import { lazy, memo, pickOther, MemoFwd, NestedPickedInner } from "./parts";
+import DefMemo from "./defmemo";
+import DefPicked from "./defpicked";
+
+// The usual way to lazy-load a named export: a concise arrow returning `{ default }`.
+const LazyNamedObj = lazy(() => import("./objparts").then((m) => ({ default: m.ObjNamed })));
+// CONTROL: the loader returning the module still renders its default export.
+const LazyDefault = lazy(() => import("./objparts"));
+// CONTROL: a non-wrapper between two wrappers stops the unwrapping.
+const MemoPicked = memo(pickOther(NestedPickedInner));
+
+export function App(): string {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+// A concise arrow body is what the arrow returns, for an ordinary function too.
+export const makeName = (n: number) => String(n).padStart(3);
+export function useName(): number {
+ return makeName(4).length;
+}
diff --git a/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defmemo.tsx b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defmemo.tsx
new file mode 100644
index 000000000..7f4ae6df4
--- /dev/null
+++ b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defmemo.tsx
@@ -0,0 +1,4 @@
+import { memo, DefaultMemoInner } from "./parts";
+
+// A wrapper call as the module's default export: the component is DefaultMemoInner.
+export default memo(DefaultMemoInner);
diff --git a/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defpicked.tsx b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defpicked.tsx
new file mode 100644
index 000000000..8ba6fddc7
--- /dev/null
+++ b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/defpicked.tsx
@@ -0,0 +1,4 @@
+import { pickOther, PickedInner } from "./parts";
+
+// CONTROL: a default-exported call that is NOT a wrapper renders what it returns.
+export default pickOther(PickedInner);
diff --git a/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/objparts.tsx b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/objparts.tsx
new file mode 100644
index 000000000..3372d6e92
--- /dev/null
+++ b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/objparts.tsx
@@ -0,0 +1,12 @@
+export function leafC(): number {
+ return 3;
+}
+export function ObjNamed(): string {
+ return String(leafC());
+}
+export function ConciseNamed(): string {
+ return "concise";
+}
+export default function ObjDefault(): string {
+ return "default";
+}
diff --git a/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/parts.tsx b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/parts.tsx
new file mode 100644
index 000000000..8f1aee8a1
--- /dev/null
+++ b/graph/test/typescript/cases/76-jsx-default-nested-wrappers/src/parts.tsx
@@ -0,0 +1,41 @@
+// Wrappers and loaders, declared with the signatures that matter. No React dependency.
+declare global {
+ namespace JSX {
+ type Element = string;
+ interface ElementAttributesProperty {
+ props: unknown;
+ }
+ interface IntrinsicElements {
+ [name: string]: unknown;
+ }
+ }
+}
+
+export type Fc
= (props: P) => string;
+export declare function memo
(component: Fc
): Fc
;
+export declare function forwardRef
(render: (props: P, ref: unknown) => string): Fc
;
+export declare function lazy(load: () => Promise<{ default: Fc }>): Fc;
+
+// NOT a component wrapper: what it returns is not argument 0.
+export function pickOther(c: Fc<{}>): Fc<{}> {
+ return () => String(c === undefined);
+}
+
+export function leafA(): number {
+ return 1;
+}
+export function leafB(): number {
+ return 2;
+}
+export function DefaultMemoInner(): string {
+ return String(leafA());
+}
+export function PickedInner(): string {
+ return "picked";
+}
+export function NestedPickedInner(): string {
+ return "nested-picked";
+}
+export const MemoFwd = memo(forwardRef(function MemoFwdInner(p: {}, ref: unknown): string {
+ return String(leafB());
+}));
diff --git a/graph/test/typescript/expected/71-returned-function-calls.fields b/graph/test/typescript/expected/71-returned-function-calls.fields
index 2429ee32c..c15413c8d 100644
--- a/graph/test/typescript/expected/71-returned-function-calls.fields
+++ b/graph/test/typescript/expected/71-returned-function-calls.fields
@@ -1 +1 @@
-known_edge read make#() -> Monoid#empty
+known_edge read make#compact(Monoid) -> Monoid#empty
diff --git a/graph/test/typescript/expected/71-returned-function-calls.fields-oracle b/graph/test/typescript/expected/71-returned-function-calls.fields-oracle
index fc2dfe09f..70bb51497 100644
--- a/graph/test/typescript/expected/71-returned-function-calls.fields-oracle
+++ b/graph/test/typescript/expected/71-returned-function-calls.fields-oracle
@@ -1,9 +1,7 @@
71-returned-function-calls [fields]
- precision 0.0000 (0 correct, 1 wrong)
- recall 0.0000 (0 of 1 the compiler resolved)
+ precision 1.0000 (1 correct, 0 wrong)
+ recall 1.0000 (1 of 1 the compiler resolved)
sites 1 resolved 1 (100.0%)
tiers known_edge=1
access read=1
not scored: 0 rows whose target is not a client declaration
- WRONG make#() READ Monoid#empty
- MISSING make#compact(Monoid) READ Monoid#empty
diff --git a/graph/test/typescript/expected/73-assigned-interface-members.fields b/graph/test/typescript/expected/73-assigned-interface-members.fields
index 31d3bf758..244bb8e43 100644
--- a/graph/test/typescript/expected/73-assigned-interface-members.fields
+++ b/graph/test/typescript/expected/73-assigned-interface-members.fields
@@ -1,3 +1,4 @@
-known_edge read schema#() -> Schema#_i
+known_edge read schema#(?) -> Schema#_i
known_edge read schema#init(Schema,boolean) -> Schema#_i
+known_edge read schema#parse(?,?) -> Schema#_i
known_edge read schema#parseTyped(Schema,number) -> Schema#_i
diff --git a/graph/test/typescript/expected/73-assigned-interface-members.fields-oracle b/graph/test/typescript/expected/73-assigned-interface-members.fields-oracle
index b84fb0bb5..aeb312580 100644
--- a/graph/test/typescript/expected/73-assigned-interface-members.fields-oracle
+++ b/graph/test/typescript/expected/73-assigned-interface-members.fields-oracle
@@ -1,10 +1,7 @@
73-assigned-interface-members [fields]
- precision 0.6667 (2 correct, 1 wrong)
- recall 0.5000 (2 of 4 the compiler resolved)
+ precision 1.0000 (4 correct, 0 wrong)
+ recall 1.0000 (4 of 4 the compiler resolved)
sites 7 resolved 7 (100.0%)
tiers known_edge=7
access read=7
not scored: 0 rows whose target is not a client declaration
- WRONG schema#() READ Schema#_i
- MISSING schema#(?) READ Schema#_i
- MISSING schema#parse(?,?) READ Schema#_i
diff --git a/graph/test/typescript/expected/74-jsx-component-forms.fields b/graph/test/typescript/expected/74-jsx-component-forms.fields
index e5353c7e7..1aa6da987 100644
--- a/graph/test/typescript/expected/74-jsx-component-forms.fields
+++ b/graph/test/typescript/expected/74-jsx-component-forms.fields
@@ -1,4 +1,4 @@
-ambiguous_unknown read parts#() -> -
+ambiguous_unknown read parts#Badge(?) -> -
known_edge read Panel#render() -> Panel#props
known_edge read Panel#render() -> { title: string }#title
known_edge read parts#Avatar({ src: string }) -> { src: string }#src
diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges
index 4fde5b860..68ca2dc39 100644
--- a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges
+++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges
@@ -33,8 +33,8 @@ known_edge FUNCTION_CALL heavy#Heavy() @L11 -> heavy#heavyWork()
known_edge FUNCTION_CALL heavy#NamedHeavy() @L15 -> heavy#namedWork()
known_edge FUNCTION_CALL parts#makeThing(() =) @L23 -> parts#()
known_edge FUNCTION_CALL parts#useMemoLike(() =,unknown[]) @L27 -> parts#()
-known_edge JSX_COMPONENT_CALL app#Page() @L35 -> parts#(T)
-known_edge JSX_COMPONENT_CALL app#Page() @L36 -> parts#(T)
+known_edge JSX_COMPONENT_CALL app#Page() @L35 -> heavy#NamedHeavy()
+known_edge JSX_COMPONENT_CALL app#Page() @L36 -> heavy#NamedHeavy()
known_edge JSX_COMPONENT_CALL app#Page() @L37 -> heavy#NamedHeavy()
known_edge JSX_COMPONENT_CALL app#Page() @L38 -> heavy#NamedHeavy()
known_edge JSX_COMPONENT_CALL app#Page() @L39 -> heavy#Heavy()
diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields
index 886e2e7cc..3da15a756 100644
--- a/graph/test/typescript/expected/75-jsx-wrapper-guards.fields
+++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.fields
@@ -1,4 +1,5 @@
+ambiguous_unknown read app#(?) -> -
+ambiguous_unknown read app#(?) -> -
ambiguous_unknown read app#(?) -> -
ambiguous_unknown read app#() -> -
-ambiguous_unknown read app#() -> -
known_edge read ui/Button#Button({ label: string }) -> { label: string }#label
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.edges b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.edges
new file mode 100644
index 000000000..5b6145e44
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.edges
@@ -0,0 +1,28 @@
+ambient_terminal FUNCTION_CALL app#() @L10 -> parts#memo(Fc)
+ambient_terminal FUNCTION_CALL app#() @L6 -> parts#lazy(() ={ default: Fc })
+ambient_terminal FUNCTION_CALL app#() @L8 -> parts#lazy(() ={ default: Fc })
+ambient_terminal FUNCTION_CALL defmemo#() @L4 -> parts#memo(Fc)
+ambient_terminal FUNCTION_CALL parts#() @L39 -> parts#forwardRef((props: P, ref: unknown) =)
+ambient_terminal FUNCTION_CALL parts#() @L39 -> parts#memo(Fc)
+ambiguous_unknown FUNCTION_CALL app#makeName(number) @L26 -> -
+ambiguous_unknown FUNCTION_CALL objparts#ObjNamed() @L5 -> -
+ambiguous_unknown FUNCTION_CALL parts#() @L21 -> -
+ambiguous_unknown FUNCTION_CALL parts#DefaultMemoInner() @L31 -> -
+ambiguous_unknown FUNCTION_CALL parts#MemoFwdInner({},unknown) @L40 -> -
+ambiguous_unknown JSX_COMPONENT_CALL app#App() @L19 -> -
+ambiguous_unknown METHOD_CALL app#() @L6 -> -
+ambiguous_unknown METHOD_CALL app#makeName(number) @L26 -> -
+intrinsic_terminal DYNAMIC_IMPORT_CALL app#() @L6 -> -
+intrinsic_terminal DYNAMIC_IMPORT_CALL app#() @L8 -> -
+known_edge FUNCTION_CALL app#() @L10 -> parts#pickOther(Fc)
+known_edge FUNCTION_CALL app#useName() @L28 -> app#makeName(number)
+known_edge FUNCTION_CALL defpicked#() @L4 -> parts#pickOther(Fc)
+known_edge FUNCTION_CALL objparts#ObjNamed() @L5 -> objparts#leafC()
+known_edge FUNCTION_CALL parts#DefaultMemoInner() @L31 -> parts#leafA()
+known_edge FUNCTION_CALL parts#MemoFwdInner({},unknown) @L40 -> parts#leafB()
+known_edge JSX_COMPONENT_CALL app#App() @L15 -> parts#DefaultMemoInner()
+known_edge JSX_COMPONENT_CALL app#App() @L17 -> objparts#ObjNamed()
+known_edge JSX_COMPONENT_CALL app#App() @L18 -> objparts#ObjDefault()
+known_edge JSX_COMPONENT_CALL app#App() @L20 -> parts#(T)
+multi_inferred JSX_COMPONENT_CALL app#App() @L16 -> parts#(T)
+multi_inferred JSX_COMPONENT_CALL app#App() @L16 -> parts#MemoFwdInner({},unknown)
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.entries b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.entries
new file mode 100644
index 000000000..72ac2e52f
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.entries
@@ -0,0 +1,4 @@
+── entry_point (3) ──
+ exported_from_entry_module app#App app.tsx:12
+ exported_from_entry_module app#useName app.tsx:27
+ unimported_module app# app.tsx:1
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.envelope b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.envelope
new file mode 100644
index 000000000..539de9007
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.envelope
@@ -0,0 +1,7 @@
+value parts#@14:21 -> parts#
+value parts#@14:21 -> parts#DefaultMemoInner
+value parts#@14:21 -> parts#NestedPickedInner
+value parts#@14:21 -> parts#PickedInner
+value parts#@16:47 -> parts#MemoFwdInner
+value parts#@17:36 -> app#@6
+value parts#@17:36 -> app#@8
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields
new file mode 100644
index 000000000..ce8e43439
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields
@@ -0,0 +1,2 @@
+ambiguous_unknown read app#(?) -> -
+ambiguous_unknown read app#useName() -> -
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle
new file mode 100644
index 000000000..c4ca47b65
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.fields-oracle
@@ -0,0 +1,7 @@
+76-jsx-default-nested-wrappers [fields]
+ precision 0.0000 (0 correct, 0 wrong)
+ recall 0.0000 (0 of 0 the compiler resolved)
+ sites 2 resolved 0 (0.0%)
+ tiers ambiguous_unknown=2
+ access read=2
+ not scored: 2 rows whose target is not a client declaration
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.oracle b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.oracle
new file mode 100644
index 000000000..e7cd4e634
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.oracle
@@ -0,0 +1,5 @@
+oracle=12 engine=16 agree=12 missing=0 (known 0, NEW 0) extra=4
+ extra app#App() -> objparts#ObjDefault()
+ extra app#App() -> objparts#ObjNamed()
+ extra app#App() -> parts#DefaultMemoInner()
+ extra app#App() -> parts#MemoFwdInner({},unknown)
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.type-use b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.type-use
new file mode 100644
index 000000000..635d5b6b5
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.type-use
@@ -0,0 +1,4 @@
+ambiguous_unknown METHOD_RETURN 1 parts [METHOD_PARAM] -> -
+known_edge FIELD_TYPE 3 parts [METHOD_PARAM] -> Fc
+known_edge METHOD_PARAM 0 parts [METHOD_PARAM] -> Fc
+known_edge METHOD_RETURN 0 parts [METHOD] -> Fc
diff --git a/graph/test/typescript/expected/76-jsx-default-nested-wrappers.types-oracle b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.types-oracle
new file mode 100644
index 000000000..daa02c928
--- /dev/null
+++ b/graph/test/typescript/expected/76-jsx-default-nested-wrappers.types-oracle
@@ -0,0 +1,7 @@
+76-jsx-default-nested-wrappers [types]
+ precision 1.0000 (1 correct, 0 wrong)
+ recall 1.0000 (1 of 1 the compiler resolved)
+ sites 8 resolved 7 (87.5%)
+ tiers ambiguous_unknown=1 known_edge=7
+ contexts FIELD_TYPE=1 METHOD_PARAM=2 METHOD_RETURN=5
+ not scored: 1 rows whose target is not a client declaration
diff --git a/graph/typescript/PARSER-DEFECTS.md b/graph/typescript/PARSER-DEFECTS.md
index cee58b565..95f3d209f 100644
--- a/graph/typescript/PARSER-DEFECTS.md
+++ b/graph/typescript/PARSER-DEFECTS.md
@@ -19,7 +19,8 @@ day the defect is fixed.
**Status: fixed.** A component element emits a JSX_ELEMENT / JSX_SELF_CLOSING row and a
JSX_COMPONENT_CALL site; `callee-resolution.dl` rule (13) resolves it, and also follows a
wrapper's argument 0 (`memo`, `forwardRef`, `observer`, `withX`) and what a `lazy` /
-`dynamic` loader settles to. Pinned by cases 10, 74 and 75. The measurement below is kept as the record of the defect.
+`dynamic` loader settles to, through nested wrappers and a default-exported wrapper
+call. Pinned by cases 10, 74, 75 and 76. The measurement below is kept as the record of the defect.
**Severity: blocking for any React / Preact / Solid codebase.**
diff --git a/graph/typescript/README.md b/graph/typescript/README.md
index aad0c5a04..3d9fb4ea0 100644
--- a/graph/typescript/README.md
+++ b/graph/typescript/README.md
@@ -138,11 +138,13 @@ Stated because a graph you cannot trust the boundaries of is not useful.
* **A JSX member tag is only as good as its receiver.** `` is a
`JSX_COMPONENT_CALL` resolved by tag name, including through the component wrappers
- (`memo`, `forwardRef`, `observer`, a `withX` HOC) and a `lazy` / `dynamic` loader;
+ (`memo`, `forwardRef`, `observer`, a `withX` HOC), wrappers nested in one another
+ (`memo(forwardRef(Inner))`), a wrapper call that is the module's default export
+ (`export default memo(Inner)` rendered through a default import), and a `lazy` /
+ `dynamic` loader, whose `.then` callback may be a concise arrow;
`` is a `METHOD_CALL` and resolves only where `ui.Card(...)` would. ``
produces no site. A tag bound by a destructured prop (`({ icon: Icon }) => `)
- is the prop, not a same-named import, and stays unresolved; so does a loader whose
- `.then` projects a named export through a concise arrow body.
+ is the prop, not a same-named import, and stays unresolved.
* **Generic inference through a callback** — `map(f: (t: T) => U): U[]` — is not
substituted. `U` is bound by the argument's return type, which is inference rather
than substitution, and guessing it would fabricate. Those sites stay unresolved and
diff --git a/graph/typescript/engine/expression-resolution/callee-resolution.dl b/graph/typescript/engine/expression-resolution/callee-resolution.dl
index 818f326ff..5d6506f0f 100644
--- a/graph/typescript/engine/expression-resolution/callee-resolution.dl
+++ b/graph/typescript/engine/expression-resolution/callee-resolution.dl
@@ -564,9 +564,28 @@ jsx_component_wrapper(w) :- call_site("client", _, n, _, _, w, _),
jsx_component_wrapper(w) :- call_site("client", _, n, _, _, w, _),
match("with[A-Z].*", n).
jsx_wrapped_component(v, m) :- var_initializer("client", _, w, v),
- jsx_component_wrapper(w),
+ jsx_wrapper_renders(w, m).
+// What a wrapper call renders: its argument 0, or — when that argument is itself a
+// wrapper call, `memo(forwardRef(function Inner() {…}))` — what the inner one renders.
+jsx_wrapper_renders(w, m) :- jsx_component_wrapper(w),
expr_child("client", w, "ARGUMENT", "0", a),
expr_callable(a, m).
+jsx_wrapper_renders(w, m) :- jsx_component_wrapper(w),
+ expr_child("client", w, "ARGUMENT", "0", a),
+ jsx_component_wrapper(a),
+ jsx_wrapper_renders(a, m).
+// A wrapper call that IS the module's default export, `export default memo(Card)`,
+// binds no variable: the tag reaches it through a default import of that module.
+jsx_default_export_renders(mod, m) :- export_decl("client", "default", _, "DEFAULT_EXPRESSION", mod, h),
+ export_expression("client", w, h),
+ jsx_wrapper_renders(w, m).
+expr_call_candidate(ce, m) :- call_site("client", "JSX_COMPONENT_CALL", cn, _, _, ce, cs),
+ cn != "",
+ call_module(cs, mod),
+ !callee_names_local(ce),
+ import_binding("client", "DEFAULT", cn, _, mod, ih),
+ import_target_module(ih, "client", mod2),
+ jsx_default_export_renders(mod2, m).
// A LOADER — `lazy(() => import("./Settings"))`, Next.js `dynamic(...)`, `loadable(...)`.
// Argument 0 is not the component: it returns a module, or a promise of one, and what
// renders is the component that settles out of it.
diff --git a/graph/typescript/engine/resolution/reference-types.dl b/graph/typescript/engine/resolution/reference-types.dl
index 461a0255b..be12fdfe0 100644
--- a/graph/typescript/engine/resolution/reference-types.dl
+++ b/graph/typescript/engine/resolution/reference-types.dl
@@ -382,6 +382,11 @@ method_returns_expr(m, e) :- expr_root_context(prov, rc, e),
return_context(rc),
expr_owner(prov, "BLOCK", b, _, e),
block_owner(prov, m, _, b).
+// …and a concise arrow body, which has no block: the parser gives it to the arrow's
+// own method row, so `(m) => m.Named` and `(m) => ({ default: m.Named })` return
+// their body just as the `{ return …; }` spelling does.
+method_returns_expr(m, e) :- expr_root_context(prov, "ARROW_BODY_EXPRESSION", e),
+ expr_owner(prov, "METHOD", m, _, e).
// Guarded so a declaration always wins: an annotation is what the function PROMISES,
// and inferring past it would let one branch of a widened return narrow the answer.
method_declares_return(m) :- method_return_ref(_, _, m).
diff --git a/graph/typescript/souffle/decls_all.dl b/graph/typescript/souffle/decls_all.dl
index 5a3bdeae9..3e7902533 100644
--- a/graph/typescript/souffle/decls_all.dl
+++ b/graph/typescript/souffle/decls_all.dl
@@ -261,11 +261,13 @@
.decl jdk_max_depth(c0:number)
.decl jsx_intrinsic_site(c0:symbol)
.decl jsx_component_wrapper(c0:symbol)
+.decl jsx_default_export_renders(c0:symbol,c1:symbol)
.decl jsx_loader_name(c0:symbol)
.decl jsx_loader_returns(c0:symbol,c1:symbol)
.decl jsx_loader_value(c0:symbol,c1:symbol)
.decl jsx_wrapped_component(c0:symbol,c1:symbol)
.decl jsx_wrapper_name(c0:symbol)
+.decl jsx_wrapper_renders(c0:symbol,c1:symbol)
.decl lib_boundary_reason(c0:symbol,c1:symbol)
.decl lib_capped()
.decl lib_export_relative_base(c0:symbol,c1:symbol)
diff --git a/parser/src/parsers/typescript/extractors/ts-expression-walker.ts b/parser/src/parsers/typescript/extractors/ts-expression-walker.ts
index ae6710e7b..1b82d1018 100644
--- a/parser/src/parsers/typescript/extractors/ts-expression-walker.ts
+++ b/parser/src/parsers/typescript/extractors/ts-expression-walker.ts
@@ -133,6 +133,15 @@ export class TsExpressionWalker {
ownerKind = TsExpressionOwnerKind.ENUM_MEMBER;
} else if (ts.isExportAssignment(current)) {
ownerKind = TsExpressionOwnerKind.EXPORT;
+ } else if (ts.isArrowFunction(current) && !ts.isBlock(current.body)) {
+ // A concise arrow body has no block row: the arrow's method row owns it,
+ // so `(m) => m.Named` is what the arrow returns, not part of the variable
+ // the arrow happens to be assigned to.
+ const hash = this.options.methodHashByNode.get(id);
+ if (hash) {
+ ownerHash = hash;
+ ownerKind = TsExpressionOwnerKind.METHOD;
+ }
} else {
const blockHash = this.options.blockHashByNode.get(id);
if (blockHash) {