Repository navigation
fix(js): extract methods on exported objects to resolve CommonJS calls (#3778) - #4121
harshaygadekar wants to merge 1 commit into
Conversation
|
Thanks for the pull request, @harshaygadekar. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Captures function assignments onto members of exported JS objects (e.g. res.format = function… after module.exports = res) as methods, so the owner object gets a node with a contains edge and calls inside the body resolve. _js_find_exported_objects decides what counts as exported by scanning top-level ESM export statements and CommonJS module.exports/exports assignments, including chained assignments and object literals. Assignments to non-exported or non-top-level objects stay uncaptured, as before.
Worth a look
- ESM exported declarations are not detected —
graphify/extractors/engine.py:2759· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1238 functions depend on the 754 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 31 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 19 callers, 7 callees - new:
extract_svelte()— 14 callers, 7 callees - new:
extract_cpp()— 32 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - …and 10 more — each is listed as a finding
Verification — 1238 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit 35adf43, 1 commit(s) behind this PR's base.
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1147 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
29 of 329 test file(s) selected (9%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_dotnet.py— impacttests/test_extract.py— impact, changed-testtests/test_extract_php_closures.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_context_bounds.py— impacttests/test_scala_self_type.py— impacttests/test_scala_top_level_binding.py— impacttests/test_scala_type_definition.py— impacttests/test_svelte_extraction.py— impacttests/test_swift_computed_properties.py— impacttests/test_swift_protocol_requirements.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
· 1 grounded finding(s) anchored inline below; 17 more finding(s) on lines outside this diff (see the check run).
693fe98 to
65f2da5
Compare
|
Landed in v0.9.77 via an authorship-preserving cherry-pick, so your commit is on |
Summary
Fixes #3778.
In Express-style CommonJS architectures (and similar Node.js packages), core methods are assigned to module-level objects that are subsequently exported:
Previously,
_js_extra_walkonly capturedexports.x = fnand<Class>.prototype.x = fn. Top-level member assignments on objects (res.format = fn) were skipped to prevent phantom god-nodes (#1077). Consequently:res.formatnever received a node.function_bodies.walk_callsnever walked the method body.normalizeType(...)) were never extracted, resulting in 0 resolved call edges despite valid destructuredrequirestatements.Fix
_js_find_exported_objects(program_node, source)to identify top-level identifiers exported via CommonJS or ESM (module.exports = res,exports = module.exports = ...,exports.response = res,module.exports = { res }, etc.).kind == "object"in_js_extra_walkwhenowner_nameis exported. We emit the owner node, method node.{member}(),methodedge, and append the method body tofunction_bodiessowalk_callsscans all call expressions inside.const obj = {}; obj.whatever = () => 1;) continue to be skipped, keeping the phantom god-node guard intact.Verification
response.jscallingutils.normalizeType. Confirmed method node and cross-filecallsedge are emitted.test_extract_js_exported_object_member_assignment_and_callsintests/test_extract.py.uv run pytest tests/test_extract.py tests/test_languages.py -k "js").test_extract_js_arbitrary_member_assignment_not_capturedpasses.