fix(extractors): align native/WASM definitions order for object-literal methods#1963
Conversation
…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.
Codegraph Impact Analysis6 functions changed → 15 callers affected across 1 files
|
Greptile SummaryFixes the native/WASM
Confidence Score: 4/5Safe 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 No files require special attention beyond the inline duplication noted in Important Files Changed
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]
%%{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]
Reviews (1): Last reviewed commit: "fix(extractors): align native/WASM defin..." | Re-trigger Greptile |
| } 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() | ||
| { |
There was a problem hiding this comment.
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.
| } 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!
Summary
const/let/var obj = { method() {} }, both engines correctly emit a baremethod(kindmethod) definition and a qualifiedobj.method(kindfunction) 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 (nativematch_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 thedefinitionsarray 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.extractObjectLiteralFunctions/extract_object_literal_functions), emitted inline exactly where the object literal's own declaration is processed, and skipping the genericmethod_definitionhandlers (handleMethodCapture/handleMethodDefin TS,handle_method_defin Rust) for exactly these nodes via a new shared predicate (isObjectLiteralDeclaratorMethod/is_object_literal_declarator_method).match_js_objlit_qualified_method_defspass (a second full-treewalk_treecall) is removed entirely. Its responsibilities — including its previouslylet/var-only qualified-pairhandling — are folded intoextract_object_literal_functions, now also called inline from a newlet/varbranch inhandle_var_decl(previously only wired up forconst).static { }block still produces a class-qualified bare entry (ClassName.method, viafindParentClass) rather than a plain bare one, guarded by an explicit enclosing-class check in the new predicate.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 ondefinitionsorder for it under the test's unsorted, order-sensitivetoEqualcomparison.Test plan
cargo test --libincrates/codegraph-core— 522 passed, 0 failed (includes the existingobject_literal_shorthand_method_bare_node_precedes_qualifiedordering test and thelet/varcomputed-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.definitionsarray order for the issue's exact repro, plus alet/varvariant 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