Skip to content

TS/JS: a call through a chained receiver (chrome.storage.local.get, this.map.get, a.b.text()) reaches the resolver as the bare method name and exact-matches any project symbol with that name #1707

Description

@bompus

What happens

In the TS/JS extractor, a call_expression whose callee is a member_expression keeps the receiver only when the receiver is a plain identifier (recv.method, codegraph-kernel/src/tsjs/extractors.rs extract_call, and the same branch in TreeSitterExtractor.extractCall). Any other receiver, a nested member expression, a subscript, a call, collapses to the bare method name:

source reference recorded
chrome.storage.local.get([key]) get
this._espnPlayerById.get(id) (a Map) get
settled.value.text() (a Response) text
thHeaders[0].text() text
document.querySelector(sel) document.querySelector (identifier receiver, kept, and JS_BUILT_INS drops it)

The bare name then goes through matchByExactName, where a single same-named project node wins at confidence 0.9. Nothing downstream can tell that the receiver was a runtime global or a builtin container, because that information is gone before resolution starts.

Measured

Real TS repo, 584 files, indexed on main + #1706:

  • 119 calls edges named get and 117 named set land on an object-literal storage wrapper's get / set methods. Every one of them is a Map#get, Map#set, chrome.storage.local.get/set or URLSearchParams#get call. Two of them are the wrapper's own methods calling chrome.storage.local.get/set, so the wrapper calls itself.
  • 8 calls edges named querySelector land on an interface member AutopickDoc::querySelector (with fix(extraction): index TypeScript interface members (#1638) #1686's interface members; without it they would land on any function so named). The calls are el.shadowRoot.querySelector(...) and this.root.querySelector(...).
  • 4 calls edges named observe / disconnect from MutationObserver instances land on two helper functions in a test file.

#1691 (#1496) fixes the this.<field>.<method> subset by keeping the field and reading its declared type, and declines when the type is not a project class. The non-this shapes above (chrome.…, x.value.text(), arr[0].text()) are outside its scope and still collapse.

Proposed fix

In the extractor, never drop a chained receiver on the floor. For a nested member_expression / subscript_expression / call_expression receiver, record the chain head plus the method, e.g. chrome.storage.local.getchrome.….get or the full dotted text (the resolver already handles dotted names in matchByQualifiedName / matchMethodCall, and Java, C++ and Go already encode chains). Then:

  1. isBuiltInOrExternal checks the chain head against JS_BUILT_INS, extended with the extension and browser globals that are missing today: chrome, browser, globalThis, navigator, location, history, localStorage, sessionStorage, performance, crypto.
  2. A chained call whose head is a local or a parameter with no inferable type declines, the way matchTsThisFieldCall declines, instead of falling through to bare-name matching. A calls edge that is a guess between Map#get and a project get is worse than no edge: it hides the real callee and inflates every caller list for common method names (get, set, text, push, clear, close).

Happy to open a PR if the direction is right; the head-plus-method encoding is the part I would want a maintainer's view on, since it changes the reference names other resolvers see.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions