fix(extractors): extract bare super(...) constructor calls as call edges#2065
Conversation
extractCallInfo (WASM/TS) and extract_call_info (native Rust) had no branch for the super keyword-callee call shape, so a bare super(...) constructor call was silently dropped -- unlike super.method(), which already resolves via the this/super hierarchy dispatch. Any base class reached only through subclass constructors (never directly new'd) was misclassified dead-unresolved. Model super(...) as a constructor call with receiver=super so it flows through the existing resolveThisDispatch machinery unchanged. Adds the matching (call_expression function: (super)) query pattern (mirrored in parser.ts and wasm-worker-entry.ts) so the WASM worker's query path extracts it identically to the walk path, and mirrors the native extractor so both engines produce the same Subclass.constructor -> ParentClass.constructor edge.
Codegraph Impact Analysis16 functions changed → 15 callers affected across 1 files
|
Greptile SummaryThis PR fixes a long-standing missing call-edge for bare
Confidence Score: 4/5The core fix is sound and well-tested across both engines; the only concern is a latent TS/Rust behavioural difference for callback-typed Both engines now correctly emit the constructor call edge; coverage is thorough across unit, parity, and integration tests. The one gap is that the TS/Rust difference in callback-reference-call extraction for src/extractors/javascript.ts — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["call_expression\nfunction: (super)"] --> B{Engine?}
B --> C[TS Walk Path]
B --> D[TS Query Path]
B --> E[Rust]
C --> C3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
D --> D3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
E --> E3["return early ✅\nCallback ref calls skipped"]
C3 --> R["constructor call edge emitted"]
D3 --> R
E3 --> R
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["call_expression\nfunction: (super)"] --> B{Engine?}
B --> C[TS Walk Path]
B --> D[TS Query Path]
B --> E[Rust]
C --> C3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
D --> D3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
E --> E3["return early ✅\nCallback ref calls skipped"]
C3 --> R["constructor call edge emitted"]
D3 --> R
E3 --> R
Reviews (1): Last reviewed commit: "fix(extractors): extract bare super(...)..." | Re-trigger Greptile |
| } else if (c.callsuper_node) { | ||
| // Bare `super(...)` constructor call — see extractCallInfo's 'super' branch. | ||
| const callInfo = extractCallInfo(c.callsuper_fn!, c.callsuper_node); | ||
| if (callInfo) calls.push(callInfo); | ||
| calls.push(...extractCallbackReferenceCalls(c.callsuper_node, callbackParamShapes)); | ||
| } else if (c.assign_node) { |
There was a problem hiding this comment.
TS/Rust divergence:
super() args still pass through extractCallbackReferenceCalls
The Rust path explicitly returns early after pushing the constructor call, skipping extract_callback_reference_calls for super() arguments (with the comment "skipped for the same reason as this(args) above"). In the TS walk path handleCallExpr, there is no early return for fn.type === 'super' — it falls through to line 1714, and now the query path (dispatchQueryMatch) also explicitly calls extractCallbackReferenceCalls here, making both TS paths consistent with each other. However, if a constructor param is function-typed — e.g., super(onInit) where onInit matches a callback-shaped parameter — the TS engine will emit a spurious callback-reference call while the Rust engine won't. The new test covers only non-callback-typed params, so this case goes undetected. Consider either adding an early-return guard in the walk path for fn.type === 'super' (mirroring Rust), or adding a parity fixture that includes a function-typed super() argument to document the intended cross-engine behaviour.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Bare
super(...)constructor calls (distinct fromsuper.method()) were never extracted as call edges by either engine —extractCallInfo(WASM/TS) andextract_call_info(native Rust) had no branch for thesuperkeyword-callee shape, so a class whose constructor is only ever reached viasuper(...)(never directlynew'd) was misclassifieddead-unresolved.src/extractors/javascript.ts):extractCallInfonow returns{ name: 'constructor', receiver: 'super' }forfnType === 'super', reusing the existing this/super hierarchy dispatch (resolveThisDispatchincha.ts) that already resolvessuper.method()— no new resolution path needed. Added the matching(call_expression function: (super) @callsuper_fn) @callsuper_nodequery pattern (mirrored insrc/domain/parser.tsandsrc/domain/wasm-worker-entry.ts) and adispatchQueryMatchbranch, so both the walk path and the query path (used by the WASM worker pool) extract it identically.crates/codegraph-core/src/extractors/javascript.rs): mirroredextract_call_info's new"super"arm, and splithandle_call_expr's combinedthis/superearly-return guard sosuper(...)now routes throughextract_call_infowhile still skipping callback-reference-call extraction on the arguments (unchanged behavior, still guards against the false cross-file edges that guard was added to prevent).Both engines now produce the same edge:
Subclass.constructor -> ParentClass.constructor.Out-of-scope findings filed separately
While adding fixture coverage I found a genuine pre-existing bug in the shared
resolveThisDispatchresolver: when a qualified name likeShape.constructoronly exists in an unrelated file (no same-file candidate), it wrongly returns the cross-file match instead of continuing the walk or giving up — filed as #2062 with a concrete repro (this is why the TypeScript fixture below usesContainer/Boxrather than reusing the existingShapename fromhierarchy.ts). Also filed #2063 for an unrelated pre-existing fixture-documentation gap (missingnew X() -> X.constructorexpected-edges entries from #1892).Test plan
tests/parsers/javascript.test.ts— new describe block covering plain class, class-expression, and callback-reference-arg suppression for baresuper(...)tests/engines/query-walk-parity.test.ts— new case asserting the walk and query extraction paths agree on baresuper(...)tests/integration/phase-8.5-cha-dispatch.test.ts(describe.each(['wasm', 'native'])) — new fixture (Vehicle.ts/Car.ts) and assertion thatCar.constructor -> Vehicle.constructoris emitted on both enginestests/benchmarks/resolution/fixtures/{javascript,typescript}— extendedinheritance.js/super-dispatch.tswith bare-super(...)constructor chains and their expected edges; also credited a correctly-resolvedEllipse.constructor -> Circle.constructoredge in the pre-existinghierarchy.tsfixture that this fix now producescrates/codegraph-core/src/extractors/javascript.rs— newbare_super_call_extracted_as_constructor_callRust unit test; existingsuper_call_args_do_not_emit_callback_reference_callsstill passes unchangednpm run lint— cleannpm run build— cleannapi build --platform --release, codesigned) and verified directly against the fixtureCloses #1929