Skip to content

fix(extractors): align native/WASM definitions order for object-literal methods#1963

Open
carlos-alm wants to merge 1 commit into
fix/issue-1817-unused-source-param-on-get-first-call-arg-infrom
fix/issue-1818-native-wasm-definitions-array-order-diverges
Open

fix(extractors): align native/WASM definitions order for object-literal methods#1963
carlos-alm wants to merge 1 commit into
fix/issue-1817-unused-source-param-on-get-first-call-arg-infrom
fix/issue-1818-native-wasm-definitions-array-order-diverges

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • For const/let/var obj = { method() {} }, both engines correctly emit a bare method (kind method) definition and a qualified obj.method (kind function) definition — but they came from different, independently-timed extraction passes: one inline with the object literal's own declaration, the other from a separate generic (WASM query-match) or deferred (native match_js_objlit_qualified_method_defs, a second full-tree walk run after everything else) pass. That let native and WASM (and even WASM's own query vs. walk paths) disagree on the relative position of the two entries in the definitions array whenever other top-level statements sat between the declaration and end of file — a strict ordered-array comparison would see them diverge even though content was identical.
  • Fixed by consolidating both entries into a single call site on each engine (extractObjectLiteralFunctions / extract_object_literal_functions), emitted inline exactly where the object literal's own declaration is processed, and skipping the generic method_definition handlers (handleMethodCapture/handleMethodDef in TS, handle_method_def in Rust) for exactly these nodes via a new shared predicate (isObjectLiteralDeclaratorMethod / is_object_literal_declarator_method).
  • Native's now-redundant deferred match_js_objlit_qualified_method_defs pass (a second full-tree walk_tree call) is removed entirely. Its responsibilities — including its previously let/var-only qualified-pair handling — are folded into extract_object_literal_functions, now also called inline from a new let/var branch in handle_var_decl (previously only wired up for const).
  • A rarer, unrelated nested edge case is preserved unchanged: an object literal declared inside a class static { } block still produces a class-qualified bare entry (ClassName.method, via findParentClass) rather than a plain bare one, guarded by an explicit enclosing-class check in the new predicate.
  • Added a regression test in tests/engines/parity.test.ts — the object-literal-with-methods shape is now included in the "exported constants of varying initializer shapes" case (previously excluded specifically because of this bug, per that test's own comment) since native and WASM now agree on definitions order for it under the test's unsorted, order-sensitive toEqual comparison.

Test plan

  • cargo test --lib in crates/codegraph-core — 522 passed, 0 failed (includes the existing object_literal_shorthand_method_bare_node_precedes_qualified ordering test and the let/var computed-key parity tests, all still green).
  • npm test — full suite green: 225 test files, 3722 passed, 30 skipped, 2 todo, 0 failed.
  • npm run lint — clean.
  • Rebuilt and codesigned the native addon locally; manually verified via direct engine invocation that native, WASM query path, and WASM walk path now all produce the identical definitions array order for the issue's exact repro, plus a let/var variant and the class-static-block edge case (confirmed content-preserving, order unaffected for that case since it's out of this fix's scope).
  • codegraph diff-impact --staged -T — impact contained to the touched extractor functions (15 transitive callers, 1 file).

Filed #1961 for a separate, narrower, pre-existing ordering gap discovered while testing edge cases (object-literal methods nested inside a class static { } block) — out of scope for this fix.

Fixes #1818

…al methods

For `const`/`let`/`var obj = { method() {} }`, both engines emitted a bare
`method` (kind: method) and a qualified `obj.method` (kind: function)
definition, but from different, independently-timed extraction passes — one
inline with the object's own declaration, the other from a separate
generic/deferred pass. That made native and WASM disagree on the relative
position of the two entries in the `definitions` array whenever other
top-level statements sat between the declaration and end of file.

Consolidate both entries into a single call site on each engine
(extractObjectLiteralFunctions / extract_object_literal_functions), emitted
inline at the object literal's own declaration, and skip the generic
method_definition handlers for exactly these nodes. Native's now-redundant
deferred `match_js_objlit_qualified_method_defs` pass is removed entirely,
folding its responsibilities (including previously let/var-only pair
handling) into the const branch's existing inline call plus a new let/var
branch in handle_var_decl. A class-static-block nested edge case (where the
generic handler legitimately produces a class-qualified name instead of a
bare one) is preserved unchanged via an enclosing-class check.
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

6 functions changed15 callers affected across 1 files

  • handleMethodCapture in src/extractors/javascript.ts:179 (3 transitive callers)
  • isEligibleObjectLiteralDeclarator in src/extractors/javascript.ts:522 (10 transitive callers)
  • isObjectLiteralDeclaratorMethod in src/extractors/javascript.ts:544 (13 transitive callers)
  • buildMethodDefinition in src/extractors/javascript.ts:553 (13 transitive callers)
  • handleMethodDef in src/extractors/javascript.ts:1047 (3 transitive callers)
  • extractObjectLiteralFunctions in src/extractors/javascript.ts:1357 (8 transitive callers)

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes the native/WASM definitions array ordering divergence for object-literal shorthand methods (const/let/var obj = { method() {} }) by consolidating both the bare (method) and qualified (obj.method) definitions into a single inline emission in extractObjectLiteralFunctions/extract_object_literal_functions, and skipping the generic method-definition handlers for those nodes via the new isObjectLiteralDeclaratorMethod/is_object_literal_declarator_method predicate.

  • Removes the deferred Rust second-pass match_js_objlit_qualified_method_defs walker entirely, folding its let/var responsibilities into a new inline handle_var_decl branch (and fixing the let/var qualified-definition gap for that same case in both engines).
  • Adds buildMethodDefinition in TS to deduplicate definition construction across handleMethodCapture, handleMethodDef, and extractObjectLiteralFunctions; regression-guard is strengthened by re-enabling the object-literal-with-methods shape in the cross-engine parity test.

Confidence Score: 4/5

Safe to merge. The fix is well-scoped: both engines now agree on definitions order for the exact shapes covered by the predicate, tests are green, and the one remaining edge case (function-scoped const objects) is a pre-existing out-of-scope gap that the change does not worsen.

The logic is consistent across Rust and TypeScript, the predicate correctly short-circuits on class-enclosed and function-scoped shapes, and the parity test now actively guards the ordering invariant. The one style note — the let/var branch in handle_var_decl inlines the function-scope exclusion list rather than delegating to is_eligible_object_literal_declarator — is minor but worth addressing to keep the two predicates in sync over time.

No files require special attention beyond the inline duplication noted in handle_var_decl.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/javascript.rs Removes the deferred second-pass walker (match_js_objlit_qualified_method_defs), adds is_eligible_object_literal_declarator and is_object_literal_declarator_method predicates, folds bare+qualified method definition emission into extract_object_literal_functions, adds let/var branch to handle_var_decl, and makes handle_method_def skip the covered nodes. Logic is well-scoped and symmetrically mirrors the TS changes.
src/extractors/javascript.ts Adds isEligibleObjectLiteralDeclarator, isObjectLiteralDeclaratorMethod, and buildMethodDefinition helpers; makes handleMethodCapture and handleMethodDef skip object-literal-declarator methods; updates extractObjectLiteralFunctions to emit bare+qualified definitions inline. Refactors are clean and symmetric with the Rust side.
tests/engines/parity.test.ts Adds the object-literal-with-methods shape (previously excluded due to #1818) to the exported-constants parity test case. The toEqual comparison is order-sensitive, giving a concrete regression guard for both engines' definitions array ordering.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[method_definition node encountered] --> B{isObjectLiteralDeclaratorMethod?}
    B -- "Yes\n(top-level / block-scoped declarator,\nno enclosing class)" --> C[handleMethodCapture / handleMethodDef\nreturns early — no emission here]
    B -- "No\n(class method, function-scoped,\nor class static-block shape)" --> D[handleMethodCapture / handleMethodDef\nemits bare/class-qualified definition as before]

    E[extract_object_literal_functions\ncalled inline from handle_var_decl\nfor const AND let/var] --> F{child is method_definition?}
    F -- Yes --> G{isObjectLiteralDeclaratorMethod?}
    G -- "Yes" --> H[Push bare method definition first\nPush qualified obj.method definition second]
    G -- "No" --> I[Push only qualified obj.method definition\ne.g. class static block shape]
    F -- "No (pair with function value)" --> J[Push qualified obj.method definition]

    H --> K[Both entries emitted adjacent,\nnative == WASM definitions order]
Loading
%%{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[method_definition node encountered] --> B{isObjectLiteralDeclaratorMethod?}
    B -- "Yes\n(top-level / block-scoped declarator,\nno enclosing class)" --> C[handleMethodCapture / handleMethodDef\nreturns early — no emission here]
    B -- "No\n(class method, function-scoped,\nor class static-block shape)" --> D[handleMethodCapture / handleMethodDef\nemits bare/class-qualified definition as before]

    E[extract_object_literal_functions\ncalled inline from handle_var_decl\nfor const AND let/var] --> F{child is method_definition?}
    F -- Yes --> G{isObjectLiteralDeclaratorMethod?}
    G -- "Yes" --> H[Push bare method definition first\nPush qualified obj.method definition second]
    G -- "No" --> I[Push only qualified obj.method definition\ne.g. class static block shape]
    F -- "No (pair with function value)" --> J[Push qualified obj.method definition]

    H --> K[Both entries emitted adjacent,\nnative == WASM definitions order]
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(extractors): align native/WASM defin..." | Re-trigger Greptile

Comment on lines +1389 to +1395
} else if !is_const && value_n.kind() == "object" && name_n.kind() == "identifier"
&& find_parent_of_types(node, &[
"function_declaration", "arrow_function",
"function_expression", "method_definition",
"generator_function_declaration", "generator_function",
]).is_none()
{

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 The new let/var branch duplicates the function-scope exclusion list inline instead of delegating to is_eligible_object_literal_declarator. If the exclusion list ever needs to grow (e.g. adding class_static_block), only one of the two sites would be obvious to update. Since the loop variable declarator is in scope at this point, calling the predicate directly would keep both paths in sync and remove the duplication.

Suggested change
} else if !is_const && value_n.kind() == "object" && name_n.kind() == "identifier"
&& find_parent_of_types(node, &[
"function_declaration", "arrow_function",
"function_expression", "method_definition",
"generator_function_declaration", "generator_function",
]).is_none()
{
} else if !is_const && value_n.kind() == "object"
&& is_eligible_object_literal_declarator(&declarator)
{

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!

Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant