fix(extract): don't bind TS/JS builtin static calls (Date.now(), …) to same-named user symbols (#1726)#1727
Closed
2loch-ness6 wants to merge 1 commit into
Conversation
…r symbols `_resolve_typescript_member_calls` treats a capitalized member-call receiver (`Date.now()`, `Object.keys()`, `Math.max()`) as a type name and matches it case-folded via `_key()`. A lone user symbol whose name case-folds to the same key — e.g. a module-local `const DATE` — then absorbs a `references` edge from every such call across the corpus, manufacturing a false Graphify-Labs#1 god-node (observed: 472 edges, betweenness 0.435, bridging ~130 communities in a 3.4k-file repo). Guard the receiver against `_LANGUAGE_BUILTIN_GLOBALS` before resolving, exactly as the call-edge and raw-call passes already do. Legitimate receiver-typed member calls (constructor-injection type tables, `this.repo.findById()`) are unaffected. Adds a regression test that reproduces the leak without the guard and pins that the resolver's real cross-file resolution still works. Fixes Graphify-Labs#1726 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014zDzgs1rz5aEdJeRJ1yuPx
safishamsi
added a commit
that referenced
this pull request
Jul 8, 2026
Adds a regression test for the Date.now()/static-call shape (credit PR #1727 / @2loch-ness6, who independently found the same fix): a capitalized builtin receiver must not bind cross-file to a same-spelled user const/class. The same guard added in 67f4f83 already covers it (verified); this locks the static-call shape in, while allowing the legitimate same-file const reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
|
Thanks for this — you independently arrived at the exact same fix. This shipped in 0.9.10 (67f4f83, #1726): the identical guard I verified your |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes #1726 — TS/JS static calls on ECMAScript built-in globals (
Date.now(),Object.keys(),Math.max(), …) bind to a same-spelled user symbol, creating a false god-node.Root cause
_resolve_typescript_member_callstreats a capitalized member-call receiver as a type name and matches it through_key(), which case-folds:If the corpus has a lone user symbol whose name case-folds to the same key — e.g. a module-local
const DATE(_key("DATE") == "date") — it is the unique match. Since the const has nonowmethod, the resolver falls torelation = "references"and emits areferences/calledge onto it.Date.now()(and friends) appear in nearly every file, so the one user symbol accumulates hundreds of cross-file edges.Observed on a real 3,368-file repo: a non-exported
const DATEbecame the #1 god-node — 472 edges, betweenness 0.435, bridging ~130 communities. Every source was a method callingnew Date()/Date.now()in unrelated packages.The call-edge pass (
if callee in _LANGUAGE_BUILTIN_GLOBALS: continue) and the raw-call pass already guard against exactly this. The member-call resolver was the one path that didn't.Fix
One guard, mirroring the existing ones — skip the receiver when it is a language builtin:
Dateis already in_LANGUAGE_BUILTIN_GLOBALS(alongsideObject,Array,Math,JSON,Promise, …), so no list changes are needed.Scope / safety
this.repo.findById()→UserRepo.findById) still resolves. Covered by an added test.class Date) matched case-sensitively would have resolved before this branch anyway; a builtin receiver never legitimately targets an unrelated same-folded user symbol.Tests
tests/test_case_sensitive_resolution.py:test_ts_builtin_static_call_does_not_resolve_to_user_symbol— reproduces the leak (fails without the guard, passes with it).test_ts_receiver_typed_member_call_still_resolves— pins that legitimate cross-file member-call resolution is preserved.Full suite green locally (2848 passed; the only failures are pre-existing
tree_sitter_hcl not installedTerraform tests, unrelated to this change).