feat(resolver): resolve super.method() dispatch via class expression + static block + field def#1399
Conversation
…+ static block + field def
- Add `(class name: ...)` query patterns for JS/TS class expressions so that
`return class Foo extends Bar { ... }` records the extends relationship in
ctx.classes — previously only class_declaration was captured, leaving class
expressions invisible to resolveThisDispatch.
- Add `class_static_block` → `ClassName.<static>` synthetic method definition
in both query path (extractClassMembersWalk) and walk path (walkJavaScriptNode).
Calls inside `static { super.f(); }` blocks are now attributed to a method-kind
node so the CHA parents map can resolve `super.f()` to the parent class.
- Add `field_definition`/`public_field_definition` → `ClassName.fieldName` method
definition when the field value is an arrow function or function expression.
`static f = () => { ... }` becomes a resolvable `A.f` node so
`resolveThisDispatch` can emit the `B.<static> → A.f` edge.
- Mirror all three changes in the native Rust extractor for parity.
- Add 6 parser unit tests and import Jelly micro-test fixtures for super,
super2, super3, super4, super5 as ground-truth benchmarks.
Benchmark result: super fixture 31% → 38% recall (B.<static> → A.f now resolved).
docs check acknowledged
Closes #1377
Greptile SummaryThis PR extends the JavaScript/TypeScript extractor to handle three previously-invisible class constructs for
Confidence Score: 5/5Safe to merge — all three new extraction paths are guarded correctly, both TS and Rust extractors are in sync, the previous review findings are addressed, and all 724 integration tests pass. The changes are well-scoped: each new handler has an early-return guard (missing name, missing value, non-callable type), the No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Tree-sitter AST Node] --> B{Node Kind?}
B -->|class_declaration / abstract_class_declaration| C[handleClassDecl]
B -->|class NEW| C
B -->|class_static_block NEW| D[handleStaticBlock]
B -->|field_definition / public_field_definition| E[handleFieldDef]
B -->|method_definition| F[handleMethodDef]
C --> G{Has name node?}
G -->|No - anonymous expr| H[skip]
G -->|Yes| I[Push class def + extends/implements to ctx.classes]
D --> J[findParentClass]
J --> K[Push ClassName.static:L:C kind=method]
E --> L{Has value node?}
L -->|No| M[skip]
L -->|Yes| N{value.type in CALLABLE_FIELD_TYPES?}
N -->|No - scalar like x=42| O[skip]
N -->|Yes - arrow/fn/generator| P[Push ClassName.fieldName kind=method]
I --> Q[resolveThisDispatch resolves super.method via CHA]
K --> Q
P --> Q
Reviews (11): Last reviewed commit: "fix: resolve merge conflicts with main" | Re-trigger Greptile |
| */ | ||
| function handleStaticBlock(node: TreeSitterNode, definitions: Definition[]): void { | ||
| const parentClass = findParentClass(node); | ||
| if (!parentClass) return; | ||
| definitions.push({ | ||
| name: `${parentClass}.<static>`, | ||
| kind: 'method', | ||
| line: nodeStartLine(node), | ||
| endLine: nodeEndLine(node), | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Extract a class field definition with a function/arrow-function value as a | ||
| * top-level `ClassName.fieldName` method definition so it is a resolvable call | ||
| * target (e.g. `static f = () => { ... }` becomes callable as `A.f`). |
There was a problem hiding this comment.
Duplicate
ClassName.<static> names for multiple static blocks
A class is allowed to have more than one static { ... } block. Each call to handleStaticBlock creates a definition named ClassName.<static>, so a class with two static blocks produces two entries with identical names but different line ranges. If any downstream consumer (e.g. the CHA parent-lookup) resolves by name rather than by line containment, it will see ambiguous entries. The line-range-based resolver works correctly, but a pure name lookup would return both.
There was a problem hiding this comment.
Fixed: renamed static block definitions from ClassName.<static> to ClassName.<static:L:C> (line:column suffix) in both the TypeScript and Rust extractors. Two static blocks in the same class always have different start positions (even if on the same line, their column differs), so names are now guaranteed unique. Updated the affected unit tests and benchmark expected-edges.json fixtures accordingly.
| let name_node = node.child_by_field_name("property") | ||
| .or_else(|| node.child_by_field_name("name")) | ||
| .or_else(|| find_child(node, "property_identifier")); |
There was a problem hiding this comment.
Rust/TypeScript parity gap in field-name fallback
The Rust handle_field_def has a third fallback find_child(node, "property_identifier") that is absent from the TypeScript handleFieldDef, which only tries childForFieldName("property") then childForFieldName("name"). If a grammar version or future node shape exposes the field name as a positional child rather than a named field, Rust will create a definition while TypeScript silently skips it, diverging the two extractors' outputs for the same input.
There was a problem hiding this comment.
Fixed: added find_child(node, "property_identifier") as a third fallback in the TypeScript handleFieldDef to match the Rust handle_field_def fallback chain. Both extractors now try: childForFieldName('name') → childForFieldName('property') → findChild(node, 'property_identifier'). The name-lookup order was also harmonized (both now try 'name' before 'property', matching main's PR #1362).
Merged origin/main (includes PR #1362 class instance method resolution). Resolved conflicts in WASM and Rust extractors: - handleStaticBlock: kept kind='method' for super.method() dispatch - handleFieldDef: merged to accept any field with initializer (broader) - Removed duplicate extractClassMembersWalk and switch case entries - Renamed static block definitions to ClassName.<static:L:C> for uniqueness - Added property_identifier fallback in TS handleFieldDef (Rust/TS parity) - Updated tests and benchmark expected-edges.json for new name format
Codegraph Impact Analysis6 functions changed → 18 callers affected across 2 files
|
…nitions (#1399) Impact: 2 functions changed, 18 affected
|
Addressed the handleFieldDef callable guard: added a check that the field value node type is in |
…_js_node (#1399) The PR had moved class_static_block and field_definition handling earlier in the match block (lines 771/773) but accidentally left identical arms below (lines 786/787) with a misleading comment. The duplicate arms were unreachable dead code and would trigger Rust unreachable_patterns warnings.
|
Fixed the unreachable duplicate match arms in |
…_js_node (#1399) The PR moved class_static_block and field_definition earlier in the match block (lines 771/773) but left identical arms below (lines 786/787) as dead code. Removing them eliminates Rust unreachable_patterns warnings.
2ff3a73 to
fd59f88
Compare
fd59f88 to
7c0682e
Compare
docs check acknowledged
Summary
Class expression extends: Add
(class name: ...)tree-sitter query patterns for JS/TS soreturn class Foo extends Bar { ... }records theextendsrelationship inctx.classes. Previously onlyclass_declarationwas captured, leaving class expressions invisible toresolveThisDispatch.Static block attribution: Add
class_static_block→ClassName.<static>synthetic method definition (both query path viaextractClassMembersWalkand walk path viawalkJavaScriptNode). Calls insidestatic { super.f(); }are now attributed to a method-kind node so the CHA parents map can resolvesuper.f()to the parent class.Field def callable nodes: Add
field_definition/public_field_definition→ClassName.fieldNamemethod definition when the field value is an arrow/function expression.static f = () => { ... }becomes a resolvableA.fcall target soresolveThisDispatchcan emitB.<static> → A.f.Native parity: All three changes are mirrored in the Rust extractor for consistency.
Test plan
tests/parsers/javascript.test.tscovering class expression extends, static block definition, and field def callable extractionsuper,super2,super3,super4,super5imported as ground-truth benchmarkssuperfixture recall: 31% → 38% (B.<static> → A.fnow resolved)super3fixture: 100% (unchanged)resolveThisDispatchintegration testLion.speak → Animal.speakstill passesCloses #1377