mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
fix(js,ts): a call on an element of a class field reaches the element's type
A TypeScript or JavaScript method called on an element of a class field, like this.handlers[type].handle() or this.items[0].save(), kept the bare method name: a member chain rooted at this collapses to it so the resolver can read the owner off the enclosing class. A subscript in the chain leaves the resolver nothing to read, so the bare name exact-matched any project method of that name, often another class's (this.listeners[type].push(h) reached an unrelated UndoStack.push). Both engines now emit this.<field>[k].<m>() as this.<field>[].<m>, and this['svc'].m() as this.svc.m; any other subscript in a this-rooted chain, or under window, emits nothing, as arr[0].run() already did. The resolver reads the element type the field's declaration writes (X[], Array<X>, ReadonlyArray<X>, Record<K, X>), on the calling class or a base, and resolves the method on it or leaves the call unresolved: a type parameter of the declaring class, a union with another type, an array of arrays or a non-plain element hold no one type. The class type-parameter reader now reads from the keyword to the list's own >, over several lines and through braces in constraints. Our changes: kept escapeRegExp from PR #2769 in the conflict; the type-parameter reader also accepts the type keyword, since the T['p'] path passes type-alias owners and the PR as written read State<Item>['current'] as the project class Item (new case in ts-array-annotated-receivers); CHANGELOG covers JavaScript and asks for a re-index. Validation: the element tests fail 10 of 11 on main with both engines; the type-alias case fails on the PR as written and its brace half on main. A/B against main, kernel built per arm: flask and gin byte-identical. TS: pixijs 7 links added, 5 retargeted and 5 typed, all right, 15 removed (9 wrong, 6 right only by name: a cast, a mixin's this, a T[] parameter, a merged mixin member, an index-signature type, an any field); typeorm 3 wrong removed, 1 right lost, 2 retargeted to the declared interface; excalidraw 1 wrong removed; vscode's base and editor 19 added, 24 retargeted and 35 typed, all right, 45 removed (37 wrong, 8 right only by name: a getter, an index-signature field, deep chains, a T[] parameter, a union alias). JS: three.js, playcanvas and pdf.js lose 288 bare-name guesses, about 230 of them wrong (array, Map and DOM calls on project classes). Kernel parity holds on every repo with deferrals unchanged. Index CPU time within noise (pixijs 32.4 s to 33.3 s, three.js 64.7 s to 67.9 s, medians of 3 interleaved). Original PR #2766 by @inth3shadows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Colby Mchenry
co-authored by
Claude Opus 5.5
parent
5c5bfd4051
commit
449eefa7da
@@ -1658,6 +1658,12 @@ impl<'t> Walker<'t> {
|
||||
// resolver can read its declared type (#1496). Mirrors
|
||||
// TreeSitterExtractor.extractCall.
|
||||
callee_name = format!("this.{field}.{method_name}");
|
||||
} else if let Some(element) = receiver.and_then(|r| self.this_subscript_of(r)) {
|
||||
// `this.<field>[k].<method>()` — an element of the
|
||||
// field, typed by the resolver from the field's
|
||||
// declaration; `this['svc']` is the field itself.
|
||||
// Mirrors TreeSitterExtractor.extractCall.
|
||||
callee_name = format!("{element}.{method_name}");
|
||||
} else if let Some(r) = receiver.filter(|r| r.kind() == "call_expression") {
|
||||
// Call receiver — `make().run()` (#1683): keep the inner
|
||||
// callee as `<inner>().<method>`, or emit nothing when it
|
||||
@@ -1739,6 +1745,36 @@ impl<'t> Walker<'t> {
|
||||
Some(self.text(property).to_string())
|
||||
}
|
||||
|
||||
/// A receiver subscripting the enclosing class's object: `this.<field>[k]`
|
||||
/// → `this.<field>[]`, `this['svc']` → `this.svc`; None for any other
|
||||
/// subscript. Mirrors tsThisSubscriptReceiver.
|
||||
fn this_subscript_of(&self, receiver: Node<'t>) -> Option<String> {
|
||||
if receiver.kind() != "subscript_expression" {
|
||||
return None;
|
||||
}
|
||||
let object = peel_receiver(receiver.child_by_field_name("object")?);
|
||||
if object.kind() == "this" {
|
||||
let index = receiver.child_by_field_name("index")?;
|
||||
if index.kind() != "string" {
|
||||
return None;
|
||||
}
|
||||
let text = self.text(index);
|
||||
let quoted = text.len() >= 2
|
||||
&& (text.starts_with('\'') || text.starts_with('"'))
|
||||
&& text.ends_with(&text[..1]);
|
||||
let key = if quoted { &text[1..text.len() - 1] } else { return None };
|
||||
let mut chars = key.chars();
|
||||
let first = chars.next()?;
|
||||
if !(first.is_ascii_alphabetic() || first == '_' || first == '$')
|
||||
|| !chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
return Some(format!("this.{key}"));
|
||||
}
|
||||
self.this_field_of(object).map(|field| format!("this.{field}[]"))
|
||||
}
|
||||
|
||||
/// `await f<T>(x)` parses with `await f` as the callee (#2586), and a
|
||||
/// prefix operator over a generic call the same way
|
||||
/// (`!settings.get<boolean>('X')`, `void apiRequest<T>()`); the call is to
|
||||
@@ -2084,11 +2120,14 @@ fn peel_receiver(node: Node<'_>) -> Node<'_> {
|
||||
}
|
||||
|
||||
/// `this` / `super`, a member chain rooted at either or at `window`, or
|
||||
/// `new C()` still collapse to the bare method name (mirrors
|
||||
/// keepsBareTsJsReceiver).
|
||||
/// `new C()` still collapse to the bare method name; a subscript anywhere in
|
||||
/// the chain does not (mirrors keepsBareTsJsReceiver).
|
||||
fn keeps_bare_receiver(node: Node<'_>, src: &str) -> bool {
|
||||
let mut cur = node;
|
||||
while matches!(cur.kind(), "member_expression" | "subscript_expression") {
|
||||
if cur.kind() == "subscript_expression" {
|
||||
return false;
|
||||
}
|
||||
match cur.child_by_field_name("object") {
|
||||
Some(object) => cur = peel_receiver(object),
|
||||
None => return false,
|
||||
|
||||
Reference in New Issue
Block a user