Skip to content

feat(resolver): resolve super.method() dispatch via class expression + static block + field def#1399

Merged
carlos-alm merged 7 commits into
mainfrom
feat/super-dispatch-1377
Jun 9, 2026
Merged

feat(resolver): resolve super.method() dispatch via class expression + static block + field def#1399
carlos-alm merged 7 commits into
mainfrom
feat/super-dispatch-1377

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Class expression extends: Add (class name: ...) tree-sitter query patterns for JS/TS so return class Foo extends Bar { ... } records the extends relationship in ctx.classes. Previously only class_declaration was captured, leaving class expressions invisible to resolveThisDispatch.

  • Static block attribution: Add class_static_blockClassName.<static> synthetic method definition (both query path via extractClassMembersWalk and walk path via walkJavaScriptNode). Calls inside static { super.f(); } are now attributed to a method-kind node so the CHA parents map can resolve super.f() to the parent class.

  • Field def callable nodes: Add field_definition/public_field_definitionClassName.fieldName method definition when the field value is an arrow/function expression. static f = () => { ... } becomes a resolvable A.f call target so resolveThisDispatch can emit B.<static> → A.f.

  • Native parity: All three changes are mirrored in the Rust extractor for consistency.

Test plan

  • 6 new unit tests in tests/parsers/javascript.test.ts covering class expression extends, static block definition, and field def callable extraction
  • Jelly micro-test fixtures super, super2, super3, super4, super5 imported as ground-truth benchmarks
  • super fixture recall: 31% → 38% (B.<static> → A.f now resolved)
  • All 97 JS parser unit tests pass
  • All 724 integration tests pass (no regressions)
  • super3 fixture: 100% (unchanged)
  • resolveThisDispatch integration test Lion.speak → Animal.speak still passes

Closes #1377

…+ 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-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the JavaScript/TypeScript extractor to handle three previously-invisible class constructs for super.method() dispatch resolution: named class expressions, static initializer blocks, and callable field definitions. Both the TypeScript and Rust extractors are kept in sync.

  • Class expression extends: The walk dispatcher now handles class (expression) nodes alongside class_declaration, populating ctx.classes with the extends relationship so resolveThisDispatch can resolve super calls inside class expression bodies.
  • Static block uniqueness: Definitions for static { } blocks are renamed from ClassName.<static> to ClassName.<static:L:C> (with line:column suffix) in both extractors, preventing name collisions when a class has multiple static blocks; the Rust kind is also corrected from \"function\" to \"method\" for CHA parity.
  • Callable field guard: handleFieldDef / handle_field_def now only emits a method-kind definition when the field initializer is an arrow/function expression, excluding scalar fields (static x = 42) from appearing as callable nodes.

Confidence Score: 5/5

Safe 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 findParentClass utility already recognised class expression nodes before this PR, and the benchmark fixtures are updated consistently. No silent-drop paths or ambiguous name collisions remain.

No files require special attention.

Important Files Changed

Filename Overview
src/extractors/javascript.ts Walk dispatcher gains case 'class': for class expressions; static blocks renamed to <static:L:C>; field-def guard filters to callable types only; function reordering is cosmetic.
crates/codegraph-core/src/extractors/javascript.rs Rust extractor gains "class" arm, renames static block definitions to <static:L:C>, fixes kind from "function" to "method", and adds callable-field guard — all mirroring the TS side.
tests/parsers/javascript.test.ts Six new / updated unit tests cover class expression extends, static block uniqueness, scalar-field exclusion, and callable-field extraction; existing static block tests updated for new <static:L:C> naming.
tests/benchmarks/resolution/fixtures/jelly-micro/super/expected-edges.json Edge source B.<static> renamed to B.<static:36:2> matching the new unique naming scheme.
tests/benchmarks/resolution/fixtures/jelly-micro/classes/expected-edges.json Two C6.<static> edge sources renamed to C6.<static:75:2> and C6.<static:78:2> for line-specific uniqueness.

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
Loading

Reviews (11): Last reviewed commit: "fix: resolve merge conflicts with main" | Re-trigger Greptile

Comment thread src/extractors/javascript.ts Outdated
Comment on lines +832 to +847
*/
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`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +894 to +896
let name_node = node.child_by_field_name("property")
.or_else(|| node.child_by_field_name("name"))
.or_else(|| find_child(node, "property_identifier"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

6 functions changed18 callers affected across 2 files

  • match_js_node in crates/codegraph-core/src/extractors/javascript.rs:763 (0 transitive callers)
  • handle_static_block in crates/codegraph-core/src/extractors/javascript.rs:873 (1 transitive callers)
  • handle_field_def in crates/codegraph-core/src/extractors/javascript.rs:893 (1 transitive callers)
  • walkJavaScriptNode in src/extractors/javascript.ts:729 (2 transitive callers)
  • handleStaticBlock in src/extractors/javascript.ts:857 (28 transitive callers)
  • handleFieldDef in src/extractors/javascript.ts:885 (28 transitive callers)

…nitions (#1399)

Impact: 2 functions changed, 18 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm

Copy link
Copy Markdown
Contributor Author

Addressed the handleFieldDef callable guard: added a check that the field value node type is in {arrow_function, function_expression, generator_function} before emitting a method-kind definition (both TS extractor and Rust extractor). Scalar fields like static x = 42 are now correctly skipped. Updated the static field test to use a simple function expression instead of a sequence expression. Added a negative assertion test for scalar fields. The two pre-existing native engine failures (prototype-method-resolution.test.ts and this-dispatch-scope.test.ts) are unrelated to this PR's changes — tracked as issue #1413.

carlos-alm added a commit that referenced this pull request Jun 8, 2026
…_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.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed the unreachable duplicate match arms in match_js_node: removed the dead field_definition | public_field_definition and class_static_block arms (lines 786–787) that were shadowed by identical arms added earlier in the same match block (lines 771/773), along with the misleading comment. The Rust compile check passes cleanly.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

…_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.
@carlos-alm carlos-alm force-pushed the feat/super-dispatch-1377 branch from 2ff3a73 to fd59f88 Compare June 8, 2026 18:54
@carlos-alm carlos-alm force-pushed the feat/super-dispatch-1377 branch from fd59f88 to 7c0682e Compare June 8, 2026 19:13
@carlos-alm carlos-alm merged commit fe804b3 into main Jun 9, 2026
27 checks passed
@carlos-alm carlos-alm deleted the feat/super-dispatch-1377 branch June 9, 2026 02:36
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(resolver): resolve super.method() dispatch to parent class (JS)

1 participant