| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496 |
- //! The extract_* family — continuation of the Walker impl (see mod.rs for the
- //! porting contract). Each function mirrors its namesake in
- //! src/extraction/tree-sitter.ts; TS-file line references are as of the R2
- //! port. Bug-for-bug fidelity is deliberate — fix the TS side first.
- use crate::textutil as util;
- use super::{
- body_of, is_builtin_type, is_literal_receiver, is_react_hoc, is_variable_type,
- is_vue_collection_name, Extra, Scope, Walker,
- };
- use crate::buffers::edge_kind_index;
- use tree_sitter::Node;
- impl<'t> Walker<'t> {
- // --- extractFunction --------------------------------------------------------
- pub(super) fn extract_function(&mut self, node: Node<'t>, name_override: Option<String>) {
- let mut name = name_override
- .clone()
- .unwrap_or_else(|| self.extract_name(node));
- // Arrow/function-expression values: resolve the name from the parent
- // variable_declarator (`export const useAuth = () => {}`), or from a
- // CommonJS export assignment (`exports.getItems = async () => {}`,
- // #1675). Mirrors TreeSitterExtractor.extractFunction.
- let mut common_js_export = false;
- if name_override.is_none()
- && name == "<anonymous>"
- && matches!(node.kind(), "arrow_function" | "function_expression" | "generator_function")
- {
- if let Some(parent) = node.parent() {
- if parent.kind() == "variable_declarator" {
- if let Some(var_name) = parent.child_by_field_name("name") {
- name = self.text(var_name).to_string();
- }
- } else if parent.kind() == "assignment_expression" {
- if let Some(export_name) = self.common_js_export_name(parent, node) {
- name = export_name;
- common_js_export = true;
- }
- }
- }
- }
- if name == "<anonymous>" {
- // Still walk the body: module wrappers hold named inner functions
- // and calls that would otherwise be lost (#528).
- if let Some(body) = body_of(node) {
- self.visit_function_body(body);
- }
- return;
- }
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- signature: self.signature_of(node),
- visibility: self.visibility_of(node),
- is_exported: Some(common_js_export || self.is_exported(node)),
- is_async: Some(self.is_async(node)),
- is_static: self.is_static(node),
- ..Extra::default()
- };
- let Some(row) = self.create_node("function", &name, node, extra) else {
- return;
- };
- self.extract_type_annotations(node, row);
- self.extract_decorators_for(node, row);
- self.stack.push(Scope { row, kind: "function", name });
- if let Some(body) = body_of(node) {
- self.visit_function_body(body);
- }
- self.stack.pop();
- }
- /// The property a CommonJS export assignment binds a function to —
- /// `exports.NAME = <node>` / `module.exports.NAME = <node>` — or None for
- /// any other assignment. The node must be the assignment's whole
- /// right-hand side. Mirrors TreeSitterExtractor.commonJsExportName.
- fn common_js_export_name(&self, assignment: Node<'t>, value: Node<'t>) -> Option<String> {
- let right = assignment.child_by_field_name("right")?;
- if right.start_byte() != value.start_byte() || right.end_byte() != value.end_byte() {
- return None;
- }
- let left = assignment.child_by_field_name("left")?;
- if left.kind() != "member_expression" {
- return None;
- }
- let object = left.child_by_field_name("object")?;
- let property = left.child_by_field_name("property")?;
- if property.kind() != "property_identifier" {
- return None;
- }
- if !matches!(self.text(object), "exports" | "module.exports") {
- return None;
- }
- Some(self.text(property).to_string())
- }
- // --- reactComponentHoc / extractReactComponentNode (#841) --------------------
- /// Some(inner) when the initializer is a recognized component wrapper —
- /// inner is the inline render function, or None for `styled.x`/`memo(Ref)`.
- /// Outer None = not a component wrapper.
- fn react_component_hoc(&self, value: Node<'t>) -> Option<Option<Node<'t>>> {
- if value.kind() != "call_expression" {
- return None;
- }
- let callee = value.child_by_field_name("function")?;
- let callee_text = self.text(callee);
- if util::styled_callee().is_match(callee_text) {
- return Some(None);
- }
- if !is_react_hoc(callee_text) {
- return None;
- }
- let mut inner: Option<Node> = None;
- if let Some(args) = value.child_by_field_name("arguments") {
- for i in 0..args.named_child_count() {
- if let Some(a) = args.named_child(i) {
- if matches!(a.kind(), "arrow_function" | "function_expression") {
- inner = Some(a);
- break;
- }
- }
- }
- }
- Some(inner)
- }
- fn extract_react_component_node(
- &mut self,
- name: &str,
- declarator: Node<'t>,
- inner_fn: Option<Node<'t>>,
- extra: Extra,
- ) {
- let Some(row) = self.create_node("component", name, declarator, extra) else {
- return;
- };
- let Some(inner) = inner_fn else { return };
- self.stack.push(Scope { row, kind: "component", name: name.to_string() });
- if let Some(body) = body_of(inner) {
- self.visit_function_body(body);
- }
- self.stack.pop();
- }
- // --- extractClass ------------------------------------------------------------
- pub(super) fn extract_class(&mut self, node: Node<'t>) {
- let resolved_body = body_of(node); // skipBodilessClass unset for TS/JS
- let name = self.extract_name(node);
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- visibility: self.visibility_of(node),
- is_exported: Some(self.is_exported(node)),
- ..Extra::default()
- };
- let Some(row) = self.create_node("class", &name, node, extra) else {
- return;
- };
- self.extract_inheritance(node, row);
- self.extract_decorators_for(node, row);
- self.stack.push(Scope { row, kind: "class", name });
- let body = resolved_body.unwrap_or(node);
- for i in 0..body.named_child_count() {
- if let Some(c) = body.named_child(i) {
- self.visit_node(c);
- }
- }
- self.stack.pop();
- }
- // --- extractMethod -------------------------------------------------------------
- pub(super) fn extract_method(&mut self, node: Node<'t>) {
- if !self.inside_class_like() {
- // Object-literal methods are ephemeral: walk the body only.
- if let Some(parent) = node.parent() {
- if matches!(parent.kind(), "object" | "object_expression") {
- if let Some(body) = body_of(node) {
- self.visit_function_body(body);
- }
- return;
- }
- }
- self.extract_function(node, None);
- return;
- }
- let name = self.extract_name(node);
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- signature: self.signature_of(node),
- visibility: self.visibility_of(node),
- is_async: Some(self.is_async(node)),
- is_static: self.is_static(node),
- ..Extra::default() // methods carry no isExported (mirrors extractMethod)
- };
- let Some(row) = self.create_node("method", &name, node, extra) else {
- return;
- };
- self.extract_type_annotations(node, row);
- self.extract_decorators_for(node, row);
- self.stack.push(Scope { row, kind: "method", name });
- if let Some(body) = body_of(node) {
- self.visit_function_body(body);
- }
- self.stack.pop();
- }
- // --- extractInterface / extractEnum / members -----------------------------------
- pub(super) fn extract_interface(&mut self, node: Node<'t>) {
- let name = self.extract_name(node);
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- is_exported: Some(self.is_exported(node)),
- ..Extra::default()
- };
- let Some(row) = self.create_node("interface", &name, node, extra) else {
- return;
- };
- self.extract_inheritance(node, row);
- self.stack.push(Scope { row, kind: "interface", name });
- let body = body_of(node).unwrap_or(node);
- for i in 0..body.named_child_count() {
- if let Some(c) = body.named_child(i) {
- self.visit_node(c);
- }
- }
- self.stack.pop();
- }
- pub(super) fn extract_enum(&mut self, node: Node<'t>) {
- let Some(body) = body_of(node) else { return };
- let name = self.extract_name(node);
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- visibility: self.visibility_of(node),
- is_exported: Some(self.is_exported(node)),
- ..Extra::default()
- };
- let Some(row) = self.create_node("enum", &name, node, extra) else {
- return;
- };
- self.extract_inheritance(node, row);
- self.stack.push(Scope { row, kind: "enum", name });
- for i in 0..body.named_child_count() {
- let Some(child) = body.named_child(i) else { continue };
- if matches!(child.kind(), "property_identifier" | "enum_assignment") {
- self.extract_enum_members(child);
- } else {
- self.visit_node(child);
- }
- }
- self.stack.pop();
- }
- fn extract_enum_members(&mut self, node: Node<'t>) {
- if let Some(name_node) = node.child_by_field_name("name") {
- let name = self.text(name_node).to_string();
- self.create_node("enum_member", &name, node, Extra::default());
- return;
- }
- let mut found = false;
- for i in 0..node.named_child_count() {
- if let Some(child) = node.named_child(i) {
- if matches!(child.kind(), "simple_identifier" | "identifier" | "property_identifier") {
- let name = self.text(child).to_string();
- self.create_node("enum_member", &name, child, Extra::default());
- found = true;
- }
- }
- }
- if !found && node.named_child_count() == 0 {
- let name = self.text(node).to_string();
- self.create_node("enum_member", &name, node, Extra::default());
- }
- }
- // --- extractProperty (#808 property-classified class fields) ---------------------
- pub(super) fn extract_property(&mut self, node: Node<'t>) -> Option<(u32, String)> {
- let docstring = crate::docstring::preceding_docstring(node, self.src);
- let visibility = self.visibility_of(node);
- let is_static = Some(self.is_static(node).unwrap_or(false)); // `?? false` — always present
- let name_node = node
- .child_by_field_name("name")
- .or_else(|| node.child_by_field_name("property"))
- .or_else(|| {
- (0..node.named_child_count())
- .filter_map(|i| node.named_child(i))
- .find(|c| c.kind() == "identifier")
- })?;
- 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). 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()
- });
- let signature = match &type_text {
- Some(t) => format!("{t} {name}"),
- None => name.clone(),
- };
- let row = self.create_node(
- "property",
- &name,
- node,
- Extra { docstring, signature: Some(signature), visibility, is_static, ..Extra::default() },
- )?;
- self.extract_decorators_for(node, row);
- self.extract_type_annotations(node, row);
- Some((row, name))
- }
- // --- extractVariable (TS/JS branch) ------------------------------------------------
- /// A top-level binding exported by a LATER statement rather than at its
- /// declaration: `export default NAME`, `export { NAME }`, `export { NAME as
- /// default }`. The declaration's own `is_exported` (an `export_statement`
- /// ancestor) cannot see these. One anchored regex over the file source.
- /// Mirrors TreeSitterExtractor.isExportedLater.
- pub(super) fn is_exported_later(&self, name: &str) -> bool {
- if name.is_empty()
- || !name.chars().next().map(|c| c.is_ascii_alphabetic() || c == '_' || c == '$').unwrap_or(false)
- || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
- {
- return false;
- }
- let n = regex::escape(name);
- let pattern = format!(
- r"(?m)^[ \t]*export\s+(?:default\s+{n}\s*;?[ \t]*$|\{{[^}}]*\b{n}\b[^}}]*\}})",
- n = n
- );
- match regex::Regex::new(&pattern) {
- Ok(re) => re.is_match(self.src),
- Err(_) => false,
- }
- }
- pub(super) fn extract_variable(&mut self, node: Node<'t>) {
- let is_const = self.is_const_decl(node);
- let kind: &'static str = if is_const { "constant" } else { "variable" };
- let docstring = crate::docstring::preceding_docstring(node, self.src);
- let is_exported = self.is_exported(node); // `?? false` — always present
- for i in 0..node.named_child_count() {
- let Some(child) = node.named_child(i) else { continue };
- if child.kind() != "variable_declarator" {
- continue;
- }
- let Some(name_node) = child.child_by_field_name("name") else { continue };
- let value = child.child_by_field_name("value");
- // Destructured patterns are skipped — except RTK Query generated
- // hooks (`export const { useGetXQuery } = api`).
- if matches!(name_node.kind(), "object_pattern" | "array_pattern") {
- if name_node.kind() == "object_pattern"
- && value.map(|v| v.kind() == "identifier").unwrap_or(false)
- {
- self.extract_rtk_hook_bindings(name_node, is_exported);
- }
- continue;
- }
- let name = self.text(name_node).to_string();
- // Arrow/function/generator values extract as functions, named by the declarator.
- if let Some(v) = value {
- if matches!(v.kind(), "arrow_function" | "function_expression" | "generator_function") {
- self.extract_function(v, None);
- continue;
- }
- }
- let init_signature = value.map(|v| util::init_signature(self.text(v)));
- // React HOC-wrapped components (#841), PascalCase-gated.
- if let Some(v) = value {
- if util::pascal_case().is_match(&name) {
- if let Some(inner) = self.react_component_hoc(v) {
- self.extract_react_component_node(
- &name,
- child,
- inner,
- Extra {
- docstring: docstring.clone(),
- signature: init_signature.clone(),
- is_exported: Some(is_exported),
- ..Extra::default()
- },
- );
- continue;
- }
- }
- }
- let var_row = self.create_node(
- kind,
- &name,
- child,
- Extra {
- docstring: docstring.clone(),
- signature: init_signature.clone(),
- is_exported: Some(is_exported),
- ..Extra::default()
- },
- );
- if let Some(row) = var_row {
- self.extract_variable_type_annotation(child, row);
- }
- // Exported const object-of-functions / store shapes.
- let object_of_fns: Option<Node> = match value {
- Some(v) if matches!(v.kind(), "object" | "object_expression") => Some(v),
- Some(v) if v.kind() == "call_expression" => self.find_initializer_returned_object(v, 0),
- _ => None,
- };
- let has_inline_fns = object_of_fns
- .map(|o| self.object_has_inline_functions(o))
- .unwrap_or(false);
- // "Exported" includes the two-statement form `const useStore =
- // create(…)` … `export default useStore` (is_exported_later), the
- // shape most React Native stores are written in. Mirrors
- // TreeSitterExtractor.isExportedLater.
- let extract_object_methods =
- (is_exported || self.is_exported_later(&name)) && object_of_fns.is_some() && has_inline_fns;
- let rtk_endpoints = match value {
- Some(v) if v.kind() == "call_expression" => self.find_rtk_endpoints_object(v),
- _ => None,
- };
- let pinia_setup = match value {
- Some(v) if v.kind() == "call_expression" => self.find_pinia_setup_fn(v),
- _ => None,
- };
- let mut store_collections: Vec<Node> = Vec::new();
- if let Some(v) = value {
- if matches!(v.kind(), "call_expression" | "new_expression") {
- store_collections.extend(self.find_vue_store_collection_objects(v));
- }
- }
- if let Some(obj) = object_of_fns {
- if !extract_object_methods
- && is_vue_collection_name(&name)
- && self.looks_like_vue_store_file()
- {
- store_collections.push(obj);
- }
- }
- // Walk the initializer for calls, ATTRIBUTED to the declared symbol
- // (#693) — except the object/store shapes whose members are
- // extracted method-by-method below (walking those too would
- // double-count each member arrow's calls). Before this the walk ran
- // with only the FILE on the stack (`const cfg = load()` recorded the
- // file as load's caller) and object literals were skipped outright.
- let members_extracted_separately = extract_object_methods
- || rtk_endpoints.is_some()
- || pinia_setup.is_some()
- || !store_collections.is_empty();
- if let Some(v) = value {
- if !members_extracted_separately {
- match var_row {
- Some(row) => {
- self.stack.push(Scope { row, kind, name: name.clone() });
- self.visit_function_body(v);
- self.stack.pop();
- }
- None => self.visit_function_body(v),
- }
- }
- }
- if extract_object_methods {
- if let Some(obj) = object_of_fns {
- self.extract_object_literal_functions(obj);
- }
- }
- if let Some(rtk) = rtk_endpoints {
- self.extract_rtk_endpoints(rtk);
- }
- if let Some(setup) = pinia_setup {
- self.extract_pinia_setup_body(setup);
- }
- for coll in store_collections {
- self.extract_object_literal_functions(coll);
- }
- }
- }
- /// extractRtkHookBindings — `export const { useGetXQuery } = api`.
- fn extract_rtk_hook_bindings(&mut self, pattern: Node<'t>, is_exported: bool) {
- for i in 0..pattern.named_child_count() {
- let Some(binding) = pattern.named_child(i) else { continue };
- if binding.kind() != "shorthand_property_identifier_pattern" {
- continue;
- }
- let name = self.text(binding).to_string();
- if !util::rtk_hook_name().is_match(&name) {
- continue;
- }
- self.create_node(
- "function",
- &name,
- binding,
- Extra {
- is_exported: Some(is_exported),
- signature: Some("= RTK Query generated hook".to_string()),
- ..Extra::default()
- },
- );
- }
- }
- // --- object-literal / store helpers -------------------------------------------------
- pub(super) fn extract_object_literal_functions(&mut self, obj: Node<'t>) {
- for i in 0..obj.named_child_count() {
- let Some(member) = obj.named_child(i) else { continue };
- if member.kind() == "pair" {
- let key = member.child_by_field_name("key");
- let value = member.child_by_field_name("value");
- if let (Some(k), Some(v)) = (key, value) {
- if matches!(v.kind(), "arrow_function" | "function_expression") {
- let name = util::object_key_name(self.text(k));
- self.extract_function(v, Some(name));
- }
- }
- } else if member.kind() == "method_definition" {
- if let Some(k) = member.child_by_field_name("name") {
- let name = util::object_key_name(self.text(k));
- self.extract_function(member, Some(name));
- }
- }
- }
- }
- fn find_initializer_returned_object(&self, call: Node<'t>, depth: u32) -> Option<Node<'t>> {
- stack_guard!();
- if depth > 4 {
- return None;
- }
- let args = call.child_by_field_name("arguments")?;
- for i in 0..args.named_child_count() {
- let Some(arg) = args.named_child(i) else { continue };
- if matches!(arg.kind(), "arrow_function" | "function_expression") {
- if let Some(obj) = self.function_returned_object(arg) {
- return Some(obj);
- }
- } else if arg.kind() == "call_expression" {
- if let Some(obj) = self.find_initializer_returned_object(arg, depth + 1) {
- return Some(obj);
- }
- }
- }
- None
- }
- fn function_returned_object(&self, fn_node: Node<'t>) -> Option<Node<'t>> {
- fn as_object<'t>(n: Node<'t>) -> Option<Node<'t>> {
- stack_guard!();
- match n.kind() {
- "object" | "object_expression" => Some(n),
- "parenthesized_expression" => {
- for i in 0..n.named_child_count() {
- if let Some(inner) = n.named_child(i).and_then(as_object) {
- return Some(inner);
- }
- }
- None
- }
- _ => None,
- }
- }
- let body = fn_node.child_by_field_name("body")?;
- if let Some(direct) = as_object(body) {
- return Some(direct);
- }
- if body.kind() == "statement_block" {
- for i in 0..body.named_child_count() {
- let Some(stmt) = body.named_child(i) else { continue };
- if stmt.kind() != "return_statement" {
- continue;
- }
- for j in 0..stmt.named_child_count() {
- if let Some(obj) = stmt.named_child(j).and_then(as_object) {
- return Some(obj);
- }
- }
- }
- }
- None
- }
- pub(super) fn object_has_inline_functions(&self, obj: Node) -> bool {
- for i in 0..obj.named_child_count() {
- let Some(member) = obj.named_child(i) else { continue };
- if member.kind() == "method_definition" {
- return true;
- }
- if member.kind() == "pair" {
- if let Some(v) = member.child_by_field_name("value") {
- if matches!(v.kind(), "arrow_function" | "function_expression") {
- return true;
- }
- }
- }
- }
- false
- }
- fn find_rtk_endpoints_object(&self, call: Node<'t>) -> Option<Node<'t>> {
- let callee = call.child_by_field_name("function")?;
- let callee_name = match callee.kind() {
- "identifier" => self.text(callee),
- "member_expression" => {
- let prop = callee.child_by_field_name("property").unwrap_or(callee);
- self.text(prop)
- }
- _ => "",
- };
- if callee_name != "createApi" && callee_name != "injectEndpoints" {
- return None;
- }
- let args = call.child_by_field_name("arguments")?;
- for i in 0..args.named_child_count() {
- let Some(arg) = args.named_child(i) else { continue };
- if !matches!(arg.kind(), "object" | "object_expression") {
- continue;
- }
- for j in 0..arg.named_child_count() {
- let Some(member) = arg.named_child(j) else { continue };
- if member.kind() == "pair" {
- let Some(key) = member.child_by_field_name("key") else { continue };
- if self.text(key) != "endpoints" {
- continue;
- }
- if let Some(value) = member.child_by_field_name("value") {
- if matches!(value.kind(), "arrow_function" | "function_expression") {
- return self.function_returned_object(value);
- }
- }
- } else if member.kind() == "method_definition" {
- let Some(key) = member.child_by_field_name("name") else { continue };
- if self.text(key) != "endpoints" {
- continue;
- }
- return self.function_returned_object(member);
- }
- }
- }
- None
- }
- fn extract_rtk_endpoints(&mut self, obj: Node<'t>) {
- for i in 0..obj.named_child_count() {
- let Some(member) = obj.named_child(i) else { continue };
- if member.kind() != "pair" {
- continue;
- }
- let key = member.child_by_field_name("key");
- let value = member.child_by_field_name("value");
- let (Some(key), Some(value)) = (key, value) else { continue };
- if value.kind() != "call_expression" {
- continue;
- }
- let Some(callee) = value.child_by_field_name("function") else { continue };
- if callee.kind() != "member_expression" {
- continue;
- }
- let method = self.text(callee.child_by_field_name("property").unwrap_or(callee));
- if method != "query" && method != "mutation" && method != "infiniteQuery" {
- continue;
- }
- let key_name = util::object_key_name(self.text(key));
- if let Some(handler) = self.rtk_endpoint_handler(value) {
- self.extract_function(handler, Some(key_name));
- } else {
- // Config-only endpoint: bare node spanning the builder call.
- let (sig, _) = util::slice_utf16(self.text(value), 80);
- let row = self.create_node(
- "function",
- &key_name,
- value,
- Extra { signature: Some(sig), ..Extra::default() },
- );
- if let Some(row) = row {
- self.stack.push(Scope { row, kind: "function", name: key_name });
- self.visit_function_body(value);
- self.stack.pop();
- }
- }
- }
- }
- fn rtk_endpoint_handler(&self, call: Node<'t>) -> Option<Node<'t>> {
- let args = call.child_by_field_name("arguments")?;
- for i in 0..args.named_child_count() {
- let Some(arg) = args.named_child(i) else { continue };
- if !matches!(arg.kind(), "object" | "object_expression") {
- continue;
- }
- let mut query_fn: Option<Node> = None;
- let mut query: Option<Node> = None;
- let mut first_fn: Option<Node> = None;
- for j in 0..arg.named_child_count() {
- let Some(member) = arg.named_child(j) else { continue };
- let mut fn_node: Option<Node> = None;
- let mut key_name = "";
- if member.kind() == "pair" {
- if let Some(v) = member.child_by_field_name("value") {
- if matches!(v.kind(), "arrow_function" | "function_expression") {
- fn_node = Some(v);
- if let Some(k) = member.child_by_field_name("key") {
- key_name = self.text(k);
- }
- }
- }
- } else if member.kind() == "method_definition" {
- fn_node = Some(member);
- if let Some(k) = member.child_by_field_name("name") {
- key_name = self.text(k);
- }
- }
- let Some(f) = fn_node else { continue };
- if key_name == "queryFn" {
- query_fn = Some(f);
- } else if key_name == "query" {
- query = Some(f);
- }
- if first_fn.is_none() {
- first_fn = Some(f);
- }
- }
- if let Some(f) = query_fn.or(query).or(first_fn) {
- return Some(f);
- }
- }
- None
- }
- pub(super) fn looks_like_vue_store_file(&mut self) -> bool {
- if let Some(v) = self.vue_store_file {
- return v;
- }
- let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
- for m in util::vue_store_signal().find_iter(self.src) {
- seen.insert(m.as_str());
- if seen.len() >= 2 {
- break;
- }
- }
- let v = seen.len() >= 2;
- self.vue_store_file = Some(v);
- v
- }
- fn find_vue_store_collection_objects(&self, call: Node<'t>) -> Vec<Node<'t>> {
- let callee = call
- .child_by_field_name("function")
- .or_else(|| call.child_by_field_name("constructor"));
- let Some(callee) = callee else { return vec![] };
- let callee_name = match callee.kind() {
- "identifier" => self.text(callee),
- "member_expression" => self.text(callee.child_by_field_name("property").unwrap_or(callee)),
- _ => "",
- };
- if !matches!(callee_name, "defineStore" | "createStore" | "Store") {
- return vec![];
- }
- let Some(args) = call.child_by_field_name("arguments") else { return vec![] };
- let mut objects = Vec::new();
- for i in 0..args.named_child_count() {
- let Some(arg) = args.named_child(i) else { continue };
- if !matches!(arg.kind(), "object" | "object_expression") {
- continue;
- }
- for j in 0..arg.named_child_count() {
- let Some(member) = arg.named_child(j) else { continue };
- if member.kind() != "pair" {
- continue;
- }
- let Some(key) = member.child_by_field_name("key") else { continue };
- if !is_vue_collection_name(self.text(key)) {
- continue;
- }
- if let Some(value) = member.child_by_field_name("value") {
- if matches!(value.kind(), "object" | "object_expression") {
- objects.push(value);
- }
- }
- }
- }
- objects
- }
- pub(super) fn extract_store_collection_methods(&mut self, config: Node<'t>) {
- for i in 0..config.named_child_count() {
- let Some(member) = config.named_child(i) else { continue };
- if member.kind() != "pair" {
- continue;
- }
- let Some(key) = member.child_by_field_name("key") else { continue };
- if !is_vue_collection_name(self.text(key)) {
- continue;
- }
- if let Some(value) = member.child_by_field_name("value") {
- if matches!(value.kind(), "object" | "object_expression") {
- self.extract_object_literal_functions(value);
- }
- }
- }
- }
- fn find_pinia_setup_fn(&self, call: Node<'t>) -> Option<Node<'t>> {
- let callee = call.child_by_field_name("function")?;
- if callee.kind() != "identifier" || self.text(callee) != "defineStore" {
- return None;
- }
- let args = call.child_by_field_name("arguments")?;
- for i in 0..args.named_child_count() {
- let Some(arg) = args.named_child(i) else { continue };
- if !matches!(arg.kind(), "arrow_function" | "function_expression") {
- continue;
- }
- if let Some(body) = arg.child_by_field_name("body") {
- if body.kind() == "statement_block" {
- return Some(arg);
- }
- }
- }
- None
- }
- fn extract_pinia_setup_body(&mut self, setup: Node<'t>) {
- let Some(body) = setup.child_by_field_name("body") else { return };
- if body.kind() != "statement_block" {
- return;
- }
- for i in 0..body.named_child_count() {
- let Some(stmt) = body.named_child(i) else { continue };
- if stmt.kind() == "function_declaration" {
- self.extract_function(stmt, None);
- } else if is_variable_type(stmt.kind()) {
- for j in 0..stmt.named_child_count() {
- let Some(decl) = stmt.named_child(j) else { continue };
- if decl.kind() != "variable_declarator" {
- continue;
- }
- if let Some(v) = decl.child_by_field_name("value") {
- if matches!(v.kind(), "arrow_function" | "function_expression") {
- self.extract_function(v, None);
- }
- }
- }
- }
- }
- }
- // --- extractTypeAlias + members (#359, #634) -------------------------------------
- /// Returns skipChildren (always false on the TS path — the alias value is
- /// still traversed by the dispatcher).
- pub(super) fn extract_type_alias(&mut self, node: Node<'t>) -> bool {
- let name = self.extract_name(node);
- if name == "<anonymous>" {
- return false;
- }
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(node, self.src),
- is_exported: Some(self.is_exported(node)),
- ..Extra::default()
- };
- let Some(row) = self.create_node("type_alias", &name, node, extra) else {
- return false;
- };
- if let Some(value) = node.child_by_field_name("value") {
- self.extract_type_refs_from_subtree(value, row);
- self.extract_ts_type_alias_members(value, row, &name);
- self.extract_ts_tuple_contract_names(value, row, &name);
- }
- false
- }
- fn extract_ts_type_alias_members(&mut self, value: Node<'t>, alias_row: u32, alias_name: &str) {
- let mut object_types: Vec<Node> = Vec::new();
- if value.kind() == "object_type" {
- object_types.push(value);
- } else if value.kind() == "intersection_type" {
- for i in 0..value.named_child_count() {
- if let Some(op) = value.named_child(i) {
- if op.kind() == "object_type" {
- object_types.push(op);
- }
- }
- }
- } else {
- return;
- }
- self.stack.push(Scope { row: alias_row, kind: "type_alias", name: alias_name.to_string() });
- for obj_type in object_types {
- for i in 0..obj_type.named_child_count() {
- let Some(child) = obj_type.named_child(i) else { continue };
- if !matches!(child.kind(), "property_signature" | "method_signature") {
- continue;
- }
- let Some(name_node) = child.child_by_field_name("name") else { continue };
- let member_name = self.text(name_node).to_string();
- if member_name.is_empty() {
- continue;
- }
- let member_kind: &'static str = if child.kind() == "method_signature"
- || self.is_ts_function_typed_property(child)
- {
- "method"
- } else {
- "property"
- };
- let extra = Extra {
- docstring: crate::docstring::preceding_docstring(child, self.src),
- signature: Some(self.text(child).to_string()),
- qualified_name: Some(format!("{alias_name}::{member_name}")),
- ..Extra::default()
- };
- self.create_node(member_kind, &member_name, child, extra);
- self.extract_type_annotations(child, alias_row);
- }
- }
- self.stack.pop();
- }
- fn extract_ts_tuple_contract_names(&mut self, value: Node<'t>, alias_row: u32, alias_name: &str) {
- let mut tuples: Vec<Node> = Vec::new();
- fn collect<'t>(n: Node<'t>, depth: u32, out: &mut Vec<Node<'t>>) {
- stack_guard!();
- if depth > 6 {
- return;
- }
- if n.kind() == "tuple_type" {
- out.push(n);
- }
- for i in 0..n.named_child_count() {
- if let Some(c) = n.named_child(i) {
- collect(c, depth + 1, out);
- }
- }
- }
- collect(value, 0, &mut tuples);
- if tuples.is_empty() {
- return;
- }
- self.stack.push(Scope { row: alias_row, kind: "type_alias", name: alias_name.to_string() });
- for tuple in tuples {
- for i in 0..tuple.named_child_count() {
- let Some(entry) = tuple.named_child(i) else { continue };
- if entry.kind() != "generic_type" {
- continue;
- }
- let Some(type_args) = entry.child_by_field_name("type_arguments") else { continue };
- for j in 0..type_args.named_child_count() {
- let Some(arg) = type_args.named_child(j) else { continue };
- if arg.kind() != "literal_type" {
- continue;
- }
- let Some(str_node) = arg.named_child(0) else { continue };
- if str_node.kind() != "string" {
- continue;
- }
- let name = util::object_key_name(self.text(str_node).trim());
- if !util::ident_dollar().is_match(&name) {
- continue;
- }
- let collapsed = collapse_ws(self.text(entry));
- let (signature, _) = util::slice_utf16(collapsed.trim(), 120);
- let extra = Extra {
- signature: Some(signature),
- qualified_name: Some(format!("{alias_name}::{name}")),
- ..Extra::default()
- };
- self.create_node("method", &name, entry, extra);
- }
- }
- }
- self.stack.pop();
- }
- fn is_ts_function_typed_property(&self, property_signature: Node) -> bool {
- let Some(type_anno) = property_signature.child_by_field_name("type") else {
- return false;
- };
- for i in 0..type_anno.named_child_count() {
- if let Some(inner) = type_anno.named_child(i) {
- if inner.kind() == "function_type" {
- return true;
- }
- }
- }
- false
- }
- // --- extractImport + binding refs ---------------------------------------------------
- pub(super) fn extract_import(&mut self, node: Node<'t>) {
- let import_text = self.text(node).trim().to_string();
- // typescriptExtractor.extractImport: the `source` field, quotes stripped
- // globally. A missing/empty module means the hook declined — no node.
- let Some(source_field) = node.child_by_field_name("source") else { return };
- let module_name: String = self
- .text(source_field)
- .chars()
- .filter(|c| *c != '\'' && *c != '"')
- .collect();
- if module_name.is_empty() {
- return;
- }
- self.create_node(
- "import",
- &module_name,
- node,
- Extra { signature: Some(import_text), ..Extra::default() },
- );
- let parent = self.top_row();
- self.push_ref(parent, &module_name.clone(), edge_kind_index("imports").unwrap(), node);
- self.emit_import_binding_refs(node, parent);
- }
- fn emit_import_binding_refs(&mut self, node: Node<'t>, from_row: u32) {
- let clause = (0..node.named_child_count())
- .filter_map(|i| node.named_child(i))
- .find(|c| c.kind() == "import_clause");
- let Some(clause) = clause else { return }; // side-effect import
- let imports_kind = edge_kind_index("imports").unwrap();
- let push = |w: &mut Self, name_node: Option<Node>| {
- let Some(n) = name_node else { return };
- let name = w.text(n).to_string();
- if name.is_empty() {
- return;
- }
- w.push_ref(from_row, &name, imports_kind, n);
- };
- for i in 0..clause.named_child_count() {
- let Some(child) = clause.named_child(i) else { continue };
- match child.kind() {
- "identifier" => push(self, Some(child)),
- "named_imports" => {
- for j in 0..child.named_child_count() {
- let Some(spec) = child.named_child(j) else { continue };
- if spec.kind() != "import_specifier" {
- continue;
- }
- let n = spec
- .child_by_field_name("alias")
- .or_else(|| spec.child_by_field_name("name"))
- .or_else(|| spec.named_child(0));
- push(self, n);
- }
- }
- "namespace_import" => {
- let n = (0..child.named_child_count())
- .filter_map(|k| child.named_child(k))
- .find(|c| c.kind() == "identifier")
- .or_else(|| child.named_child(0));
- push(self, n);
- }
- _ => {}
- }
- }
- }
- pub(super) fn emit_re_export_refs(&mut self, node: Node<'t>) {
- let from_row = self.top_row();
- let clause = (0..node.named_child_count())
- .filter_map(|i| node.named_child(i))
- .find(|c| c.kind() == "export_clause");
- let Some(clause) = clause else { return }; // `export * from './y'`
- let imports_kind = edge_kind_index("imports").unwrap();
- for i in 0..clause.named_child_count() {
- let Some(spec) = clause.named_child(i) else { continue };
- if spec.kind() != "export_specifier" {
- continue;
- }
- let name_node = spec.child_by_field_name("name").or_else(|| spec.named_child(0));
- let Some(n) = name_node else { continue };
- let name = self.text(n).to_string();
- if name.is_empty() || name == "default" {
- continue;
- }
- self.push_ref(from_row, &name, imports_kind, n);
- }
- }
- // --- extractCall (TS/JS generic tail) -------------------------------------------------
- /// Identifier-rooted member chains have no inferred property type (#1566),
- /// including host API chains (#1707). Keep the existing window namespace
- /// escape; call-result and `this` receivers are outside this guard.
- fn is_unresolved_member_chain(&self, receiver: Node<'t>) -> bool {
- let mut cur = receiver;
- if !matches!(cur.kind(), "member_expression" | "subscript_expression") {
- return false;
- }
- while matches!(cur.kind(), "member_expression" | "subscript_expression") {
- match cur.child_by_field_name("object") {
- Some(next) => cur = next,
- None => return false,
- }
- }
- cur.kind() == "identifier" && self.text(cur) != "window"
- }
- pub(super) fn extract_call(&mut self, node: Node<'t>) {
- if self.stack.is_empty() {
- return;
- }
- let func = node
- .child_by_field_name("function")
- .or_else(|| node.named_child(0));
- let mut callee_name = String::new();
- if let Some(func) = func {
- if func.kind() == "member_expression" {
- let property = func
- .child_by_field_name("property")
- .or_else(|| func.child_by_field_name("field"))
- .or_else(|| func.named_child(1));
- if let Some(property) = property {
- let method_name = self.text(property);
- let receiver = func
- .child_by_field_name("object")
- .or_else(|| func.child_by_field_name("operand"))
- .or_else(|| func.child_by_field_name("argument"))
- .or_else(|| func.named_child(0));
- // Literal receivers call builtins, never project symbols (#1230).
- if let Some(r) = receiver {
- if is_literal_receiver(r.kind()) {
- return;
- }
- }
- let recv_ident = receiver.filter(|r| {
- matches!(r.kind(), "identifier" | "simple_identifier" | "field_identifier")
- });
- if let Some(r) = recv_ident {
- let receiver_name = self.text(r);
- if !matches!(receiver_name, "self" | "this" | "cls" | "super") {
- callee_name = format!("{receiver_name}.{method_name}");
- } else {
- callee_name = method_name.to_string();
- }
- } else if receiver.is_some_and(|r| self.is_unresolved_member_chain(r)) {
- // Retain the call site for effects without guessing a
- // project method. Mirrors the TS extraction path.
- let chain = self.text(func).replace("?.", ".");
- let Some(chain) = Self::plain_member_name(&chain) else { return };
- callee_name = chain;
- } else if let Some(field) = receiver.and_then(|r| self.this_field_of(r)) {
- // `this.<field>.<method>()` — keep the field so the
- // resolver can read its declared type (#1496). Mirrors
- // TreeSitterExtractor.extractCall.
- callee_name = format!("this.{field}.{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
- // is not a plain name / member chain. Mirrors
- // TreeSitterExtractor.extractCall.
- let Some(inner) = self.plain_inner_callee(r) else { return };
- callee_name = format!("{inner}().{method_name}");
- } else {
- callee_name = method_name.to_string();
- }
- }
- } else {
- callee_name = self.text(func).to_string();
- }
- }
- // Parenthesized-callee normalization (`(fn)()` → fn).
- if !callee_name.is_empty() {
- if let Some(c) = util::paren_conversion().captures(&callee_name) {
- callee_name = c[1].to_string();
- }
- }
- if !callee_name.is_empty() {
- self.push_call_ref(&callee_name.clone(), node);
- }
- }
- // --- extractInstantiation -----------------------------------------------------------
- /// `this.<field>` as a member_expression receiver → Some(field) (#1496).
- fn this_field_of(&self, receiver: Node<'t>) -> Option<String> {
- if receiver.kind() != "member_expression" {
- return None;
- }
- let object = receiver.child_by_field_name("object")?;
- let property = receiver.child_by_field_name("property")?;
- if object.kind() != "this" || property.kind() != "property_identifier" {
- return None;
- }
- Some(self.text(property).to_string())
- }
- /// The callee of a call-expression receiver when it is a plain identifier
- /// or member chain (`make`, `d.setdefault`), whitespace stripped (#1683).
- fn plain_inner_callee(&self, call: Node<'t>) -> Option<String> {
- let inner = call.child_by_field_name("function")?;
- Self::plain_member_name(self.text(inner))
- }
- fn plain_member_name(source: &str) -> Option<String> {
- let text: String = source.chars().filter(|c| !c.is_whitespace()).collect();
- if text.is_empty() {
- return None;
- }
- let ok = text.split('.').all(|seg| {
- let mut chars = seg.chars();
- matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '$')
- && chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
- });
- if ok { Some(text) } else { None }
- }
- pub(super) fn extract_instantiation(&mut self, node: Node<'t>) {
- if self.stack.is_empty() {
- return;
- }
- let ctor = node
- .child_by_field_name("constructor")
- .or_else(|| node.child_by_field_name("type"))
- .or_else(|| node.child_by_field_name("name"))
- .or_else(|| node.named_child(0));
- let Some(ctor) = ctor else { return };
- let mut class_name = self.text(ctor).to_string();
- // `new Map<K, V>()` → Map.
- if let Some(lt) = class_name.find('<') {
- if lt > 0 {
- class_name.truncate(lt);
- }
- }
- // `new ns.Foo()` → Foo.
- let last_dot = class_name
- .rfind('.')
- .map(|i| i as isize)
- .unwrap_or(-1)
- .max(class_name.rfind("::").map(|i| i as isize).unwrap_or(-1));
- if last_dot >= 0 {
- class_name = class_name[(last_dot as usize + 1)..].to_string();
- // TS: .replace(/^[:.]/, '') — one leading colon-or-dot.
- if class_name.starts_with(':') || class_name.starts_with('.') {
- class_name.remove(0);
- }
- }
- let class_name = class_name.trim().to_string();
- if !class_name.is_empty() {
- let from = self.top_row();
- self.push_ref(from, &class_name, edge_kind_index("instantiates").unwrap(), node);
- }
- }
- // --- extractDecoratorsFor --------------------------------------------------------------
- pub(super) fn extract_decorators_for(&mut self, decl: Node<'t>, decorated_row: u32) {
- // 1. Direct children (method/property style).
- for i in 0..decl.named_child_count() {
- let Some(child) = decl.named_child(i) else { continue };
- self.consider_decorator(child, decorated_row);
- if child.kind() == "modifiers" {
- for j in 0..child.named_child_count() {
- if let Some(m) = child.named_child(j) {
- self.consider_decorator(m, decorated_row);
- }
- }
- }
- }
- // 2. Preceding siblings (TypeScript class style), stopping at the
- // first non-decorator so an earlier declaration's decorators never
- // leak in. Matching by startIndex, not object identity.
- let Some(parent) = decl.parent() else { return };
- let decl_start = decl.start_byte();
- let mut decl_idx: isize = -1;
- for i in 0..parent.named_child_count() {
- if let Some(sib) = parent.named_child(i) {
- if sib.start_byte() == decl_start {
- decl_idx = i as isize;
- break;
- }
- }
- }
- if decl_idx > 0 {
- let mut j = decl_idx - 1;
- while j >= 0 {
- let Some(sib) = parent.named_child(j as usize) else {
- j -= 1;
- continue;
- };
- if !matches!(sib.kind(), "decorator" | "annotation" | "marker_annotation") {
- break;
- }
- self.consider_decorator(sib, decorated_row);
- j -= 1;
- }
- }
- }
- fn consider_decorator(&mut self, n: Node<'t>, decorated_row: u32) {
- if !matches!(n.kind(), "decorator" | "annotation" | "marker_annotation" | "attribute") {
- return;
- }
- let mut target: Option<Node> = None;
- for i in 0..n.named_child_count() {
- let Some(child) = n.named_child(i) else { continue };
- if child.kind() == "call_expression" {
- target = child.child_by_field_name("function").or_else(|| child.named_child(0));
- if target.is_some() {
- break;
- }
- }
- if matches!(
- child.kind(),
- "identifier" | "member_expression" | "scoped_identifier" | "navigation_expression"
- | "user_type" | "type_identifier"
- ) {
- target = Some(child);
- break;
- }
- }
- let Some(target) = target else { return };
- let mut name = self.text(target).to_string();
- if let Some(lt) = name.find('<') {
- if lt > 0 {
- name.truncate(lt);
- }
- }
- let last_dot = name
- .rfind('.')
- .map(|i| i as isize)
- .unwrap_or(-1)
- .max(name.rfind("::").map(|i| i as isize).unwrap_or(-1));
- if last_dot >= 0 {
- name = name[(last_dot as usize + 1)..].to_string();
- if name.starts_with(':') || name.starts_with('.') {
- name.remove(0);
- }
- }
- let name = name.trim().to_string();
- if name.is_empty() {
- return;
- }
- self.push_ref(decorated_row, &name, edge_kind_index("decorates").unwrap(), n);
- }
- // --- extractInheritance (TS/JS clauses) ---------------------------------------------------
- pub(super) fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) {
- stack_guard!();
- let extends_kind = edge_kind_index("extends").unwrap();
- let implements_kind = edge_kind_index("implements").unwrap();
- for i in 0..node.named_child_count() {
- let Some(child) = node.named_child(i) else { continue };
- match child.kind() {
- // TS `extends_clause` (the other spellings are other grammars').
- "extends_clause" | "superclass" | "base_clause" | "extends_interfaces" => {
- if let Some(target) = child.named_child(0) {
- let name = self.text(target).to_string();
- self.push_ref(class_row, &name, extends_kind, target);
- }
- }
- "implements_clause" | "class_interface_clause" | "super_interfaces" | "interfaces" => {
- for j in 0..child.named_child_count() {
- if let Some(iface) = child.named_child(j) {
- let name = self.text(iface).to_string();
- self.push_ref(class_row, &name, implements_kind, iface);
- }
- }
- }
- // JS `class Foo extends Bar` — class_heritage holds a bare
- // identifier without an extends_clause wrapper.
- "identifier" | "type_identifier" if node.kind() == "class_heritage" => {
- let name = self.text(child).to_string();
- self.push_ref(class_row, &name, extends_kind, child);
- }
- // TS class_heritage wraps extends/implements — recurse.
- "field_declaration_list" | "class_heritage" => {
- self.extract_inheritance(child, class_row);
- }
- _ => {}
- }
- }
- }
- // --- type annotations (#381 — TS family only) ----------------------------------------------
- pub(super) fn extract_type_annotations(&mut self, node: Node<'t>, from_row: u32) {
- if !self.variant.is_ts() {
- return;
- }
- if let Some(params) = node.child_by_field_name("parameters") {
- self.extract_type_refs_from_subtree(params, from_row);
- }
- if let Some(ret) = node.child_by_field_name("return_type") {
- self.extract_type_refs_from_subtree(ret, from_row);
- }
- let type_annotation = (0..node.named_child_count())
- .filter_map(|i| node.named_child(i))
- .find(|c| c.kind() == "type_annotation");
- if let Some(ta) = type_annotation {
- self.extract_type_refs_from_subtree(ta, from_row);
- }
- }
- pub(super) fn extract_variable_type_annotation(&mut self, node: Node<'t>, from_row: u32) {
- if !self.variant.is_ts() {
- return;
- }
- let type_annotation = (0..node.named_child_count())
- .filter_map(|i| node.named_child(i))
- .find(|c| c.kind() == "type_annotation");
- if let Some(ta) = type_annotation {
- self.extract_type_refs_from_subtree(ta, from_row);
- }
- }
- fn extract_type_refs_from_subtree(&mut self, node: Node<'t>, from_row: u32) {
- stack_guard!();
- if node.kind() == "type_identifier" {
- let type_name = self.text(node).to_string();
- if !type_name.is_empty() && !is_builtin_type(&type_name) {
- self.push_ref(from_row, &type_name, edge_kind_index("references").unwrap(), node);
- }
- return;
- }
- for i in 0..node.named_child_count() {
- if let Some(c) = node.named_child(i) {
- self.extract_type_refs_from_subtree(c, from_row);
- }
- }
- }
- }
- /// `.replace(/\s+/g, ' ')` for the tuple-contract signature.
- fn collapse_ws(s: &str) -> String {
- let mut out = String::with_capacity(s.len());
- let mut in_ws = false;
- for c in s.chars() {
- if c.is_whitespace() {
- if !in_ws {
- out.push(' ');
- in_ws = true;
- }
- } else {
- out.push(c);
- in_ws = false;
- }
- }
- out
- }
|