fix(extraction): index TypeScript interface members (#1638) (#1780)

Land upstream #1686 (maxmilian + bompus kernel/CG-28 follow-ups)
onto current main. tree-sitter-typescript interface members
(method_signature / property_signature) were never listed in the
TS extractor, so platform .d.ts APIs had no declaration nodes for
call edges. Mirrors on the Rust kernel path; keeps CG-28 damping
for pure-interface declaration files; filters damped files from
the explore RWR seed set.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 11:27:05 -05:00
committed by GitHub
co-authored by Colby McHenry
parent 8c047342cd
commit ee83636acb
10 changed files with 375 additions and 49 deletions
+42 -8
View File
@@ -55,10 +55,32 @@ impl Variant {
/// typescriptExtractor.methodTypes / javascriptExtractor.methodTypes.
fn is_method_type(v: Variant, kind: &str) -> bool {
kind == "method_definition"
|| (v.is_ts() && kind == "public_field_definition")
|| (v.is_ts() && matches!(kind, "public_field_definition" | "method_signature"))
|| (!v.is_ts() && kind == "field_definition")
}
/// typescriptExtractor.propertyTypes. The interface counterpart of
/// `public_field_definition`: it carries no value, so it is always a property
/// and never goes through classify_ts_class_member (#1638).
fn is_property_type(v: Variant, kind: &str) -> bool {
v.is_ts() && kind == "property_signature"
}
/// Method node types that spell a SIGNATURE — a declaration with no body (#1638).
///
/// They are a method of whatever type declares them and nothing on their own, so
/// they must not take `extract_method`'s "no class-like parent, so treat it as a
/// free function" fallback. The other method types can: a `method_definition`
/// outside a class really is a function. This one appears outside a class only
/// inside a type literal (`type Handle = { stop(): void }`), whose members
/// `extract_ts_type_alias_members` already extracts and attaches to the alias
/// (#359) — take the fallback and the file gains a phantom top-level
/// `function stop` beside the real `Handle::stop`. Mirrors the TS extractor's
/// SIGNATURE_METHOD_NODE_TYPES (extraction/tree-sitter.ts).
fn is_signature_method_type(kind: &str) -> bool {
kind == "method_signature"
}
fn is_function_type(kind: &str) -> bool {
matches!(kind, "function_declaration" | "generator_function_declaration" | "arrow_function" | "function_expression" | "generator_function")
}
@@ -622,7 +644,9 @@ impl<'t> Walker<'t> {
} else if is_class_type(self.variant, kind) {
self.extract_class(node);
skip_children = true;
} else if is_method_type(self.variant, kind) {
} else if is_method_type(self.variant, kind)
&& (!is_signature_method_type(kind) || self.inside_class_like())
{
if classify_ts_class_member(node) == Member::Property {
let prop = self.extract_property(node);
if let (Some((row, name)), Some(value)) = (prop, node.child_by_field_name("value")) {
@@ -664,12 +688,22 @@ impl<'t> Walker<'t> {
self.extract_call(node);
} else if kind == "new_expression" {
self.extract_instantiation(node);
} else if self.variant.is_ts()
&& matches!(kind, "property_signature" | "method_signature")
&& self.inside_class_like()
{
let parent = self.top_row();
self.extract_type_annotations(node, parent);
} else if is_property_type(self.variant, kind) && self.inside_class_like() {
// NOTE: `property_signature` / `method_signature` used to be handled
// here together, hanging their type annotations off the ENCLOSING
// INTERFACE — the only anchor available while the members themselves
// went unextracted. Since #1638 `method_signature` is a method type
// and `property_signature` a property type, so the method branch
// above claims the first (under the same inside_class_like guard
// this branch had) and this one extracts the second as a real node.
// The `references` edges survive — extract_method and
// extract_property each call extract_type_annotations — but now hang
// off the member, the more precise anchor: `Api::fetch → PageId`
// says which member wants the type, where `Api → PageId` only said
// the file did.
self.extract_property(node);
self.scan_fn_ref_subtree(node, 0);
skip_children = true;
}
if !skip_children {