Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions codegraph-kernel/src/tsjs/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,33 @@ impl<'t> Walker<'t> {
let name = self.text(name_node).to_string();

// TS/JS field definitions carry an explicit `type` field; the generic
// scan is for other languages (#808).
let type_text = node.child_by_field_name("type").map(|t| {
// scan is for other languages (#808). A `property_signature` (an
// interface member, #1638) carries a `type` field and no value, so it
// reads the type field too: the generic scan's exclusion list covers
// `identifier` but not the `property_identifier` an interface member is
// named with, so it would stop on the name and make the signature repeat
// it (`counts counts`) instead of naming the type. Mirrors
// extractProperty's isTsJsField.
let is_ts_js_field = matches!(
node.kind(),
"public_field_definition" | "field_definition" | "property_signature"
);
let type_node = if is_ts_js_field {
node.child_by_field_name("type")
} else {
(0..node.named_child_count()).filter_map(|i| node.named_child(i)).find(|c| {
!matches!(
c.kind(),
"modifier"
| "modifiers"
| "identifier"
| "accessor_list"
| "accessors"
| "equals_value_clause"
)
})
};
let type_text = type_node.map(|t| {
let raw = self.text(t);
raw.strip_prefix(':').unwrap_or(raw).trim_start().to_string()
});
Expand Down
50 changes: 42 additions & 8 deletions codegraph-kernel/src/tsjs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" | "arrow_function" | "function_expression")
}
Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion src/extraction/tree-sitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2049,8 +2049,18 @@ export class TreeSitterExtractor {
// and the initializer VALUE, which the generic finder below would
// wrongly pick — so fields use the type field only (#808). Other
// languages (C# property_declaration) keep the generic scan.
//
// A `property_signature` (an interface member, #1638) carries a `type`
// field and no value, so it reads the type field too. It cannot take the
// generic scan: that scan's exclusion list covers `identifier` but not the
// `property_identifier` an interface member is named with, so it stops on
// the name and `interface Stats { counts: Record<string, number> }` yields
// `signature: "counts counts"` instead of the type. Named explicitly
// rather than folded into the field test so no other language's
// `property_declaration` moves off the generic scan.
const isTsJsField =
node.type === 'public_field_definition' || node.type === 'field_definition';
node.type === 'public_field_definition' || node.type === 'field_definition'
|| node.type === 'property_signature';
const typeNode = isTsJsField
? getChildByField(node, 'type')
: node.namedChildren.find(
Expand Down