extractors.rs 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496
  1. //! The extract_* family — continuation of the Walker impl (see mod.rs for the
  2. //! porting contract). Each function mirrors its namesake in
  3. //! src/extraction/tree-sitter.ts; TS-file line references are as of the R2
  4. //! port. Bug-for-bug fidelity is deliberate — fix the TS side first.
  5. use crate::textutil as util;
  6. use super::{
  7. body_of, is_builtin_type, is_literal_receiver, is_react_hoc, is_variable_type,
  8. is_vue_collection_name, Extra, Scope, Walker,
  9. };
  10. use crate::buffers::edge_kind_index;
  11. use tree_sitter::Node;
  12. impl<'t> Walker<'t> {
  13. // --- extractFunction --------------------------------------------------------
  14. pub(super) fn extract_function(&mut self, node: Node<'t>, name_override: Option<String>) {
  15. let mut name = name_override
  16. .clone()
  17. .unwrap_or_else(|| self.extract_name(node));
  18. // Arrow/function-expression values: resolve the name from the parent
  19. // variable_declarator (`export const useAuth = () => {}`), or from a
  20. // CommonJS export assignment (`exports.getItems = async () => {}`,
  21. // #1675). Mirrors TreeSitterExtractor.extractFunction.
  22. let mut common_js_export = false;
  23. if name_override.is_none()
  24. && name == "<anonymous>"
  25. && matches!(node.kind(), "arrow_function" | "function_expression" | "generator_function")
  26. {
  27. if let Some(parent) = node.parent() {
  28. if parent.kind() == "variable_declarator" {
  29. if let Some(var_name) = parent.child_by_field_name("name") {
  30. name = self.text(var_name).to_string();
  31. }
  32. } else if parent.kind() == "assignment_expression" {
  33. if let Some(export_name) = self.common_js_export_name(parent, node) {
  34. name = export_name;
  35. common_js_export = true;
  36. }
  37. }
  38. }
  39. }
  40. if name == "<anonymous>" {
  41. // Still walk the body: module wrappers hold named inner functions
  42. // and calls that would otherwise be lost (#528).
  43. if let Some(body) = body_of(node) {
  44. self.visit_function_body(body);
  45. }
  46. return;
  47. }
  48. let extra = Extra {
  49. docstring: crate::docstring::preceding_docstring(node, self.src),
  50. signature: self.signature_of(node),
  51. visibility: self.visibility_of(node),
  52. is_exported: Some(common_js_export || self.is_exported(node)),
  53. is_async: Some(self.is_async(node)),
  54. is_static: self.is_static(node),
  55. ..Extra::default()
  56. };
  57. let Some(row) = self.create_node("function", &name, node, extra) else {
  58. return;
  59. };
  60. self.extract_type_annotations(node, row);
  61. self.extract_decorators_for(node, row);
  62. self.stack.push(Scope { row, kind: "function", name });
  63. if let Some(body) = body_of(node) {
  64. self.visit_function_body(body);
  65. }
  66. self.stack.pop();
  67. }
  68. /// The property a CommonJS export assignment binds a function to —
  69. /// `exports.NAME = <node>` / `module.exports.NAME = <node>` — or None for
  70. /// any other assignment. The node must be the assignment's whole
  71. /// right-hand side. Mirrors TreeSitterExtractor.commonJsExportName.
  72. fn common_js_export_name(&self, assignment: Node<'t>, value: Node<'t>) -> Option<String> {
  73. let right = assignment.child_by_field_name("right")?;
  74. if right.start_byte() != value.start_byte() || right.end_byte() != value.end_byte() {
  75. return None;
  76. }
  77. let left = assignment.child_by_field_name("left")?;
  78. if left.kind() != "member_expression" {
  79. return None;
  80. }
  81. let object = left.child_by_field_name("object")?;
  82. let property = left.child_by_field_name("property")?;
  83. if property.kind() != "property_identifier" {
  84. return None;
  85. }
  86. if !matches!(self.text(object), "exports" | "module.exports") {
  87. return None;
  88. }
  89. Some(self.text(property).to_string())
  90. }
  91. // --- reactComponentHoc / extractReactComponentNode (#841) --------------------
  92. /// Some(inner) when the initializer is a recognized component wrapper —
  93. /// inner is the inline render function, or None for `styled.x`/`memo(Ref)`.
  94. /// Outer None = not a component wrapper.
  95. fn react_component_hoc(&self, value: Node<'t>) -> Option<Option<Node<'t>>> {
  96. if value.kind() != "call_expression" {
  97. return None;
  98. }
  99. let callee = value.child_by_field_name("function")?;
  100. let callee_text = self.text(callee);
  101. if util::styled_callee().is_match(callee_text) {
  102. return Some(None);
  103. }
  104. if !is_react_hoc(callee_text) {
  105. return None;
  106. }
  107. let mut inner: Option<Node> = None;
  108. if let Some(args) = value.child_by_field_name("arguments") {
  109. for i in 0..args.named_child_count() {
  110. if let Some(a) = args.named_child(i) {
  111. if matches!(a.kind(), "arrow_function" | "function_expression") {
  112. inner = Some(a);
  113. break;
  114. }
  115. }
  116. }
  117. }
  118. Some(inner)
  119. }
  120. fn extract_react_component_node(
  121. &mut self,
  122. name: &str,
  123. declarator: Node<'t>,
  124. inner_fn: Option<Node<'t>>,
  125. extra: Extra,
  126. ) {
  127. let Some(row) = self.create_node("component", name, declarator, extra) else {
  128. return;
  129. };
  130. let Some(inner) = inner_fn else { return };
  131. self.stack.push(Scope { row, kind: "component", name: name.to_string() });
  132. if let Some(body) = body_of(inner) {
  133. self.visit_function_body(body);
  134. }
  135. self.stack.pop();
  136. }
  137. // --- extractClass ------------------------------------------------------------
  138. pub(super) fn extract_class(&mut self, node: Node<'t>) {
  139. let resolved_body = body_of(node); // skipBodilessClass unset for TS/JS
  140. let name = self.extract_name(node);
  141. let extra = Extra {
  142. docstring: crate::docstring::preceding_docstring(node, self.src),
  143. visibility: self.visibility_of(node),
  144. is_exported: Some(self.is_exported(node)),
  145. ..Extra::default()
  146. };
  147. let Some(row) = self.create_node("class", &name, node, extra) else {
  148. return;
  149. };
  150. self.extract_inheritance(node, row);
  151. self.extract_decorators_for(node, row);
  152. self.stack.push(Scope { row, kind: "class", name });
  153. let body = resolved_body.unwrap_or(node);
  154. for i in 0..body.named_child_count() {
  155. if let Some(c) = body.named_child(i) {
  156. self.visit_node(c);
  157. }
  158. }
  159. self.stack.pop();
  160. }
  161. // --- extractMethod -------------------------------------------------------------
  162. pub(super) fn extract_method(&mut self, node: Node<'t>) {
  163. if !self.inside_class_like() {
  164. // Object-literal methods are ephemeral: walk the body only.
  165. if let Some(parent) = node.parent() {
  166. if matches!(parent.kind(), "object" | "object_expression") {
  167. if let Some(body) = body_of(node) {
  168. self.visit_function_body(body);
  169. }
  170. return;
  171. }
  172. }
  173. self.extract_function(node, None);
  174. return;
  175. }
  176. let name = self.extract_name(node);
  177. let extra = Extra {
  178. docstring: crate::docstring::preceding_docstring(node, self.src),
  179. signature: self.signature_of(node),
  180. visibility: self.visibility_of(node),
  181. is_async: Some(self.is_async(node)),
  182. is_static: self.is_static(node),
  183. ..Extra::default() // methods carry no isExported (mirrors extractMethod)
  184. };
  185. let Some(row) = self.create_node("method", &name, node, extra) else {
  186. return;
  187. };
  188. self.extract_type_annotations(node, row);
  189. self.extract_decorators_for(node, row);
  190. self.stack.push(Scope { row, kind: "method", name });
  191. if let Some(body) = body_of(node) {
  192. self.visit_function_body(body);
  193. }
  194. self.stack.pop();
  195. }
  196. // --- extractInterface / extractEnum / members -----------------------------------
  197. pub(super) fn extract_interface(&mut self, node: Node<'t>) {
  198. let name = self.extract_name(node);
  199. let extra = Extra {
  200. docstring: crate::docstring::preceding_docstring(node, self.src),
  201. is_exported: Some(self.is_exported(node)),
  202. ..Extra::default()
  203. };
  204. let Some(row) = self.create_node("interface", &name, node, extra) else {
  205. return;
  206. };
  207. self.extract_inheritance(node, row);
  208. self.stack.push(Scope { row, kind: "interface", name });
  209. let body = body_of(node).unwrap_or(node);
  210. for i in 0..body.named_child_count() {
  211. if let Some(c) = body.named_child(i) {
  212. self.visit_node(c);
  213. }
  214. }
  215. self.stack.pop();
  216. }
  217. pub(super) fn extract_enum(&mut self, node: Node<'t>) {
  218. let Some(body) = body_of(node) else { return };
  219. let name = self.extract_name(node);
  220. let extra = Extra {
  221. docstring: crate::docstring::preceding_docstring(node, self.src),
  222. visibility: self.visibility_of(node),
  223. is_exported: Some(self.is_exported(node)),
  224. ..Extra::default()
  225. };
  226. let Some(row) = self.create_node("enum", &name, node, extra) else {
  227. return;
  228. };
  229. self.extract_inheritance(node, row);
  230. self.stack.push(Scope { row, kind: "enum", name });
  231. for i in 0..body.named_child_count() {
  232. let Some(child) = body.named_child(i) else { continue };
  233. if matches!(child.kind(), "property_identifier" | "enum_assignment") {
  234. self.extract_enum_members(child);
  235. } else {
  236. self.visit_node(child);
  237. }
  238. }
  239. self.stack.pop();
  240. }
  241. fn extract_enum_members(&mut self, node: Node<'t>) {
  242. if let Some(name_node) = node.child_by_field_name("name") {
  243. let name = self.text(name_node).to_string();
  244. self.create_node("enum_member", &name, node, Extra::default());
  245. return;
  246. }
  247. let mut found = false;
  248. for i in 0..node.named_child_count() {
  249. if let Some(child) = node.named_child(i) {
  250. if matches!(child.kind(), "simple_identifier" | "identifier" | "property_identifier") {
  251. let name = self.text(child).to_string();
  252. self.create_node("enum_member", &name, child, Extra::default());
  253. found = true;
  254. }
  255. }
  256. }
  257. if !found && node.named_child_count() == 0 {
  258. let name = self.text(node).to_string();
  259. self.create_node("enum_member", &name, node, Extra::default());
  260. }
  261. }
  262. // --- extractProperty (#808 property-classified class fields) ---------------------
  263. pub(super) fn extract_property(&mut self, node: Node<'t>) -> Option<(u32, String)> {
  264. let docstring = crate::docstring::preceding_docstring(node, self.src);
  265. let visibility = self.visibility_of(node);
  266. let is_static = Some(self.is_static(node).unwrap_or(false)); // `?? false` — always present
  267. let name_node = node
  268. .child_by_field_name("name")
  269. .or_else(|| node.child_by_field_name("property"))
  270. .or_else(|| {
  271. (0..node.named_child_count())
  272. .filter_map(|i| node.named_child(i))
  273. .find(|c| c.kind() == "identifier")
  274. })?;
  275. let name = self.text(name_node).to_string();
  276. // TS/JS field definitions carry an explicit `type` field; the generic
  277. // scan is for other languages (#808). A `property_signature` (an
  278. // interface member, #1638) carries a `type` field and no value, so it
  279. // reads the type field too: the generic scan's exclusion list covers
  280. // `identifier` but not the `property_identifier` an interface member is
  281. // named with, so it would stop on the name and make the signature repeat
  282. // it (`counts counts`) instead of naming the type. Mirrors
  283. // extractProperty's isTsJsField.
  284. let is_ts_js_field = matches!(
  285. node.kind(),
  286. "public_field_definition" | "field_definition" | "property_signature"
  287. );
  288. let type_node = if is_ts_js_field {
  289. node.child_by_field_name("type")
  290. } else {
  291. (0..node.named_child_count()).filter_map(|i| node.named_child(i)).find(|c| {
  292. !matches!(
  293. c.kind(),
  294. "modifier"
  295. | "modifiers"
  296. | "identifier"
  297. | "accessor_list"
  298. | "accessors"
  299. | "equals_value_clause"
  300. )
  301. })
  302. };
  303. let type_text = type_node.map(|t| {
  304. let raw = self.text(t);
  305. raw.strip_prefix(':').unwrap_or(raw).trim_start().to_string()
  306. });
  307. let signature = match &type_text {
  308. Some(t) => format!("{t} {name}"),
  309. None => name.clone(),
  310. };
  311. let row = self.create_node(
  312. "property",
  313. &name,
  314. node,
  315. Extra { docstring, signature: Some(signature), visibility, is_static, ..Extra::default() },
  316. )?;
  317. self.extract_decorators_for(node, row);
  318. self.extract_type_annotations(node, row);
  319. Some((row, name))
  320. }
  321. // --- extractVariable (TS/JS branch) ------------------------------------------------
  322. /// A top-level binding exported by a LATER statement rather than at its
  323. /// declaration: `export default NAME`, `export { NAME }`, `export { NAME as
  324. /// default }`. The declaration's own `is_exported` (an `export_statement`
  325. /// ancestor) cannot see these. One anchored regex over the file source.
  326. /// Mirrors TreeSitterExtractor.isExportedLater.
  327. pub(super) fn is_exported_later(&self, name: &str) -> bool {
  328. if name.is_empty()
  329. || !name.chars().next().map(|c| c.is_ascii_alphabetic() || c == '_' || c == '$').unwrap_or(false)
  330. || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
  331. {
  332. return false;
  333. }
  334. let n = regex::escape(name);
  335. let pattern = format!(
  336. r"(?m)^[ \t]*export\s+(?:default\s+{n}\s*;?[ \t]*$|\{{[^}}]*\b{n}\b[^}}]*\}})",
  337. n = n
  338. );
  339. match regex::Regex::new(&pattern) {
  340. Ok(re) => re.is_match(self.src),
  341. Err(_) => false,
  342. }
  343. }
  344. pub(super) fn extract_variable(&mut self, node: Node<'t>) {
  345. let is_const = self.is_const_decl(node);
  346. let kind: &'static str = if is_const { "constant" } else { "variable" };
  347. let docstring = crate::docstring::preceding_docstring(node, self.src);
  348. let is_exported = self.is_exported(node); // `?? false` — always present
  349. for i in 0..node.named_child_count() {
  350. let Some(child) = node.named_child(i) else { continue };
  351. if child.kind() != "variable_declarator" {
  352. continue;
  353. }
  354. let Some(name_node) = child.child_by_field_name("name") else { continue };
  355. let value = child.child_by_field_name("value");
  356. // Destructured patterns are skipped — except RTK Query generated
  357. // hooks (`export const { useGetXQuery } = api`).
  358. if matches!(name_node.kind(), "object_pattern" | "array_pattern") {
  359. if name_node.kind() == "object_pattern"
  360. && value.map(|v| v.kind() == "identifier").unwrap_or(false)
  361. {
  362. self.extract_rtk_hook_bindings(name_node, is_exported);
  363. }
  364. continue;
  365. }
  366. let name = self.text(name_node).to_string();
  367. // Arrow/function/generator values extract as functions, named by the declarator.
  368. if let Some(v) = value {
  369. if matches!(v.kind(), "arrow_function" | "function_expression" | "generator_function") {
  370. self.extract_function(v, None);
  371. continue;
  372. }
  373. }
  374. let init_signature = value.map(|v| util::init_signature(self.text(v)));
  375. // React HOC-wrapped components (#841), PascalCase-gated.
  376. if let Some(v) = value {
  377. if util::pascal_case().is_match(&name) {
  378. if let Some(inner) = self.react_component_hoc(v) {
  379. self.extract_react_component_node(
  380. &name,
  381. child,
  382. inner,
  383. Extra {
  384. docstring: docstring.clone(),
  385. signature: init_signature.clone(),
  386. is_exported: Some(is_exported),
  387. ..Extra::default()
  388. },
  389. );
  390. continue;
  391. }
  392. }
  393. }
  394. let var_row = self.create_node(
  395. kind,
  396. &name,
  397. child,
  398. Extra {
  399. docstring: docstring.clone(),
  400. signature: init_signature.clone(),
  401. is_exported: Some(is_exported),
  402. ..Extra::default()
  403. },
  404. );
  405. if let Some(row) = var_row {
  406. self.extract_variable_type_annotation(child, row);
  407. }
  408. // Exported const object-of-functions / store shapes.
  409. let object_of_fns: Option<Node> = match value {
  410. Some(v) if matches!(v.kind(), "object" | "object_expression") => Some(v),
  411. Some(v) if v.kind() == "call_expression" => self.find_initializer_returned_object(v, 0),
  412. _ => None,
  413. };
  414. let has_inline_fns = object_of_fns
  415. .map(|o| self.object_has_inline_functions(o))
  416. .unwrap_or(false);
  417. // "Exported" includes the two-statement form `const useStore =
  418. // create(…)` … `export default useStore` (is_exported_later), the
  419. // shape most React Native stores are written in. Mirrors
  420. // TreeSitterExtractor.isExportedLater.
  421. let extract_object_methods =
  422. (is_exported || self.is_exported_later(&name)) && object_of_fns.is_some() && has_inline_fns;
  423. let rtk_endpoints = match value {
  424. Some(v) if v.kind() == "call_expression" => self.find_rtk_endpoints_object(v),
  425. _ => None,
  426. };
  427. let pinia_setup = match value {
  428. Some(v) if v.kind() == "call_expression" => self.find_pinia_setup_fn(v),
  429. _ => None,
  430. };
  431. let mut store_collections: Vec<Node> = Vec::new();
  432. if let Some(v) = value {
  433. if matches!(v.kind(), "call_expression" | "new_expression") {
  434. store_collections.extend(self.find_vue_store_collection_objects(v));
  435. }
  436. }
  437. if let Some(obj) = object_of_fns {
  438. if !extract_object_methods
  439. && is_vue_collection_name(&name)
  440. && self.looks_like_vue_store_file()
  441. {
  442. store_collections.push(obj);
  443. }
  444. }
  445. // Walk the initializer for calls, ATTRIBUTED to the declared symbol
  446. // (#693) — except the object/store shapes whose members are
  447. // extracted method-by-method below (walking those too would
  448. // double-count each member arrow's calls). Before this the walk ran
  449. // with only the FILE on the stack (`const cfg = load()` recorded the
  450. // file as load's caller) and object literals were skipped outright.
  451. let members_extracted_separately = extract_object_methods
  452. || rtk_endpoints.is_some()
  453. || pinia_setup.is_some()
  454. || !store_collections.is_empty();
  455. if let Some(v) = value {
  456. if !members_extracted_separately {
  457. match var_row {
  458. Some(row) => {
  459. self.stack.push(Scope { row, kind, name: name.clone() });
  460. self.visit_function_body(v);
  461. self.stack.pop();
  462. }
  463. None => self.visit_function_body(v),
  464. }
  465. }
  466. }
  467. if extract_object_methods {
  468. if let Some(obj) = object_of_fns {
  469. self.extract_object_literal_functions(obj);
  470. }
  471. }
  472. if let Some(rtk) = rtk_endpoints {
  473. self.extract_rtk_endpoints(rtk);
  474. }
  475. if let Some(setup) = pinia_setup {
  476. self.extract_pinia_setup_body(setup);
  477. }
  478. for coll in store_collections {
  479. self.extract_object_literal_functions(coll);
  480. }
  481. }
  482. }
  483. /// extractRtkHookBindings — `export const { useGetXQuery } = api`.
  484. fn extract_rtk_hook_bindings(&mut self, pattern: Node<'t>, is_exported: bool) {
  485. for i in 0..pattern.named_child_count() {
  486. let Some(binding) = pattern.named_child(i) else { continue };
  487. if binding.kind() != "shorthand_property_identifier_pattern" {
  488. continue;
  489. }
  490. let name = self.text(binding).to_string();
  491. if !util::rtk_hook_name().is_match(&name) {
  492. continue;
  493. }
  494. self.create_node(
  495. "function",
  496. &name,
  497. binding,
  498. Extra {
  499. is_exported: Some(is_exported),
  500. signature: Some("= RTK Query generated hook".to_string()),
  501. ..Extra::default()
  502. },
  503. );
  504. }
  505. }
  506. // --- object-literal / store helpers -------------------------------------------------
  507. pub(super) fn extract_object_literal_functions(&mut self, obj: Node<'t>) {
  508. for i in 0..obj.named_child_count() {
  509. let Some(member) = obj.named_child(i) else { continue };
  510. if member.kind() == "pair" {
  511. let key = member.child_by_field_name("key");
  512. let value = member.child_by_field_name("value");
  513. if let (Some(k), Some(v)) = (key, value) {
  514. if matches!(v.kind(), "arrow_function" | "function_expression") {
  515. let name = util::object_key_name(self.text(k));
  516. self.extract_function(v, Some(name));
  517. }
  518. }
  519. } else if member.kind() == "method_definition" {
  520. if let Some(k) = member.child_by_field_name("name") {
  521. let name = util::object_key_name(self.text(k));
  522. self.extract_function(member, Some(name));
  523. }
  524. }
  525. }
  526. }
  527. fn find_initializer_returned_object(&self, call: Node<'t>, depth: u32) -> Option<Node<'t>> {
  528. stack_guard!();
  529. if depth > 4 {
  530. return None;
  531. }
  532. let args = call.child_by_field_name("arguments")?;
  533. for i in 0..args.named_child_count() {
  534. let Some(arg) = args.named_child(i) else { continue };
  535. if matches!(arg.kind(), "arrow_function" | "function_expression") {
  536. if let Some(obj) = self.function_returned_object(arg) {
  537. return Some(obj);
  538. }
  539. } else if arg.kind() == "call_expression" {
  540. if let Some(obj) = self.find_initializer_returned_object(arg, depth + 1) {
  541. return Some(obj);
  542. }
  543. }
  544. }
  545. None
  546. }
  547. fn function_returned_object(&self, fn_node: Node<'t>) -> Option<Node<'t>> {
  548. fn as_object<'t>(n: Node<'t>) -> Option<Node<'t>> {
  549. stack_guard!();
  550. match n.kind() {
  551. "object" | "object_expression" => Some(n),
  552. "parenthesized_expression" => {
  553. for i in 0..n.named_child_count() {
  554. if let Some(inner) = n.named_child(i).and_then(as_object) {
  555. return Some(inner);
  556. }
  557. }
  558. None
  559. }
  560. _ => None,
  561. }
  562. }
  563. let body = fn_node.child_by_field_name("body")?;
  564. if let Some(direct) = as_object(body) {
  565. return Some(direct);
  566. }
  567. if body.kind() == "statement_block" {
  568. for i in 0..body.named_child_count() {
  569. let Some(stmt) = body.named_child(i) else { continue };
  570. if stmt.kind() != "return_statement" {
  571. continue;
  572. }
  573. for j in 0..stmt.named_child_count() {
  574. if let Some(obj) = stmt.named_child(j).and_then(as_object) {
  575. return Some(obj);
  576. }
  577. }
  578. }
  579. }
  580. None
  581. }
  582. pub(super) fn object_has_inline_functions(&self, obj: Node) -> bool {
  583. for i in 0..obj.named_child_count() {
  584. let Some(member) = obj.named_child(i) else { continue };
  585. if member.kind() == "method_definition" {
  586. return true;
  587. }
  588. if member.kind() == "pair" {
  589. if let Some(v) = member.child_by_field_name("value") {
  590. if matches!(v.kind(), "arrow_function" | "function_expression") {
  591. return true;
  592. }
  593. }
  594. }
  595. }
  596. false
  597. }
  598. fn find_rtk_endpoints_object(&self, call: Node<'t>) -> Option<Node<'t>> {
  599. let callee = call.child_by_field_name("function")?;
  600. let callee_name = match callee.kind() {
  601. "identifier" => self.text(callee),
  602. "member_expression" => {
  603. let prop = callee.child_by_field_name("property").unwrap_or(callee);
  604. self.text(prop)
  605. }
  606. _ => "",
  607. };
  608. if callee_name != "createApi" && callee_name != "injectEndpoints" {
  609. return None;
  610. }
  611. let args = call.child_by_field_name("arguments")?;
  612. for i in 0..args.named_child_count() {
  613. let Some(arg) = args.named_child(i) else { continue };
  614. if !matches!(arg.kind(), "object" | "object_expression") {
  615. continue;
  616. }
  617. for j in 0..arg.named_child_count() {
  618. let Some(member) = arg.named_child(j) else { continue };
  619. if member.kind() == "pair" {
  620. let Some(key) = member.child_by_field_name("key") else { continue };
  621. if self.text(key) != "endpoints" {
  622. continue;
  623. }
  624. if let Some(value) = member.child_by_field_name("value") {
  625. if matches!(value.kind(), "arrow_function" | "function_expression") {
  626. return self.function_returned_object(value);
  627. }
  628. }
  629. } else if member.kind() == "method_definition" {
  630. let Some(key) = member.child_by_field_name("name") else { continue };
  631. if self.text(key) != "endpoints" {
  632. continue;
  633. }
  634. return self.function_returned_object(member);
  635. }
  636. }
  637. }
  638. None
  639. }
  640. fn extract_rtk_endpoints(&mut self, obj: Node<'t>) {
  641. for i in 0..obj.named_child_count() {
  642. let Some(member) = obj.named_child(i) else { continue };
  643. if member.kind() != "pair" {
  644. continue;
  645. }
  646. let key = member.child_by_field_name("key");
  647. let value = member.child_by_field_name("value");
  648. let (Some(key), Some(value)) = (key, value) else { continue };
  649. if value.kind() != "call_expression" {
  650. continue;
  651. }
  652. let Some(callee) = value.child_by_field_name("function") else { continue };
  653. if callee.kind() != "member_expression" {
  654. continue;
  655. }
  656. let method = self.text(callee.child_by_field_name("property").unwrap_or(callee));
  657. if method != "query" && method != "mutation" && method != "infiniteQuery" {
  658. continue;
  659. }
  660. let key_name = util::object_key_name(self.text(key));
  661. if let Some(handler) = self.rtk_endpoint_handler(value) {
  662. self.extract_function(handler, Some(key_name));
  663. } else {
  664. // Config-only endpoint: bare node spanning the builder call.
  665. let (sig, _) = util::slice_utf16(self.text(value), 80);
  666. let row = self.create_node(
  667. "function",
  668. &key_name,
  669. value,
  670. Extra { signature: Some(sig), ..Extra::default() },
  671. );
  672. if let Some(row) = row {
  673. self.stack.push(Scope { row, kind: "function", name: key_name });
  674. self.visit_function_body(value);
  675. self.stack.pop();
  676. }
  677. }
  678. }
  679. }
  680. fn rtk_endpoint_handler(&self, call: Node<'t>) -> Option<Node<'t>> {
  681. let args = call.child_by_field_name("arguments")?;
  682. for i in 0..args.named_child_count() {
  683. let Some(arg) = args.named_child(i) else { continue };
  684. if !matches!(arg.kind(), "object" | "object_expression") {
  685. continue;
  686. }
  687. let mut query_fn: Option<Node> = None;
  688. let mut query: Option<Node> = None;
  689. let mut first_fn: Option<Node> = None;
  690. for j in 0..arg.named_child_count() {
  691. let Some(member) = arg.named_child(j) else { continue };
  692. let mut fn_node: Option<Node> = None;
  693. let mut key_name = "";
  694. if member.kind() == "pair" {
  695. if let Some(v) = member.child_by_field_name("value") {
  696. if matches!(v.kind(), "arrow_function" | "function_expression") {
  697. fn_node = Some(v);
  698. if let Some(k) = member.child_by_field_name("key") {
  699. key_name = self.text(k);
  700. }
  701. }
  702. }
  703. } else if member.kind() == "method_definition" {
  704. fn_node = Some(member);
  705. if let Some(k) = member.child_by_field_name("name") {
  706. key_name = self.text(k);
  707. }
  708. }
  709. let Some(f) = fn_node else { continue };
  710. if key_name == "queryFn" {
  711. query_fn = Some(f);
  712. } else if key_name == "query" {
  713. query = Some(f);
  714. }
  715. if first_fn.is_none() {
  716. first_fn = Some(f);
  717. }
  718. }
  719. if let Some(f) = query_fn.or(query).or(first_fn) {
  720. return Some(f);
  721. }
  722. }
  723. None
  724. }
  725. pub(super) fn looks_like_vue_store_file(&mut self) -> bool {
  726. if let Some(v) = self.vue_store_file {
  727. return v;
  728. }
  729. let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
  730. for m in util::vue_store_signal().find_iter(self.src) {
  731. seen.insert(m.as_str());
  732. if seen.len() >= 2 {
  733. break;
  734. }
  735. }
  736. let v = seen.len() >= 2;
  737. self.vue_store_file = Some(v);
  738. v
  739. }
  740. fn find_vue_store_collection_objects(&self, call: Node<'t>) -> Vec<Node<'t>> {
  741. let callee = call
  742. .child_by_field_name("function")
  743. .or_else(|| call.child_by_field_name("constructor"));
  744. let Some(callee) = callee else { return vec![] };
  745. let callee_name = match callee.kind() {
  746. "identifier" => self.text(callee),
  747. "member_expression" => self.text(callee.child_by_field_name("property").unwrap_or(callee)),
  748. _ => "",
  749. };
  750. if !matches!(callee_name, "defineStore" | "createStore" | "Store") {
  751. return vec![];
  752. }
  753. let Some(args) = call.child_by_field_name("arguments") else { return vec![] };
  754. let mut objects = Vec::new();
  755. for i in 0..args.named_child_count() {
  756. let Some(arg) = args.named_child(i) else { continue };
  757. if !matches!(arg.kind(), "object" | "object_expression") {
  758. continue;
  759. }
  760. for j in 0..arg.named_child_count() {
  761. let Some(member) = arg.named_child(j) else { continue };
  762. if member.kind() != "pair" {
  763. continue;
  764. }
  765. let Some(key) = member.child_by_field_name("key") else { continue };
  766. if !is_vue_collection_name(self.text(key)) {
  767. continue;
  768. }
  769. if let Some(value) = member.child_by_field_name("value") {
  770. if matches!(value.kind(), "object" | "object_expression") {
  771. objects.push(value);
  772. }
  773. }
  774. }
  775. }
  776. objects
  777. }
  778. pub(super) fn extract_store_collection_methods(&mut self, config: Node<'t>) {
  779. for i in 0..config.named_child_count() {
  780. let Some(member) = config.named_child(i) else { continue };
  781. if member.kind() != "pair" {
  782. continue;
  783. }
  784. let Some(key) = member.child_by_field_name("key") else { continue };
  785. if !is_vue_collection_name(self.text(key)) {
  786. continue;
  787. }
  788. if let Some(value) = member.child_by_field_name("value") {
  789. if matches!(value.kind(), "object" | "object_expression") {
  790. self.extract_object_literal_functions(value);
  791. }
  792. }
  793. }
  794. }
  795. fn find_pinia_setup_fn(&self, call: Node<'t>) -> Option<Node<'t>> {
  796. let callee = call.child_by_field_name("function")?;
  797. if callee.kind() != "identifier" || self.text(callee) != "defineStore" {
  798. return None;
  799. }
  800. let args = call.child_by_field_name("arguments")?;
  801. for i in 0..args.named_child_count() {
  802. let Some(arg) = args.named_child(i) else { continue };
  803. if !matches!(arg.kind(), "arrow_function" | "function_expression") {
  804. continue;
  805. }
  806. if let Some(body) = arg.child_by_field_name("body") {
  807. if body.kind() == "statement_block" {
  808. return Some(arg);
  809. }
  810. }
  811. }
  812. None
  813. }
  814. fn extract_pinia_setup_body(&mut self, setup: Node<'t>) {
  815. let Some(body) = setup.child_by_field_name("body") else { return };
  816. if body.kind() != "statement_block" {
  817. return;
  818. }
  819. for i in 0..body.named_child_count() {
  820. let Some(stmt) = body.named_child(i) else { continue };
  821. if stmt.kind() == "function_declaration" {
  822. self.extract_function(stmt, None);
  823. } else if is_variable_type(stmt.kind()) {
  824. for j in 0..stmt.named_child_count() {
  825. let Some(decl) = stmt.named_child(j) else { continue };
  826. if decl.kind() != "variable_declarator" {
  827. continue;
  828. }
  829. if let Some(v) = decl.child_by_field_name("value") {
  830. if matches!(v.kind(), "arrow_function" | "function_expression") {
  831. self.extract_function(v, None);
  832. }
  833. }
  834. }
  835. }
  836. }
  837. }
  838. // --- extractTypeAlias + members (#359, #634) -------------------------------------
  839. /// Returns skipChildren (always false on the TS path — the alias value is
  840. /// still traversed by the dispatcher).
  841. pub(super) fn extract_type_alias(&mut self, node: Node<'t>) -> bool {
  842. let name = self.extract_name(node);
  843. if name == "<anonymous>" {
  844. return false;
  845. }
  846. let extra = Extra {
  847. docstring: crate::docstring::preceding_docstring(node, self.src),
  848. is_exported: Some(self.is_exported(node)),
  849. ..Extra::default()
  850. };
  851. let Some(row) = self.create_node("type_alias", &name, node, extra) else {
  852. return false;
  853. };
  854. if let Some(value) = node.child_by_field_name("value") {
  855. self.extract_type_refs_from_subtree(value, row);
  856. self.extract_ts_type_alias_members(value, row, &name);
  857. self.extract_ts_tuple_contract_names(value, row, &name);
  858. }
  859. false
  860. }
  861. fn extract_ts_type_alias_members(&mut self, value: Node<'t>, alias_row: u32, alias_name: &str) {
  862. let mut object_types: Vec<Node> = Vec::new();
  863. if value.kind() == "object_type" {
  864. object_types.push(value);
  865. } else if value.kind() == "intersection_type" {
  866. for i in 0..value.named_child_count() {
  867. if let Some(op) = value.named_child(i) {
  868. if op.kind() == "object_type" {
  869. object_types.push(op);
  870. }
  871. }
  872. }
  873. } else {
  874. return;
  875. }
  876. self.stack.push(Scope { row: alias_row, kind: "type_alias", name: alias_name.to_string() });
  877. for obj_type in object_types {
  878. for i in 0..obj_type.named_child_count() {
  879. let Some(child) = obj_type.named_child(i) else { continue };
  880. if !matches!(child.kind(), "property_signature" | "method_signature") {
  881. continue;
  882. }
  883. let Some(name_node) = child.child_by_field_name("name") else { continue };
  884. let member_name = self.text(name_node).to_string();
  885. if member_name.is_empty() {
  886. continue;
  887. }
  888. let member_kind: &'static str = if child.kind() == "method_signature"
  889. || self.is_ts_function_typed_property(child)
  890. {
  891. "method"
  892. } else {
  893. "property"
  894. };
  895. let extra = Extra {
  896. docstring: crate::docstring::preceding_docstring(child, self.src),
  897. signature: Some(self.text(child).to_string()),
  898. qualified_name: Some(format!("{alias_name}::{member_name}")),
  899. ..Extra::default()
  900. };
  901. self.create_node(member_kind, &member_name, child, extra);
  902. self.extract_type_annotations(child, alias_row);
  903. }
  904. }
  905. self.stack.pop();
  906. }
  907. fn extract_ts_tuple_contract_names(&mut self, value: Node<'t>, alias_row: u32, alias_name: &str) {
  908. let mut tuples: Vec<Node> = Vec::new();
  909. fn collect<'t>(n: Node<'t>, depth: u32, out: &mut Vec<Node<'t>>) {
  910. stack_guard!();
  911. if depth > 6 {
  912. return;
  913. }
  914. if n.kind() == "tuple_type" {
  915. out.push(n);
  916. }
  917. for i in 0..n.named_child_count() {
  918. if let Some(c) = n.named_child(i) {
  919. collect(c, depth + 1, out);
  920. }
  921. }
  922. }
  923. collect(value, 0, &mut tuples);
  924. if tuples.is_empty() {
  925. return;
  926. }
  927. self.stack.push(Scope { row: alias_row, kind: "type_alias", name: alias_name.to_string() });
  928. for tuple in tuples {
  929. for i in 0..tuple.named_child_count() {
  930. let Some(entry) = tuple.named_child(i) else { continue };
  931. if entry.kind() != "generic_type" {
  932. continue;
  933. }
  934. let Some(type_args) = entry.child_by_field_name("type_arguments") else { continue };
  935. for j in 0..type_args.named_child_count() {
  936. let Some(arg) = type_args.named_child(j) else { continue };
  937. if arg.kind() != "literal_type" {
  938. continue;
  939. }
  940. let Some(str_node) = arg.named_child(0) else { continue };
  941. if str_node.kind() != "string" {
  942. continue;
  943. }
  944. let name = util::object_key_name(self.text(str_node).trim());
  945. if !util::ident_dollar().is_match(&name) {
  946. continue;
  947. }
  948. let collapsed = collapse_ws(self.text(entry));
  949. let (signature, _) = util::slice_utf16(collapsed.trim(), 120);
  950. let extra = Extra {
  951. signature: Some(signature),
  952. qualified_name: Some(format!("{alias_name}::{name}")),
  953. ..Extra::default()
  954. };
  955. self.create_node("method", &name, entry, extra);
  956. }
  957. }
  958. }
  959. self.stack.pop();
  960. }
  961. fn is_ts_function_typed_property(&self, property_signature: Node) -> bool {
  962. let Some(type_anno) = property_signature.child_by_field_name("type") else {
  963. return false;
  964. };
  965. for i in 0..type_anno.named_child_count() {
  966. if let Some(inner) = type_anno.named_child(i) {
  967. if inner.kind() == "function_type" {
  968. return true;
  969. }
  970. }
  971. }
  972. false
  973. }
  974. // --- extractImport + binding refs ---------------------------------------------------
  975. pub(super) fn extract_import(&mut self, node: Node<'t>) {
  976. let import_text = self.text(node).trim().to_string();
  977. // typescriptExtractor.extractImport: the `source` field, quotes stripped
  978. // globally. A missing/empty module means the hook declined — no node.
  979. let Some(source_field) = node.child_by_field_name("source") else { return };
  980. let module_name: String = self
  981. .text(source_field)
  982. .chars()
  983. .filter(|c| *c != '\'' && *c != '"')
  984. .collect();
  985. if module_name.is_empty() {
  986. return;
  987. }
  988. self.create_node(
  989. "import",
  990. &module_name,
  991. node,
  992. Extra { signature: Some(import_text), ..Extra::default() },
  993. );
  994. let parent = self.top_row();
  995. self.push_ref(parent, &module_name.clone(), edge_kind_index("imports").unwrap(), node);
  996. self.emit_import_binding_refs(node, parent);
  997. }
  998. fn emit_import_binding_refs(&mut self, node: Node<'t>, from_row: u32) {
  999. let clause = (0..node.named_child_count())
  1000. .filter_map(|i| node.named_child(i))
  1001. .find(|c| c.kind() == "import_clause");
  1002. let Some(clause) = clause else { return }; // side-effect import
  1003. let imports_kind = edge_kind_index("imports").unwrap();
  1004. let push = |w: &mut Self, name_node: Option<Node>| {
  1005. let Some(n) = name_node else { return };
  1006. let name = w.text(n).to_string();
  1007. if name.is_empty() {
  1008. return;
  1009. }
  1010. w.push_ref(from_row, &name, imports_kind, n);
  1011. };
  1012. for i in 0..clause.named_child_count() {
  1013. let Some(child) = clause.named_child(i) else { continue };
  1014. match child.kind() {
  1015. "identifier" => push(self, Some(child)),
  1016. "named_imports" => {
  1017. for j in 0..child.named_child_count() {
  1018. let Some(spec) = child.named_child(j) else { continue };
  1019. if spec.kind() != "import_specifier" {
  1020. continue;
  1021. }
  1022. let n = spec
  1023. .child_by_field_name("alias")
  1024. .or_else(|| spec.child_by_field_name("name"))
  1025. .or_else(|| spec.named_child(0));
  1026. push(self, n);
  1027. }
  1028. }
  1029. "namespace_import" => {
  1030. let n = (0..child.named_child_count())
  1031. .filter_map(|k| child.named_child(k))
  1032. .find(|c| c.kind() == "identifier")
  1033. .or_else(|| child.named_child(0));
  1034. push(self, n);
  1035. }
  1036. _ => {}
  1037. }
  1038. }
  1039. }
  1040. pub(super) fn emit_re_export_refs(&mut self, node: Node<'t>) {
  1041. let from_row = self.top_row();
  1042. let clause = (0..node.named_child_count())
  1043. .filter_map(|i| node.named_child(i))
  1044. .find(|c| c.kind() == "export_clause");
  1045. let Some(clause) = clause else { return }; // `export * from './y'`
  1046. let imports_kind = edge_kind_index("imports").unwrap();
  1047. for i in 0..clause.named_child_count() {
  1048. let Some(spec) = clause.named_child(i) else { continue };
  1049. if spec.kind() != "export_specifier" {
  1050. continue;
  1051. }
  1052. let name_node = spec.child_by_field_name("name").or_else(|| spec.named_child(0));
  1053. let Some(n) = name_node else { continue };
  1054. let name = self.text(n).to_string();
  1055. if name.is_empty() || name == "default" {
  1056. continue;
  1057. }
  1058. self.push_ref(from_row, &name, imports_kind, n);
  1059. }
  1060. }
  1061. // --- extractCall (TS/JS generic tail) -------------------------------------------------
  1062. /// Identifier-rooted member chains have no inferred property type (#1566),
  1063. /// including host API chains (#1707). Keep the existing window namespace
  1064. /// escape; call-result and `this` receivers are outside this guard.
  1065. fn is_unresolved_member_chain(&self, receiver: Node<'t>) -> bool {
  1066. let mut cur = receiver;
  1067. if !matches!(cur.kind(), "member_expression" | "subscript_expression") {
  1068. return false;
  1069. }
  1070. while matches!(cur.kind(), "member_expression" | "subscript_expression") {
  1071. match cur.child_by_field_name("object") {
  1072. Some(next) => cur = next,
  1073. None => return false,
  1074. }
  1075. }
  1076. cur.kind() == "identifier" && self.text(cur) != "window"
  1077. }
  1078. pub(super) fn extract_call(&mut self, node: Node<'t>) {
  1079. if self.stack.is_empty() {
  1080. return;
  1081. }
  1082. let func = node
  1083. .child_by_field_name("function")
  1084. .or_else(|| node.named_child(0));
  1085. let mut callee_name = String::new();
  1086. if let Some(func) = func {
  1087. if func.kind() == "member_expression" {
  1088. let property = func
  1089. .child_by_field_name("property")
  1090. .or_else(|| func.child_by_field_name("field"))
  1091. .or_else(|| func.named_child(1));
  1092. if let Some(property) = property {
  1093. let method_name = self.text(property);
  1094. let receiver = func
  1095. .child_by_field_name("object")
  1096. .or_else(|| func.child_by_field_name("operand"))
  1097. .or_else(|| func.child_by_field_name("argument"))
  1098. .or_else(|| func.named_child(0));
  1099. // Literal receivers call builtins, never project symbols (#1230).
  1100. if let Some(r) = receiver {
  1101. if is_literal_receiver(r.kind()) {
  1102. return;
  1103. }
  1104. }
  1105. let recv_ident = receiver.filter(|r| {
  1106. matches!(r.kind(), "identifier" | "simple_identifier" | "field_identifier")
  1107. });
  1108. if let Some(r) = recv_ident {
  1109. let receiver_name = self.text(r);
  1110. if !matches!(receiver_name, "self" | "this" | "cls" | "super") {
  1111. callee_name = format!("{receiver_name}.{method_name}");
  1112. } else {
  1113. callee_name = method_name.to_string();
  1114. }
  1115. } else if receiver.is_some_and(|r| self.is_unresolved_member_chain(r)) {
  1116. // Retain the call site for effects without guessing a
  1117. // project method. Mirrors the TS extraction path.
  1118. let chain = self.text(func).replace("?.", ".");
  1119. let Some(chain) = Self::plain_member_name(&chain) else { return };
  1120. callee_name = chain;
  1121. } else if let Some(field) = receiver.and_then(|r| self.this_field_of(r)) {
  1122. // `this.<field>.<method>()` — keep the field so the
  1123. // resolver can read its declared type (#1496). Mirrors
  1124. // TreeSitterExtractor.extractCall.
  1125. callee_name = format!("this.{field}.{method_name}");
  1126. } else if let Some(r) = receiver.filter(|r| r.kind() == "call_expression") {
  1127. // Call receiver — `make().run()` (#1683): keep the inner
  1128. // callee as `<inner>().<method>`, or emit nothing when it
  1129. // is not a plain name / member chain. Mirrors
  1130. // TreeSitterExtractor.extractCall.
  1131. let Some(inner) = self.plain_inner_callee(r) else { return };
  1132. callee_name = format!("{inner}().{method_name}");
  1133. } else {
  1134. callee_name = method_name.to_string();
  1135. }
  1136. }
  1137. } else {
  1138. callee_name = self.text(func).to_string();
  1139. }
  1140. }
  1141. // Parenthesized-callee normalization (`(fn)()` → fn).
  1142. if !callee_name.is_empty() {
  1143. if let Some(c) = util::paren_conversion().captures(&callee_name) {
  1144. callee_name = c[1].to_string();
  1145. }
  1146. }
  1147. if !callee_name.is_empty() {
  1148. self.push_call_ref(&callee_name.clone(), node);
  1149. }
  1150. }
  1151. // --- extractInstantiation -----------------------------------------------------------
  1152. /// `this.<field>` as a member_expression receiver → Some(field) (#1496).
  1153. fn this_field_of(&self, receiver: Node<'t>) -> Option<String> {
  1154. if receiver.kind() != "member_expression" {
  1155. return None;
  1156. }
  1157. let object = receiver.child_by_field_name("object")?;
  1158. let property = receiver.child_by_field_name("property")?;
  1159. if object.kind() != "this" || property.kind() != "property_identifier" {
  1160. return None;
  1161. }
  1162. Some(self.text(property).to_string())
  1163. }
  1164. /// The callee of a call-expression receiver when it is a plain identifier
  1165. /// or member chain (`make`, `d.setdefault`), whitespace stripped (#1683).
  1166. fn plain_inner_callee(&self, call: Node<'t>) -> Option<String> {
  1167. let inner = call.child_by_field_name("function")?;
  1168. Self::plain_member_name(self.text(inner))
  1169. }
  1170. fn plain_member_name(source: &str) -> Option<String> {
  1171. let text: String = source.chars().filter(|c| !c.is_whitespace()).collect();
  1172. if text.is_empty() {
  1173. return None;
  1174. }
  1175. let ok = text.split('.').all(|seg| {
  1176. let mut chars = seg.chars();
  1177. matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '$')
  1178. && chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
  1179. });
  1180. if ok { Some(text) } else { None }
  1181. }
  1182. pub(super) fn extract_instantiation(&mut self, node: Node<'t>) {
  1183. if self.stack.is_empty() {
  1184. return;
  1185. }
  1186. let ctor = node
  1187. .child_by_field_name("constructor")
  1188. .or_else(|| node.child_by_field_name("type"))
  1189. .or_else(|| node.child_by_field_name("name"))
  1190. .or_else(|| node.named_child(0));
  1191. let Some(ctor) = ctor else { return };
  1192. let mut class_name = self.text(ctor).to_string();
  1193. // `new Map<K, V>()` → Map.
  1194. if let Some(lt) = class_name.find('<') {
  1195. if lt > 0 {
  1196. class_name.truncate(lt);
  1197. }
  1198. }
  1199. // `new ns.Foo()` → Foo.
  1200. let last_dot = class_name
  1201. .rfind('.')
  1202. .map(|i| i as isize)
  1203. .unwrap_or(-1)
  1204. .max(class_name.rfind("::").map(|i| i as isize).unwrap_or(-1));
  1205. if last_dot >= 0 {
  1206. class_name = class_name[(last_dot as usize + 1)..].to_string();
  1207. // TS: .replace(/^[:.]/, '') — one leading colon-or-dot.
  1208. if class_name.starts_with(':') || class_name.starts_with('.') {
  1209. class_name.remove(0);
  1210. }
  1211. }
  1212. let class_name = class_name.trim().to_string();
  1213. if !class_name.is_empty() {
  1214. let from = self.top_row();
  1215. self.push_ref(from, &class_name, edge_kind_index("instantiates").unwrap(), node);
  1216. }
  1217. }
  1218. // --- extractDecoratorsFor --------------------------------------------------------------
  1219. pub(super) fn extract_decorators_for(&mut self, decl: Node<'t>, decorated_row: u32) {
  1220. // 1. Direct children (method/property style).
  1221. for i in 0..decl.named_child_count() {
  1222. let Some(child) = decl.named_child(i) else { continue };
  1223. self.consider_decorator(child, decorated_row);
  1224. if child.kind() == "modifiers" {
  1225. for j in 0..child.named_child_count() {
  1226. if let Some(m) = child.named_child(j) {
  1227. self.consider_decorator(m, decorated_row);
  1228. }
  1229. }
  1230. }
  1231. }
  1232. // 2. Preceding siblings (TypeScript class style), stopping at the
  1233. // first non-decorator so an earlier declaration's decorators never
  1234. // leak in. Matching by startIndex, not object identity.
  1235. let Some(parent) = decl.parent() else { return };
  1236. let decl_start = decl.start_byte();
  1237. let mut decl_idx: isize = -1;
  1238. for i in 0..parent.named_child_count() {
  1239. if let Some(sib) = parent.named_child(i) {
  1240. if sib.start_byte() == decl_start {
  1241. decl_idx = i as isize;
  1242. break;
  1243. }
  1244. }
  1245. }
  1246. if decl_idx > 0 {
  1247. let mut j = decl_idx - 1;
  1248. while j >= 0 {
  1249. let Some(sib) = parent.named_child(j as usize) else {
  1250. j -= 1;
  1251. continue;
  1252. };
  1253. if !matches!(sib.kind(), "decorator" | "annotation" | "marker_annotation") {
  1254. break;
  1255. }
  1256. self.consider_decorator(sib, decorated_row);
  1257. j -= 1;
  1258. }
  1259. }
  1260. }
  1261. fn consider_decorator(&mut self, n: Node<'t>, decorated_row: u32) {
  1262. if !matches!(n.kind(), "decorator" | "annotation" | "marker_annotation" | "attribute") {
  1263. return;
  1264. }
  1265. let mut target: Option<Node> = None;
  1266. for i in 0..n.named_child_count() {
  1267. let Some(child) = n.named_child(i) else { continue };
  1268. if child.kind() == "call_expression" {
  1269. target = child.child_by_field_name("function").or_else(|| child.named_child(0));
  1270. if target.is_some() {
  1271. break;
  1272. }
  1273. }
  1274. if matches!(
  1275. child.kind(),
  1276. "identifier" | "member_expression" | "scoped_identifier" | "navigation_expression"
  1277. | "user_type" | "type_identifier"
  1278. ) {
  1279. target = Some(child);
  1280. break;
  1281. }
  1282. }
  1283. let Some(target) = target else { return };
  1284. let mut name = self.text(target).to_string();
  1285. if let Some(lt) = name.find('<') {
  1286. if lt > 0 {
  1287. name.truncate(lt);
  1288. }
  1289. }
  1290. let last_dot = name
  1291. .rfind('.')
  1292. .map(|i| i as isize)
  1293. .unwrap_or(-1)
  1294. .max(name.rfind("::").map(|i| i as isize).unwrap_or(-1));
  1295. if last_dot >= 0 {
  1296. name = name[(last_dot as usize + 1)..].to_string();
  1297. if name.starts_with(':') || name.starts_with('.') {
  1298. name.remove(0);
  1299. }
  1300. }
  1301. let name = name.trim().to_string();
  1302. if name.is_empty() {
  1303. return;
  1304. }
  1305. self.push_ref(decorated_row, &name, edge_kind_index("decorates").unwrap(), n);
  1306. }
  1307. // --- extractInheritance (TS/JS clauses) ---------------------------------------------------
  1308. pub(super) fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) {
  1309. stack_guard!();
  1310. let extends_kind = edge_kind_index("extends").unwrap();
  1311. let implements_kind = edge_kind_index("implements").unwrap();
  1312. for i in 0..node.named_child_count() {
  1313. let Some(child) = node.named_child(i) else { continue };
  1314. match child.kind() {
  1315. // TS `extends_clause` (the other spellings are other grammars').
  1316. "extends_clause" | "superclass" | "base_clause" | "extends_interfaces" => {
  1317. if let Some(target) = child.named_child(0) {
  1318. let name = self.text(target).to_string();
  1319. self.push_ref(class_row, &name, extends_kind, target);
  1320. }
  1321. }
  1322. "implements_clause" | "class_interface_clause" | "super_interfaces" | "interfaces" => {
  1323. for j in 0..child.named_child_count() {
  1324. if let Some(iface) = child.named_child(j) {
  1325. let name = self.text(iface).to_string();
  1326. self.push_ref(class_row, &name, implements_kind, iface);
  1327. }
  1328. }
  1329. }
  1330. // JS `class Foo extends Bar` — class_heritage holds a bare
  1331. // identifier without an extends_clause wrapper.
  1332. "identifier" | "type_identifier" if node.kind() == "class_heritage" => {
  1333. let name = self.text(child).to_string();
  1334. self.push_ref(class_row, &name, extends_kind, child);
  1335. }
  1336. // TS class_heritage wraps extends/implements — recurse.
  1337. "field_declaration_list" | "class_heritage" => {
  1338. self.extract_inheritance(child, class_row);
  1339. }
  1340. _ => {}
  1341. }
  1342. }
  1343. }
  1344. // --- type annotations (#381 — TS family only) ----------------------------------------------
  1345. pub(super) fn extract_type_annotations(&mut self, node: Node<'t>, from_row: u32) {
  1346. if !self.variant.is_ts() {
  1347. return;
  1348. }
  1349. if let Some(params) = node.child_by_field_name("parameters") {
  1350. self.extract_type_refs_from_subtree(params, from_row);
  1351. }
  1352. if let Some(ret) = node.child_by_field_name("return_type") {
  1353. self.extract_type_refs_from_subtree(ret, from_row);
  1354. }
  1355. let type_annotation = (0..node.named_child_count())
  1356. .filter_map(|i| node.named_child(i))
  1357. .find(|c| c.kind() == "type_annotation");
  1358. if let Some(ta) = type_annotation {
  1359. self.extract_type_refs_from_subtree(ta, from_row);
  1360. }
  1361. }
  1362. pub(super) fn extract_variable_type_annotation(&mut self, node: Node<'t>, from_row: u32) {
  1363. if !self.variant.is_ts() {
  1364. return;
  1365. }
  1366. let type_annotation = (0..node.named_child_count())
  1367. .filter_map(|i| node.named_child(i))
  1368. .find(|c| c.kind() == "type_annotation");
  1369. if let Some(ta) = type_annotation {
  1370. self.extract_type_refs_from_subtree(ta, from_row);
  1371. }
  1372. }
  1373. fn extract_type_refs_from_subtree(&mut self, node: Node<'t>, from_row: u32) {
  1374. stack_guard!();
  1375. if node.kind() == "type_identifier" {
  1376. let type_name = self.text(node).to_string();
  1377. if !type_name.is_empty() && !is_builtin_type(&type_name) {
  1378. self.push_ref(from_row, &type_name, edge_kind_index("references").unwrap(), node);
  1379. }
  1380. return;
  1381. }
  1382. for i in 0..node.named_child_count() {
  1383. if let Some(c) = node.named_child(i) {
  1384. self.extract_type_refs_from_subtree(c, from_row);
  1385. }
  1386. }
  1387. }
  1388. }
  1389. /// `.replace(/\s+/g, ' ')` for the tuple-contract signature.
  1390. fn collapse_ws(s: &str) -> String {
  1391. let mut out = String::with_capacity(s.len());
  1392. let mut in_ws = false;
  1393. for c in s.chars() {
  1394. if c.is_whitespace() {
  1395. if !in_ws {
  1396. out.push(' ');
  1397. in_ws = true;
  1398. }
  1399. } else {
  1400. out.push(c);
  1401. in_ws = false;
  1402. }
  1403. }
  1404. out
  1405. }