java.rs 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  1. //! Java extraction — a faithful Rust port of `TreeSitterExtractor`'s Java
  2. //! paths (src/extraction/tree-sitter.ts) plus languages/java.ts, including
  3. //! the Lombok member synthesizer (#912).
  4. //!
  5. //! Same porting contract as tsjs/: behavior parity with the wasm path,
  6. //! bug-for-bug, verified by scripts/kernel-parity.mjs and the full-index
  7. //! dump-diff gate. Positions in UTF-16 code units. Files whose parse tree
  8. //! contains ERRORS defer to the wasm extractor (encoding-dependent recovery —
  9. //! see tsjs/mod.rs).
  10. use crate::buffers::{
  11. build_meta, edge_kind_index, node_kind_index, Arena, BoolFlags, EdgeRow, EmitOut, NodeRow,
  12. RefRow, StrRef, Tables, FLAG_IS_STATIC, FUNCTION_REF_CODE, NONE, NONE_STR,
  13. };
  14. use crate::docstring::preceding_docstring;
  15. use crate::ids;
  16. use crate::textutil as util;
  17. use regex::Regex;
  18. use std::collections::{HashMap, HashSet};
  19. use std::sync::OnceLock;
  20. use tree_sitter::{Node, Parser};
  21. const MAX_VALUE_REF_NODES: usize = 20_000;
  22. fn is_method_type(kind: &str) -> bool {
  23. matches!(kind, "method_declaration" | "constructor_declaration")
  24. }
  25. fn is_interface_type(kind: &str) -> bool {
  26. matches!(kind, "interface_declaration" | "annotation_type_declaration")
  27. }
  28. /// JAVA_NON_CLASS_RETURN_NODES (languages/java.ts).
  29. fn is_non_class_return(kind: &str) -> bool {
  30. matches!(kind, "void_type" | "integral_type" | "floating_point_type" | "boolean_type")
  31. }
  32. /// BUILTIN_TYPES (tree-sitter.ts) — shared table; only the Java-relevant names
  33. /// fire here but membership is what the TS code tests.
  34. fn is_builtin_type(name: &str) -> bool {
  35. matches!(
  36. name,
  37. "string" | "number" | "boolean" | "void" | "null" | "undefined" | "never" | "any"
  38. | "unknown" | "object" | "symbol" | "bigint" | "true" | "false"
  39. | "str" | "bool" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize"
  40. | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "f32" | "f64" | "char"
  41. | "int" | "long" | "short" | "byte" | "float" | "double"
  42. | "int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64"
  43. | "float32" | "float64" | "complex64" | "complex128" | "rune" | "error"
  44. | "Int" | "Long" | "Short" | "Byte" | "Float" | "Double" | "Boolean" | "Char"
  45. | "Unit" | "String" | "Any" | "AnyRef" | "AnyVal" | "Nothing" | "Null"
  46. )
  47. }
  48. /// LOMBOK_LOG_ANNOTATIONS (languages/java.ts).
  49. fn is_lombok_log_annotation(name: &str) -> bool {
  50. matches!(
  51. name,
  52. "Slf4j" | "Log4j" | "Log4j2" | "Log" | "CommonsLog" | "JBossLog" | "Flogger" | "XSlf4j"
  53. | "CustomLog"
  54. )
  55. }
  56. fn generic_args_re() -> &'static Regex {
  57. static RE: OnceLock<Regex> = OnceLock::new();
  58. RE.get_or_init(|| Regex::new(r"<[^>]*>").unwrap())
  59. }
  60. fn simple_ident_re() -> &'static Regex {
  61. static RE: OnceLock<Regex> = OnceLock::new();
  62. RE.get_or_init(|| Regex::new(r"^[A-Za-z_]\w*$").unwrap())
  63. }
  64. fn capitalized_re() -> &'static Regex {
  65. static RE: OnceLock<Regex> = OnceLock::new();
  66. RE.get_or_init(|| Regex::new(r"^[A-Z][A-Za-z0-9_]*$").unwrap())
  67. }
  68. fn method_ref_type_re() -> &'static Regex {
  69. static RE: OnceLock<Regex> = OnceLock::new();
  70. RE.get_or_init(|| Regex::new(r"^([A-Z][A-Za-z0-9_]*)\s*::").unwrap())
  71. }
  72. fn is_prefix_re(word: &str) -> bool {
  73. // /^is[A-Z]/ for Lombok boolean getters.
  74. word.len() > 2 && word.starts_with("is") && word.as_bytes()[2].is_ascii_uppercase()
  75. }
  76. struct Scope {
  77. row: u32,
  78. kind: &'static str,
  79. name: String,
  80. }
  81. /// Per-node metadata kept for the Lombok synthesizer's taken-member scan
  82. /// (mirrors its walk over ctx.nodes by qualifiedName).
  83. struct NodeMeta {
  84. kind: &'static str,
  85. name: String,
  86. qualified_name: String,
  87. }
  88. #[derive(Default)]
  89. struct Extra {
  90. docstring: Option<String>,
  91. signature: Option<String>,
  92. visibility: Option<u8>,
  93. is_static: Option<bool>,
  94. return_type: Option<String>,
  95. decorators: Option<Vec<String>>,
  96. }
  97. struct ValueScope<'t> {
  98. row: u32,
  99. node: Node<'t>,
  100. name: String,
  101. }
  102. struct Cand {
  103. from: u32,
  104. name: String,
  105. line: u32,
  106. column_byte: usize,
  107. row: usize,
  108. }
  109. pub struct Walker<'t> {
  110. src: &'t str,
  111. file_path: &'t str,
  112. line_starts: Vec<usize>,
  113. arena: Arena,
  114. tables: Tables,
  115. stack: Vec<Scope>,
  116. nodes_meta: Vec<NodeMeta>,
  117. /// Node id string per row — ids COLLIDE for same-(kind, name, line) nodes
  118. /// and the TS side's fn-ref dedupe / value-ref self-checks key on the id.
  119. node_ids: Vec<String>,
  120. defined_fn_names: HashSet<String>,
  121. imported_names: HashSet<String>,
  122. fn_ref_cands: Vec<Cand>,
  123. fs_values: HashMap<String, u32>,
  124. fs_value_counts: HashMap<String, u32>,
  125. value_scopes: Vec<ValueScope<'t>>,
  126. }
  127. pub fn extract(file_path: &str, source: &str) -> Result<EmitOut, String> {
  128. let grammar = crate::langs::grammar_for("java").ok_or("no java grammar")?;
  129. let t0 = std::time::Instant::now();
  130. let mut parser = Parser::new();
  131. parser
  132. .set_language(&grammar)
  133. .map_err(|e| format!("set_language(java) failed: {e}"))?;
  134. let tree = parser
  135. .parse(source, None)
  136. .ok_or_else(|| "parser returned null tree".to_string())?;
  137. if tree.root_node().has_error() {
  138. return Err("defer: parse tree contains errors — wasm recovery is canonical".to_string());
  139. }
  140. let mut w = Walker {
  141. src: source,
  142. file_path,
  143. line_starts: util::line_starts(source),
  144. arena: Arena::default(),
  145. tables: Tables::default(),
  146. stack: Vec::new(),
  147. nodes_meta: Vec::new(),
  148. node_ids: Vec::new(),
  149. defined_fn_names: HashSet::new(),
  150. imported_names: HashSet::new(),
  151. fn_ref_cands: Vec::new(),
  152. fs_values: HashMap::new(),
  153. fs_value_counts: HashMap::new(),
  154. value_scopes: Vec::new(),
  155. };
  156. // File node (TreeSitterExtractor.extract).
  157. let line_count = source.bytes().filter(|b| *b == b'\n').count() as u32 + 1;
  158. let base_name = file_path.rsplit(['/', '\\']).next().unwrap_or(file_path);
  159. let mut flags = BoolFlags::default();
  160. flags.set(crate::buffers::FLAG_IS_EXPORTED, false);
  161. let file_id = w.arena.put(&ids::file_node_id(file_path));
  162. let name_ref = w.arena.put(base_name);
  163. let qn_ref = w.arena.put(file_path);
  164. w.tables.push_node(&NodeRow {
  165. kind: node_kind_index("file").unwrap(),
  166. visibility: 0,
  167. flags,
  168. start_line: 1,
  169. end_line: line_count,
  170. start_column: 0,
  171. end_column: 0,
  172. name: name_ref,
  173. qualified_name: qn_ref,
  174. id: file_id,
  175. docstring: NONE_STR,
  176. signature: NONE_STR,
  177. decorators: NONE_STR,
  178. type_parameters: NONE_STR,
  179. return_type: NONE_STR,
  180. extra_json: NONE_STR,
  181. });
  182. w.nodes_meta.push(NodeMeta {
  183. kind: "file",
  184. name: base_name.to_string(),
  185. qualified_name: file_path.to_string(),
  186. });
  187. w.node_ids.push(ids::file_node_id(file_path));
  188. w.stack.push(Scope { row: 0, kind: "file", name: base_name.to_string() });
  189. // extractFilePackage: wrap top-level declarations in a `namespace` node
  190. // carrying the package FQN.
  191. let root = tree.root_node();
  192. let mut pkg_pushed = false;
  193. for i in 0..root.named_child_count() {
  194. let Some(child) = root.named_child(i) else { continue };
  195. if child.kind() != "package_declaration" {
  196. continue;
  197. }
  198. let id_node = (0..child.named_child_count())
  199. .filter_map(|j| child.named_child(j))
  200. .find(|c| matches!(c.kind(), "scoped_identifier" | "identifier"));
  201. if let Some(id_node) = id_node {
  202. let pkg = w.text(id_node).trim().to_string();
  203. if !pkg.is_empty() {
  204. if let Some(row) = w.create_node("namespace", &pkg, child, Extra::default()) {
  205. w.stack.push(Scope { row, kind: "namespace", name: pkg });
  206. pkg_pushed = true;
  207. }
  208. }
  209. }
  210. break;
  211. }
  212. w.visit_node(root);
  213. w.flush_fn_ref_candidates();
  214. w.flush_value_refs(root);
  215. if pkg_pushed {
  216. w.stack.pop();
  217. }
  218. w.stack.pop();
  219. let duration_ms = t0.elapsed().as_secs_f64() * 1000.0;
  220. let meta = build_meta(&w.tables, w.arena.len(), NONE_STR, duration_ms);
  221. Ok(EmitOut {
  222. meta,
  223. nodes: w.tables.nodes,
  224. edges: w.tables.edges,
  225. refs: w.tables.refs,
  226. arena: w.arena.into_vec(),
  227. })
  228. }
  229. impl<'t> Walker<'t> {
  230. fn text(&self, node: Node) -> &'t str {
  231. &self.src[node.byte_range()]
  232. }
  233. fn line_of(&self, node: Node) -> u32 {
  234. node.start_position().row as u32 + 1
  235. }
  236. fn col_of(&self, node: Node) -> u32 {
  237. util::col16(self.src, &self.line_starts, node.start_position().row, node.start_byte())
  238. }
  239. fn end_col_of(&self, node: Node) -> u32 {
  240. util::col16(self.src, &self.line_starts, node.end_position().row, node.end_byte())
  241. }
  242. fn top_row(&self) -> u32 {
  243. self.stack.last().map(|s| s.row).unwrap_or(0)
  244. }
  245. fn inside_class_like(&self) -> bool {
  246. self.stack
  247. .last()
  248. .map(|s| matches!(s.kind, "class" | "struct" | "interface" | "trait" | "enum" | "module"))
  249. .unwrap_or(false)
  250. }
  251. fn push_ref(&mut self, from_row: u32, name: &str, kind_code: u8, line: u32, column: u32) {
  252. let name_ref = self.arena.put(name);
  253. self.tables.push_ref(&RefRow {
  254. from_idx: from_row,
  255. kind: kind_code,
  256. line,
  257. column,
  258. reference_name: name_ref,
  259. candidates: NONE_STR,
  260. from_id_str: NONE_STR,
  261. });
  262. if kind_code == edge_kind_index("imports").unwrap() {
  263. if util::simple_name().is_match(name) {
  264. self.imported_names.insert(name.to_string());
  265. } else if let Some(c) = util::qualified_import().captures(name) {
  266. self.imported_names.insert(c[1].to_string());
  267. }
  268. }
  269. }
  270. fn push_ref_at(&mut self, from_row: u32, name: &str, kind_code: u8, node: Node) {
  271. self.push_ref(from_row, name, kind_code, self.line_of(node), self.col_of(node));
  272. }
  273. // --- createNode ------------------------------------------------------------
  274. fn create_node(&mut self, kind: &'static str, name: &str, node: Node<'t>, extra: Extra) -> Option<u32> {
  275. if name.is_empty() {
  276. return None;
  277. }
  278. let start_line = self.line_of(node);
  279. let id = ids::node_id(self.file_path, kind, name, start_line);
  280. let end_line = node.end_position().row as u32 + 1; // no resolveBody for java
  281. let qualified = {
  282. let mut parts: Vec<&str> = Vec::new();
  283. for s in &self.stack {
  284. if s.kind != "file" {
  285. parts.push(&s.name);
  286. }
  287. }
  288. let mut qn = parts.join("::");
  289. if !qn.is_empty() {
  290. qn.push_str("::");
  291. }
  292. qn.push_str(name);
  293. qn
  294. };
  295. let mut flags = BoolFlags::default();
  296. if let Some(v) = extra.is_static {
  297. flags.set(FLAG_IS_STATIC, v);
  298. }
  299. let name_ref = self.arena.put(name);
  300. let qn_ref = self.arena.put(&qualified);
  301. let id_ref = self.arena.put(&id);
  302. let doc_ref = opt_str(&mut self.arena, extra.docstring.as_deref());
  303. let sig_ref = opt_str(&mut self.arena, extra.signature.as_deref());
  304. let ret_ref = opt_str(&mut self.arena, extra.return_type.as_deref());
  305. let dec_ref: StrRef = match &extra.decorators {
  306. Some(list) if !list.is_empty() => self.arena.put_list(list),
  307. _ => NONE_STR,
  308. };
  309. let row = self.tables.push_node(&NodeRow {
  310. kind: node_kind_index(kind).unwrap(),
  311. visibility: extra.visibility.unwrap_or(0),
  312. flags,
  313. start_line,
  314. end_line,
  315. start_column: self.col_of(node),
  316. end_column: self.end_col_of(node),
  317. name: name_ref,
  318. qualified_name: qn_ref,
  319. id: id_ref,
  320. docstring: doc_ref,
  321. signature: sig_ref,
  322. decorators: dec_ref,
  323. type_parameters: NONE_STR,
  324. return_type: ret_ref,
  325. extra_json: NONE_STR,
  326. });
  327. self.nodes_meta.push(NodeMeta { kind, name: name.to_string(), qualified_name: qualified });
  328. self.node_ids.push(id);
  329. let parent_row = self.top_row();
  330. self.tables.push_edge(&EdgeRow {
  331. source_idx: parent_row,
  332. target_idx: row,
  333. kind: edge_kind_index("contains").unwrap(),
  334. provenance: 0,
  335. line: NONE,
  336. column: NONE,
  337. metadata_json: NONE_STR,
  338. source_id_str: NONE_STR,
  339. target_id_str: NONE_STR,
  340. });
  341. if kind == "function" || kind == "method" {
  342. self.defined_fn_names.insert(name.to_string());
  343. }
  344. self.capture_value_ref_scope(kind, name, row, node);
  345. Some(row)
  346. }
  347. fn capture_value_ref_scope(&mut self, kind: &'static str, name: &str, row: u32, node: Node<'t>) {
  348. let target_kind_ok = kind == "constant" || kind == "variable";
  349. if target_kind_ok
  350. && util::utf16_len(name) >= 3
  351. && util::has_upper_or_underscore().is_match(name)
  352. {
  353. let parent_ok = self
  354. .stack
  355. .last()
  356. .map(|s| matches!(s.kind, "file" | "class" | "module" | "struct" | "enum"))
  357. .unwrap_or(false);
  358. if parent_ok {
  359. self.fs_values.insert(name.to_string(), row);
  360. *self.fs_value_counts.entry(name.to_string()).or_insert(0) += 1;
  361. }
  362. }
  363. if matches!(kind, "function" | "method" | "constant" | "variable") {
  364. self.value_scopes.push(ValueScope { row, node, name: name.to_string() });
  365. }
  366. }
  367. // --- modifiers / hooks (languages/java.ts) -----------------------------------
  368. fn modifiers_child(&self, node: Node<'t>) -> Option<Node<'t>> {
  369. (0..node.named_child_count())
  370. .filter_map(|i| node.named_child(i))
  371. .find(|c| c.kind() == "modifiers")
  372. }
  373. fn visibility_of(&self, node: Node) -> Option<u8> {
  374. for i in 0..node.child_count() {
  375. let child = node.child(i)?;
  376. if child.kind() == "modifiers" {
  377. let text = self.text(child);
  378. if text.contains("public") {
  379. return Some(1);
  380. }
  381. if text.contains("private") {
  382. return Some(2);
  383. }
  384. if text.contains("protected") {
  385. return Some(3);
  386. }
  387. }
  388. }
  389. None
  390. }
  391. fn is_static(&self, node: Node) -> bool {
  392. for i in 0..node.child_count() {
  393. if let Some(child) = node.child(i) {
  394. if child.kind() == "modifiers" && self.text(child).contains("static") {
  395. return true;
  396. }
  397. }
  398. }
  399. false
  400. }
  401. /// javaExtractor.isConst: `static final` field → constant.
  402. fn is_const(&self, node: Node) -> bool {
  403. for i in 0..node.child_count() {
  404. if let Some(child) = node.child(i) {
  405. if child.kind() == "modifiers" {
  406. let text = self.text(child);
  407. return word_re("static").is_match(text) && word_re("final").is_match(text);
  408. }
  409. }
  410. }
  411. false
  412. }
  413. fn signature_of(&self, node: Node) -> Option<String> {
  414. let params = node.child_by_field_name("parameters")?;
  415. let params_text = self.text(params);
  416. match node.child_by_field_name("type") {
  417. Some(ret) => Some(format!("{} {}", self.text(ret), params_text)),
  418. None => Some(params_text.to_string()),
  419. }
  420. }
  421. /// normalizeJavaType (languages/java.ts).
  422. fn normalize_java_type(&self, type_node: Option<Node>) -> Option<String> {
  423. let t = type_node?;
  424. if is_non_class_return(t.kind()) || t.kind() == "array_type" {
  425. return None;
  426. }
  427. let raw = generic_args_re().replace_all(self.text(t).trim(), "").into_owned();
  428. let last = raw.rsplit('.').next().unwrap_or("").trim().to_string();
  429. if last.is_empty() || !simple_ident_re().is_match(&last) {
  430. return None;
  431. }
  432. Some(last)
  433. }
  434. fn extract_name(&self, node: Node) -> String {
  435. if let Some(name_node) = node.child_by_field_name("name") {
  436. return self.text(name_node).to_string();
  437. }
  438. for i in 0..node.named_child_count() {
  439. if let Some(c) = node.named_child(i) {
  440. if matches!(c.kind(), "identifier" | "type_identifier" | "simple_identifier" | "constant") {
  441. return self.text(c).to_string();
  442. }
  443. }
  444. }
  445. "<anonymous>".to_string()
  446. }
  447. // --- the dispatcher (visitNode, Java-relevant branches) -----------------------
  448. fn visit_node(&mut self, node: Node<'t>) {
  449. let kind = node.kind();
  450. let mut skip_children = false;
  451. self.maybe_capture_fn_refs(node);
  452. if kind == "class_declaration" {
  453. self.extract_class(node);
  454. skip_children = true;
  455. } else if is_method_type(kind) {
  456. self.extract_method(node);
  457. skip_children = true;
  458. } else if is_interface_type(kind) {
  459. self.extract_interface(node);
  460. skip_children = true;
  461. } else if kind == "enum_declaration" {
  462. self.extract_enum(node);
  463. skip_children = true;
  464. } else if kind == "field_declaration" && self.inside_class_like() {
  465. self.extract_field(node);
  466. self.scan_fn_ref_subtree(node, 0);
  467. skip_children = true;
  468. } else if kind == "local_variable_declaration" && !self.inside_class_like() {
  469. self.extract_variable(node);
  470. self.scan_fn_ref_subtree(node, 0);
  471. skip_children = true;
  472. } else if kind == "import_declaration" {
  473. self.extract_import(node);
  474. } else if kind == "method_invocation" {
  475. self.extract_call(node);
  476. } else if kind == "object_creation_expression" {
  477. self.extract_instantiation(node);
  478. if let Some(anon_body) = find_anonymous_class_body(node) {
  479. self.extract_anonymous_class(node, anon_body);
  480. skip_children = true;
  481. }
  482. }
  483. if !skip_children {
  484. for i in 0..node.named_child_count() {
  485. if let Some(c) = node.named_child(i) {
  486. self.visit_node(c);
  487. }
  488. }
  489. }
  490. }
  491. // --- visitFunctionBody ----------------------------------------------------------
  492. fn visit_function_body(&mut self, body: Node<'t>) {
  493. self.visit_for_calls_and_structure(body);
  494. }
  495. fn visit_for_calls_and_structure(&mut self, node: Node<'t>) {
  496. let kind = node.kind();
  497. self.maybe_capture_fn_refs(node);
  498. if kind == "method_invocation" {
  499. self.extract_call(node);
  500. } else if kind == "object_creation_expression" {
  501. self.extract_instantiation(node);
  502. if let Some(anon_body) = find_anonymous_class_body(node) {
  503. self.extract_anonymous_class(node, anon_body);
  504. return;
  505. }
  506. }
  507. // Static-member / value-read (`Type.CONST`) — self-gates on field_access.
  508. self.extract_static_member_ref(node);
  509. if kind == "class_declaration" {
  510. self.extract_class(node);
  511. return;
  512. }
  513. if kind == "enum_declaration" {
  514. self.extract_enum(node);
  515. return;
  516. }
  517. if is_interface_type(kind) {
  518. self.extract_interface(node);
  519. return;
  520. }
  521. for i in 0..node.named_child_count() {
  522. if let Some(c) = node.named_child(i) {
  523. self.visit_for_calls_and_structure(c);
  524. }
  525. }
  526. }
  527. // --- extractors --------------------------------------------------------------
  528. fn extract_class(&mut self, node: Node<'t>) {
  529. let name = self.extract_name(node);
  530. let extra = Extra {
  531. docstring: preceding_docstring(node, self.src),
  532. visibility: self.visibility_of(node),
  533. ..Extra::default() // java has no isExported hook
  534. };
  535. let Some(row) = self.create_node("class", &name, node, extra) else { return };
  536. self.extract_inheritance(node, row);
  537. self.extract_decorators_for(node, row);
  538. self.stack.push(Scope { row, kind: "class", name });
  539. let body = node.child_by_field_name("body").unwrap_or(node);
  540. for i in 0..body.named_child_count() {
  541. if let Some(c) = body.named_child(i) {
  542. self.visit_node(c);
  543. }
  544. }
  545. // Lombok member synthesis (#912) — class still on the stack.
  546. self.synthesize_lombok_members(node, row);
  547. self.stack.pop();
  548. }
  549. fn extract_method(&mut self, node: Node<'t>) {
  550. if !self.inside_class_like() {
  551. // (object-literal parents don't exist in Java; a stray top-level
  552. // method extracts as a function, mirroring extractMethod's tail)
  553. self.extract_function(node);
  554. return;
  555. }
  556. let name = self.extract_name(node);
  557. let extra = Extra {
  558. docstring: preceding_docstring(node, self.src),
  559. signature: self.signature_of(node),
  560. visibility: self.visibility_of(node),
  561. is_static: Some(self.is_static(node)),
  562. return_type: self.normalize_java_type(node.child_by_field_name("type")),
  563. ..Extra::default()
  564. };
  565. let Some(row) = self.create_node("method", &name, node, extra) else { return };
  566. self.extract_type_annotations(node, row);
  567. self.extract_decorators_for(node, row);
  568. self.stack.push(Scope { row, kind: "method", name });
  569. if let Some(body) = node.child_by_field_name("body") {
  570. self.visit_function_body(body);
  571. }
  572. self.stack.pop();
  573. }
  574. /// extractFunction — only reachable for a method outside any class.
  575. fn extract_function(&mut self, node: Node<'t>) {
  576. let name = self.extract_name(node);
  577. if name == "<anonymous>" {
  578. if let Some(body) = node.child_by_field_name("body") {
  579. self.visit_function_body(body);
  580. }
  581. return;
  582. }
  583. let extra = Extra {
  584. docstring: preceding_docstring(node, self.src),
  585. signature: self.signature_of(node),
  586. visibility: self.visibility_of(node),
  587. is_static: Some(self.is_static(node)),
  588. return_type: self.normalize_java_type(node.child_by_field_name("type")),
  589. ..Extra::default()
  590. };
  591. let Some(row) = self.create_node("function", &name, node, extra) else { return };
  592. self.extract_type_annotations(node, row);
  593. self.extract_decorators_for(node, row);
  594. self.stack.push(Scope { row, kind: "function", name });
  595. if let Some(body) = node.child_by_field_name("body") {
  596. self.visit_function_body(body);
  597. }
  598. self.stack.pop();
  599. }
  600. fn extract_interface(&mut self, node: Node<'t>) {
  601. let name = self.extract_name(node);
  602. let extra = Extra {
  603. docstring: preceding_docstring(node, self.src),
  604. ..Extra::default()
  605. };
  606. let Some(row) = self.create_node("interface", &name, node, extra) else { return };
  607. self.extract_inheritance(node, row);
  608. self.stack.push(Scope { row, kind: "interface", name });
  609. let body = node.child_by_field_name("body").unwrap_or(node);
  610. for i in 0..body.named_child_count() {
  611. if let Some(c) = body.named_child(i) {
  612. self.visit_node(c);
  613. }
  614. }
  615. self.stack.pop();
  616. }
  617. fn extract_enum(&mut self, node: Node<'t>) {
  618. let Some(body) = node.child_by_field_name("body") else { return };
  619. let name = self.extract_name(node);
  620. let extra = Extra {
  621. docstring: preceding_docstring(node, self.src),
  622. visibility: self.visibility_of(node),
  623. ..Extra::default()
  624. };
  625. let Some(row) = self.create_node("enum", &name, node, extra) else { return };
  626. self.extract_inheritance(node, row);
  627. self.stack.push(Scope { row, kind: "enum", name });
  628. for i in 0..body.named_child_count() {
  629. let Some(child) = body.named_child(i) else { continue };
  630. if child.kind() == "enum_constant" {
  631. self.extract_enum_members(child);
  632. } else {
  633. self.visit_node(child);
  634. }
  635. }
  636. self.stack.pop();
  637. }
  638. fn extract_enum_members(&mut self, node: Node<'t>) {
  639. if let Some(name_node) = node.child_by_field_name("name") {
  640. let name = self.text(name_node).to_string();
  641. self.create_node("enum_member", &name, node, Extra::default());
  642. }
  643. // (identifier-children / leaf fallbacks are other grammars' shapes)
  644. }
  645. /// extractField — each declarator becomes a field/constant node.
  646. fn extract_field(&mut self, node: Node<'t>) {
  647. let docstring = preceding_docstring(node, self.src);
  648. let visibility = self.visibility_of(node);
  649. let is_static = Some(self.is_static(node));
  650. let field_kind: &'static str = if self.is_const(node) { "constant" } else { "field" };
  651. let declarators: Vec<Node> = (0..node.named_child_count())
  652. .filter_map(|i| node.named_child(i))
  653. .filter(|c| c.kind() == "variable_declarator")
  654. .collect();
  655. if !declarators.is_empty() {
  656. let type_node = (0..node.named_child_count())
  657. .filter_map(|i| node.named_child(i))
  658. .find(|c| {
  659. !matches!(
  660. c.kind(),
  661. "modifiers" | "modifier" | "variable_declarator" | "variable_declaration"
  662. | "marker_annotation" | "annotation"
  663. )
  664. });
  665. let type_text = type_node.map(|t| self.text(t).to_string());
  666. for decl in declarators {
  667. let name_node = decl.child_by_field_name("name").or_else(|| {
  668. (0..decl.named_child_count())
  669. .filter_map(|i| decl.named_child(i))
  670. .find(|c| c.kind() == "identifier")
  671. });
  672. let Some(name_node) = name_node else { continue };
  673. let name = self.text(name_node).to_string();
  674. let signature = match &type_text {
  675. Some(t) => format!("{t} {name}"),
  676. None => name.clone(),
  677. };
  678. let row = self.create_node(
  679. field_kind,
  680. &name,
  681. decl,
  682. Extra {
  683. docstring: docstring.clone(),
  684. signature: Some(signature),
  685. visibility,
  686. is_static,
  687. ..Extra::default()
  688. },
  689. );
  690. if let Some(row) = row {
  691. self.extract_decorators_for(node, row);
  692. self.extract_type_annotations(node, row);
  693. }
  694. }
  695. } else {
  696. let name_node = node.child_by_field_name("name").or_else(|| {
  697. (0..node.named_child_count())
  698. .filter_map(|i| node.named_child(i))
  699. .find(|c| c.kind() == "identifier")
  700. });
  701. if let Some(name_node) = name_node {
  702. let name = self.text(name_node).to_string();
  703. self.create_node(
  704. field_kind,
  705. &name,
  706. node,
  707. Extra { docstring, visibility, is_static, ..Extra::default() },
  708. );
  709. }
  710. }
  711. }
  712. /// extractVariable's generic fallback (top-level locals — rare in Java).
  713. fn extract_variable(&mut self, node: Node<'t>) {
  714. let kind: &'static str = if self.is_const(node) { "constant" } else { "variable" };
  715. let docstring = preceding_docstring(node, self.src);
  716. for i in 0..node.named_child_count() {
  717. let Some(child) = node.named_child(i) else { continue };
  718. let name = match child.kind() {
  719. "identifier" => self.text(child).to_string(),
  720. "variable_declarator" => self.extract_name(child),
  721. _ => continue,
  722. };
  723. if name.is_empty() || name == "<anonymous>" {
  724. continue;
  725. }
  726. self.create_node(
  727. kind,
  728. &name,
  729. child,
  730. Extra { docstring: docstring.clone(), ..Extra::default() },
  731. );
  732. }
  733. }
  734. fn extract_import(&mut self, node: Node<'t>) {
  735. let import_text = self.text(node).trim().to_string();
  736. let scoped = (0..node.named_child_count())
  737. .filter_map(|i| node.named_child(i))
  738. .find(|c| c.kind() == "scoped_identifier");
  739. let Some(scoped) = scoped else { return }; // hook declined
  740. let module_name = self.text(scoped).to_string();
  741. if module_name.is_empty() {
  742. return;
  743. }
  744. self.create_node(
  745. "import",
  746. &module_name,
  747. node,
  748. Extra { signature: Some(import_text), ..Extra::default() },
  749. );
  750. let parent = self.top_row();
  751. self.push_ref_at(parent, &module_name.clone(), edge_kind_index("imports").unwrap(), node);
  752. }
  753. /// extractCall — the Java method_invocation paths.
  754. fn extract_call(&mut self, node: Node<'t>) {
  755. if self.stack.is_empty() {
  756. return;
  757. }
  758. let caller = self.top_row();
  759. let name_field = node.child_by_field_name("name");
  760. let object_field = node
  761. .child_by_field_name("object")
  762. .or_else(|| node.child_by_field_name("scope"));
  763. let mut callee_name = String::new();
  764. if let (Some(name_field), Some(object_field)) = (name_field, object_field) {
  765. let method_name = self.text(name_field);
  766. // Static-factory / fluent chain: `Foo.getInstance().bar()` →
  767. // `<inner-receiver>.<inner-method>().<method>` (#645/#608).
  768. if !method_name.is_empty() && object_field.kind() == "method_invocation" {
  769. let inner_obj = object_field.child_by_field_name("object");
  770. let inner_name = object_field.child_by_field_name("name");
  771. if let (Some(io), Some(inm)) = (inner_obj, inner_name) {
  772. let callee = format!("{}.{}().{}", self.text(io), self.text(inm), method_name);
  773. self.push_ref_at(caller, &callee, edge_kind_index("calls").unwrap(), node);
  774. return;
  775. }
  776. }
  777. // `this.userbo.toLogin2()` — unwrap the field after `this.`.
  778. let receiver_name = if object_field.kind() == "field_access" {
  779. let inner = object_field.child_by_field_name("object");
  780. let fld = object_field.child_by_field_name("field");
  781. match (inner, fld) {
  782. (Some(inner), Some(fld))
  783. if matches!(inner.kind(), "this" | "this_expression") =>
  784. {
  785. self.text(fld).to_string()
  786. }
  787. _ => self.text(object_field).to_string(),
  788. }
  789. } else {
  790. self.text(object_field).to_string()
  791. };
  792. let receiver_name = receiver_name.strip_prefix('$').unwrap_or(&receiver_name);
  793. if !method_name.is_empty() {
  794. if matches!(receiver_name, "self" | "this" | "cls" | "super" | "parent" | "static") {
  795. callee_name = method_name.to_string();
  796. } else {
  797. callee_name = format!("{receiver_name}.{method_name}");
  798. }
  799. }
  800. } else {
  801. // Bare call `foo()` — the generic tail: function field ?? first child.
  802. let func = node
  803. .child_by_field_name("function")
  804. .or_else(|| node.named_child(0));
  805. if let Some(func) = func {
  806. callee_name = self.text(func).to_string();
  807. }
  808. }
  809. if !callee_name.is_empty() {
  810. if let Some(c) = util::paren_conversion().captures(&callee_name) {
  811. callee_name = c[1].to_string();
  812. }
  813. self.push_ref_at(caller, &callee_name.clone(), edge_kind_index("calls").unwrap(), node);
  814. }
  815. }
  816. fn extract_instantiation(&mut self, node: Node<'t>) {
  817. if self.stack.is_empty() {
  818. return;
  819. }
  820. let ctor = node
  821. .child_by_field_name("constructor")
  822. .or_else(|| node.child_by_field_name("type"))
  823. .or_else(|| node.child_by_field_name("name"))
  824. .or_else(|| node.named_child(0));
  825. let Some(ctor) = ctor else { return };
  826. let class_name = strip_generic_and_qualifier(self.text(ctor));
  827. if !class_name.is_empty() {
  828. let from = self.top_row();
  829. self.push_ref_at(from, &class_name, edge_kind_index("instantiates").unwrap(), node);
  830. }
  831. }
  832. /// extractAnonymousClass — `new T() { ... }`.
  833. fn extract_anonymous_class(&mut self, node: Node<'t>, body: Node<'t>) {
  834. let type_node = node
  835. .child_by_field_name("constructor")
  836. .or_else(|| node.child_by_field_name("type"))
  837. .or_else(|| node.child_by_field_name("name"))
  838. .or_else(|| node.named_child(0));
  839. let mut type_name = type_node.map(|t| self.text(t).to_string()).unwrap_or_else(|| "Object".to_string());
  840. type_name = strip_generic_and_qualifier(&type_name);
  841. if type_name.is_empty() {
  842. type_name = "Object".to_string();
  843. }
  844. let anon_name = format!("<{type_name}$anon@{}>", node.start_position().row + 1);
  845. let Some(row) = self.create_node("class", &anon_name, node, Extra::default()) else {
  846. return;
  847. };
  848. // Bug-for-bug: the TS code uses `startPosition.row` (0-based) as the
  849. // LINE here — the one place it forgets the +1.
  850. let (line, column) = match type_node {
  851. Some(t) => (t.start_position().row as u32, self.col_of(t)),
  852. None => (node.start_position().row as u32, self.col_of(node)),
  853. };
  854. self.push_ref(row, &type_name, edge_kind_index("extends").unwrap(), line, column);
  855. self.stack.push(Scope { row, kind: "class", name: anon_name });
  856. for i in 0..body.named_child_count() {
  857. if let Some(c) = body.named_child(i) {
  858. self.visit_node(c);
  859. }
  860. }
  861. self.stack.pop();
  862. }
  863. /// extractStaticMemberRef — `Type.CONST` value reads (java: field_access).
  864. fn extract_static_member_ref(&mut self, node: Node<'t>) {
  865. if node.kind() != "field_access" {
  866. return;
  867. }
  868. if self.stack.is_empty() {
  869. return;
  870. }
  871. let owner = self.top_row();
  872. // Skip `Type.method()` — the access is a call's callee, already linked.
  873. if let Some(parent) = node.parent() {
  874. if parent.kind() == "method_invocation" {
  875. let callee = parent
  876. .child_by_field_name("function")
  877. .or_else(|| parent.child_by_field_name("method"))
  878. .or_else(|| parent.named_child(0));
  879. if let Some(callee) = callee {
  880. if callee.start_byte() == node.start_byte() {
  881. return;
  882. }
  883. }
  884. }
  885. }
  886. let recv = node
  887. .child_by_field_name("object")
  888. .or_else(|| node.child_by_field_name("expression"))
  889. .or_else(|| node.child_by_field_name("scope"))
  890. .or_else(|| node.named_child(0));
  891. let Some(recv) = recv else { return };
  892. if matches!(
  893. recv.kind(),
  894. "identifier" | "type_identifier" | "simple_identifier" | "name" | "scoped_type_identifier"
  895. ) {
  896. let text = self.text(recv);
  897. if capitalized_re().is_match(text) {
  898. self.push_ref_at(owner, &text.to_string(), edge_kind_index("references").unwrap(), recv);
  899. }
  900. }
  901. }
  902. /// extractInheritance — the Java clauses (type_list-aware).
  903. fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) {
  904. let extends_kind = edge_kind_index("extends").unwrap();
  905. let implements_kind = edge_kind_index("implements").unwrap();
  906. for i in 0..node.named_child_count() {
  907. let Some(child) = node.named_child(i) else { continue };
  908. match child.kind() {
  909. "superclass" | "extends_interfaces" => {
  910. let type_list = (0..child.named_child_count())
  911. .filter_map(|j| child.named_child(j))
  912. .find(|c| c.kind() == "type_list");
  913. let targets: Vec<Node> = match type_list {
  914. Some(tl) => (0..tl.named_child_count()).filter_map(|j| tl.named_child(j)).collect(),
  915. None => child.named_child(0).into_iter().collect(),
  916. };
  917. for target in targets {
  918. let name = self.text(target).to_string();
  919. self.push_ref_at(class_row, &name, extends_kind, target);
  920. }
  921. }
  922. "super_interfaces" => {
  923. let type_list = (0..child.named_child_count())
  924. .filter_map(|j| child.named_child(j))
  925. .find(|c| c.kind() == "type_list");
  926. let targets: Vec<Node> = match type_list {
  927. Some(tl) => (0..tl.named_child_count()).filter_map(|j| tl.named_child(j)).collect(),
  928. None => (0..child.named_child_count()).filter_map(|j| child.named_child(j)).collect(),
  929. };
  930. for iface in targets {
  931. let name = self.text(iface).to_string();
  932. self.push_ref_at(class_row, &name, implements_kind, iface);
  933. }
  934. }
  935. _ => {}
  936. }
  937. }
  938. }
  939. /// extractDecoratorsFor — Java annotations live inside `modifiers`.
  940. fn extract_decorators_for(&mut self, decl: Node<'t>, decorated_row: u32) {
  941. for i in 0..decl.named_child_count() {
  942. let Some(child) = decl.named_child(i) else { continue };
  943. self.consider_decorator(child, decorated_row);
  944. if child.kind() == "modifiers" {
  945. for j in 0..child.named_child_count() {
  946. if let Some(m) = child.named_child(j) {
  947. self.consider_decorator(m, decorated_row);
  948. }
  949. }
  950. }
  951. }
  952. // Preceding-sibling scan (TS-style class decorators) — Java annotations
  953. // are inside modifiers, so this is inert here; kept for parity of shape.
  954. let Some(parent) = decl.parent() else { return };
  955. let decl_start = decl.start_byte();
  956. let mut decl_idx: isize = -1;
  957. for i in 0..parent.named_child_count() {
  958. if let Some(sib) = parent.named_child(i) {
  959. if sib.start_byte() == decl_start {
  960. decl_idx = i as isize;
  961. break;
  962. }
  963. }
  964. }
  965. if decl_idx > 0 {
  966. let mut j = decl_idx - 1;
  967. while j >= 0 {
  968. let Some(sib) = parent.named_child(j as usize) else {
  969. j -= 1;
  970. continue;
  971. };
  972. if !matches!(sib.kind(), "decorator" | "annotation" | "marker_annotation") {
  973. break;
  974. }
  975. self.consider_decorator(sib, decorated_row);
  976. j -= 1;
  977. }
  978. }
  979. }
  980. fn consider_decorator(&mut self, n: Node<'t>, decorated_row: u32) {
  981. if !matches!(n.kind(), "decorator" | "annotation" | "marker_annotation" | "attribute") {
  982. return;
  983. }
  984. let mut target: Option<Node> = None;
  985. for i in 0..n.named_child_count() {
  986. let Some(child) = n.named_child(i) else { continue };
  987. if child.kind() == "call_expression" {
  988. target = child.child_by_field_name("function").or_else(|| child.named_child(0));
  989. if target.is_some() {
  990. break;
  991. }
  992. }
  993. if matches!(
  994. child.kind(),
  995. "identifier" | "member_expression" | "scoped_identifier" | "navigation_expression"
  996. | "user_type" | "type_identifier"
  997. ) {
  998. target = Some(child);
  999. break;
  1000. }
  1001. }
  1002. let Some(target) = target else { return };
  1003. let name = strip_generic_and_qualifier(self.text(target));
  1004. if name.is_empty() {
  1005. return;
  1006. }
  1007. self.push_ref_at(decorated_row, &name, edge_kind_index("decorates").unwrap(), n);
  1008. }
  1009. /// extractTypeAnnotations — Java's returnField is `type`.
  1010. fn extract_type_annotations(&mut self, node: Node<'t>, from_row: u32) {
  1011. if let Some(params) = node.child_by_field_name("parameters") {
  1012. self.extract_type_refs_from_subtree(params, from_row);
  1013. }
  1014. if let Some(ret) = node.child_by_field_name("type") {
  1015. self.extract_type_refs_from_subtree(ret, from_row);
  1016. }
  1017. let type_annotation = (0..node.named_child_count())
  1018. .filter_map(|i| node.named_child(i))
  1019. .find(|c| c.kind() == "type_annotation");
  1020. if let Some(ta) = type_annotation {
  1021. self.extract_type_refs_from_subtree(ta, from_row);
  1022. }
  1023. }
  1024. fn extract_type_refs_from_subtree(&mut self, node: Node<'t>, from_row: u32) {
  1025. if node.kind() == "type_identifier" {
  1026. let type_name = self.text(node).to_string();
  1027. if !type_name.is_empty() && !is_builtin_type(&type_name) {
  1028. self.push_ref_at(from_row, &type_name, edge_kind_index("references").unwrap(), node);
  1029. }
  1030. return;
  1031. }
  1032. for i in 0..node.named_child_count() {
  1033. if let Some(c) = node.named_child(i) {
  1034. self.extract_type_refs_from_subtree(c, from_row);
  1035. }
  1036. }
  1037. }
  1038. // --- function-as-value refs (JAVA_SPEC: method references only) ----------------
  1039. fn maybe_capture_fn_refs(&mut self, node: Node<'t>) {
  1040. let mode_field: Option<&str> = match node.kind() {
  1041. "argument_list" => Some(""), // args: every named child
  1042. "assignment_expression" => Some("right"),
  1043. "variable_declarator" => Some("value"),
  1044. _ => None,
  1045. };
  1046. let Some(field) = mode_field else { return };
  1047. if self.stack.is_empty() {
  1048. return;
  1049. }
  1050. let from = self.top_row();
  1051. let mut values: Vec<Node> = Vec::new();
  1052. if field.is_empty() {
  1053. for i in 0..node.named_child_count() {
  1054. if let Some(c) = node.named_child(i) {
  1055. values.push(c);
  1056. }
  1057. }
  1058. } else if field == "right" {
  1059. if let Some(rhs) = node.child_by_field_name("right") {
  1060. let lhs_text = node
  1061. .child_by_field_name("left")
  1062. .map(|l| self.text(l))
  1063. .unwrap_or("");
  1064. let lhs_last = util::lhs_last_name()
  1065. .captures(lhs_text)
  1066. .and_then(|c| c.get(1))
  1067. .map(|m| m.as_str());
  1068. if !(lhs_last.is_some() && lhs_last == Some(self.text(rhs).trim())) {
  1069. values.push(rhs);
  1070. }
  1071. }
  1072. } else if let Some(v) = node.child_by_field_name("value") {
  1073. // varinit — destructuring patterns don't exist in Java.
  1074. values.push(v);
  1075. }
  1076. for v in values {
  1077. if v.kind() != "method_reference" {
  1078. continue; // idTypes is EMPTY for Java — only method references
  1079. }
  1080. let mut last_ident: Option<Node> = None;
  1081. for i in 0..v.named_child_count() {
  1082. if let Some(c) = v.named_child(i) {
  1083. if c.kind() == "identifier" {
  1084. last_ident = Some(c);
  1085. }
  1086. }
  1087. }
  1088. let Some(last) = last_ident else { continue };
  1089. let m = self.text(last);
  1090. let text = self.text(v);
  1091. let name = if text.starts_with("this::") || text.starts_with("super::") {
  1092. format!("this.{m}")
  1093. } else if let Some(c) = method_ref_type_re().captures(text) {
  1094. if m == "new" {
  1095. continue;
  1096. }
  1097. format!("{}::{m}", &c[1])
  1098. } else {
  1099. continue;
  1100. };
  1101. let p = last.start_position();
  1102. self.fn_ref_cands.push(Cand {
  1103. from,
  1104. name,
  1105. line: p.row as u32 + 1,
  1106. column_byte: last.start_byte(),
  1107. row: p.row,
  1108. });
  1109. }
  1110. }
  1111. fn scan_fn_ref_subtree(&mut self, node: Node<'t>, depth: u32) {
  1112. if depth > 12 {
  1113. return;
  1114. }
  1115. // (functionTypes is empty for Java; lambda_expression halts the scan)
  1116. if depth > 0 && matches!(node.kind(), "lambda_literal" | "lambda_expression") {
  1117. return;
  1118. }
  1119. self.maybe_capture_fn_refs(node);
  1120. for i in 0..node.named_child_count() {
  1121. if let Some(c) = node.named_child(i) {
  1122. self.scan_fn_ref_subtree(c, depth + 1);
  1123. }
  1124. }
  1125. }
  1126. fn flush_fn_ref_candidates(&mut self) {
  1127. let cands = std::mem::take(&mut self.fn_ref_cands);
  1128. if cands.is_empty() || util::is_generated_file(self.file_path) {
  1129. return;
  1130. }
  1131. let mut seen: HashSet<(String, String)> = HashSet::new();
  1132. for c in cands {
  1133. if !c.name.starts_with("this.")
  1134. && !c.name.contains("::")
  1135. && !self.defined_fn_names.contains(&c.name)
  1136. && !self.imported_names.contains(&c.name)
  1137. {
  1138. continue;
  1139. }
  1140. // Dedupe on the node ID string (ids collide; the TS side keys on
  1141. // `${fromNodeId}|${name}`).
  1142. if !seen.insert((self.node_ids[c.from as usize].clone(), c.name.clone())) {
  1143. continue;
  1144. }
  1145. let column = util::col16(self.src, &self.line_starts, c.row, c.column_byte);
  1146. let name_ref = self.arena.put(&c.name);
  1147. self.tables.push_ref(&RefRow {
  1148. from_idx: c.from,
  1149. kind: FUNCTION_REF_CODE,
  1150. line: c.line,
  1151. column,
  1152. reference_name: name_ref,
  1153. candidates: NONE_STR,
  1154. from_id_str: NONE_STR,
  1155. });
  1156. }
  1157. }
  1158. // --- value references ------------------------------------------------------------
  1159. fn flush_value_refs(&mut self, root: Node<'t>) {
  1160. let scopes = std::mem::take(&mut self.value_scopes);
  1161. let mut targets = std::mem::take(&mut self.fs_values);
  1162. let counts = std::mem::take(&mut self.fs_value_counts);
  1163. if std::env::var("CODEGRAPH_VALUE_REFS").as_deref() == Ok("0") {
  1164. return;
  1165. }
  1166. if targets.is_empty() || scopes.is_empty() || util::is_generated_file(self.file_path) {
  1167. return;
  1168. }
  1169. let mut decl_counts: HashMap<&str, u32> = HashMap::new();
  1170. let mut dstack: Vec<Node> = vec![root];
  1171. let mut dvisited = 0usize;
  1172. while let Some(n) = dstack.pop() {
  1173. if dvisited >= MAX_VALUE_REF_NODES {
  1174. break;
  1175. }
  1176. dvisited += 1;
  1177. if n.kind() == "variable_declarator" {
  1178. if let Some(first) = n.named_child(0) {
  1179. if first.kind() == "identifier" {
  1180. let nm = self.text(first);
  1181. if targets.contains_key(nm) {
  1182. *decl_counts.entry(nm).or_insert(0) += 1;
  1183. }
  1184. }
  1185. }
  1186. }
  1187. for i in 0..n.named_child_count() {
  1188. if let Some(c) = n.named_child(i) {
  1189. dstack.push(c);
  1190. }
  1191. }
  1192. }
  1193. let shadowed: Vec<String> = decl_counts
  1194. .iter()
  1195. .filter(|(nm, c)| **c > counts.get(**nm).copied().unwrap_or(1))
  1196. .map(|(nm, _)| nm.to_string())
  1197. .collect();
  1198. for nm in shadowed {
  1199. targets.remove(&nm);
  1200. }
  1201. if targets.is_empty() {
  1202. return;
  1203. }
  1204. let refs_kind = edge_kind_index("references").unwrap();
  1205. for scope in &scopes {
  1206. // ID-string comparisons, matching the TS side (ids collide).
  1207. let mut seen: HashSet<&str> = HashSet::new();
  1208. let mut stack: Vec<Node> = vec![scope.node];
  1209. let mut visited = 0usize;
  1210. while let Some(n) = stack.pop() {
  1211. if visited >= MAX_VALUE_REF_NODES {
  1212. break;
  1213. }
  1214. visited += 1;
  1215. if matches!(n.kind(), "identifier" | "constant" | "name" | "simple_identifier") {
  1216. let ref_name = self.text(n);
  1217. if let Some(&target_row) = targets.get(ref_name) {
  1218. let target_id = self.node_ids[target_row as usize].as_str();
  1219. if target_id != self.node_ids[scope.row as usize]
  1220. && ref_name != scope.name
  1221. && !seen.contains(&target_id)
  1222. {
  1223. seen.insert(target_id);
  1224. let meta = self.arena.put(r#"{"valueRef":true}"#);
  1225. self.tables.push_edge(&EdgeRow {
  1226. source_idx: scope.row,
  1227. target_idx: target_row,
  1228. kind: refs_kind,
  1229. provenance: 0,
  1230. line: NONE,
  1231. column: NONE,
  1232. metadata_json: meta,
  1233. source_id_str: NONE_STR,
  1234. target_id_str: NONE_STR,
  1235. });
  1236. }
  1237. }
  1238. }
  1239. for i in 0..n.named_child_count() {
  1240. if let Some(c) = n.named_child(i) {
  1241. stack.push(c);
  1242. }
  1243. }
  1244. }
  1245. }
  1246. }
  1247. // --- Lombok synthesis (#912, languages/java.ts synthesizeLombokMembers) ------------
  1248. fn lombok_annotation_names(&self, node: Node<'t>) -> HashSet<String> {
  1249. let mut names = HashSet::new();
  1250. let Some(modifiers) = self.modifiers_child(node) else { return names };
  1251. for i in 0..modifiers.named_child_count() {
  1252. let Some(child) = modifiers.named_child(i) else { continue };
  1253. if matches!(child.kind(), "marker_annotation" | "annotation") {
  1254. if let Some(name_node) = child.child_by_field_name("name") {
  1255. if let Some(simple) = self.text(name_node).trim().rsplit('.').next() {
  1256. if !simple.is_empty() {
  1257. names.insert(simple.to_string());
  1258. }
  1259. }
  1260. }
  1261. }
  1262. }
  1263. names
  1264. }
  1265. fn synthesize_lombok_members(&mut self, class_node: Node<'t>, class_row: u32) {
  1266. let class_anns = self.lombok_annotation_names(class_node);
  1267. let class_getter = class_anns.contains("Getter");
  1268. let class_setter = class_anns.contains("Setter");
  1269. let is_data = class_anns.contains("Data");
  1270. let is_value = class_anns.contains("Value");
  1271. let has_builder = class_anns.contains("Builder") || class_anns.contains("SuperBuilder");
  1272. let has_to_string = is_data || is_value || class_anns.contains("ToString");
  1273. let has_equals = is_data || is_value || class_anns.contains("EqualsAndHashCode");
  1274. let log_ann = class_anns.iter().find(|a| is_lombok_log_annotation(a)).cloned();
  1275. let Some(body) = class_node.child_by_field_name("body") else { return };
  1276. let fields: Vec<Node> = (0..body.named_child_count())
  1277. .filter_map(|i| body.named_child(i))
  1278. .filter(|c| c.kind() == "field_declaration")
  1279. .collect();
  1280. let class_has_lombok = class_getter
  1281. || class_setter
  1282. || is_data
  1283. || is_value
  1284. || has_builder
  1285. || has_to_string
  1286. || has_equals
  1287. || log_ann.is_some();
  1288. if !class_has_lombok && !fields.iter().any(|f| !self.lombok_annotation_names(*f).is_empty()) {
  1289. return;
  1290. }
  1291. // Members the source already declares (exact `classQN::name` matches).
  1292. let class_qn = self.nodes_meta[class_row as usize].qualified_name.clone();
  1293. let class_name = self.nodes_meta[class_row as usize].name.clone();
  1294. let mut taken_methods: HashSet<String> = HashSet::new();
  1295. let mut taken_fields: HashSet<String> = HashSet::new();
  1296. for m in &self.nodes_meta {
  1297. if m.qualified_name == format!("{class_qn}::{}", m.name) {
  1298. match m.kind {
  1299. "method" | "function" => {
  1300. taken_methods.insert(m.name.clone());
  1301. }
  1302. "field" | "variable" | "constant" | "property" => {
  1303. taken_fields.insert(m.name.clone());
  1304. }
  1305. _ => {}
  1306. }
  1307. }
  1308. }
  1309. let class_name_node = class_node.child_by_field_name("name").unwrap_or(class_node);
  1310. macro_rules! emit_method {
  1311. ($name:expr, $anchor:expr, $sig:expr, $from:expr, $is_static:expr, $ret:expr) => {{
  1312. let name: String = $name;
  1313. if !name.is_empty() && !taken_methods.contains(&name) {
  1314. taken_methods.insert(name.clone());
  1315. self.create_node(
  1316. "method",
  1317. &name,
  1318. $anchor,
  1319. Extra {
  1320. visibility: Some(1),
  1321. signature: Some($sig),
  1322. docstring: Some(format!("Lombok-generated ({})", $from)),
  1323. decorators: Some(vec!["lombok".to_string()]),
  1324. is_static: $is_static,
  1325. return_type: $ret,
  1326. },
  1327. );
  1328. }
  1329. }};
  1330. }
  1331. // Per-field getters/setters.
  1332. for fd in &fields {
  1333. let mods = self
  1334. .modifiers_child(*fd)
  1335. .map(|m| self.text(m))
  1336. .unwrap_or("");
  1337. if word_re("static").is_match(mods) {
  1338. continue;
  1339. }
  1340. let is_final = word_re("final").is_match(mods);
  1341. let field_anns = self.lombok_annotation_names(*fd);
  1342. let field_getter = field_anns.contains("Getter");
  1343. let field_setter = field_anns.contains("Setter");
  1344. let want_getter = class_getter || is_data || is_value || field_getter;
  1345. let want_setter = (class_setter || is_data || field_setter) && !is_final;
  1346. if !want_getter && !want_setter {
  1347. continue;
  1348. }
  1349. let type_node = fd.child_by_field_name("type");
  1350. let type_text = type_node
  1351. .map(|t| self.text(t).trim().to_string())
  1352. .unwrap_or_else(|| "Object".to_string());
  1353. let is_boolean_primitive = type_node.map(|t| t.kind() == "boolean_type").unwrap_or(false);
  1354. let return_type = self.normalize_java_type(type_node);
  1355. for i in 0..fd.named_child_count() {
  1356. let Some(vd) = fd.named_child(i) else { continue };
  1357. if vd.kind() != "variable_declarator" {
  1358. continue;
  1359. }
  1360. let Some(name_node) = vd.child_by_field_name("name") else { continue };
  1361. let field_name = self.text(name_node).trim().to_string();
  1362. if field_name.is_empty() {
  1363. continue;
  1364. }
  1365. if want_getter {
  1366. let g = if is_boolean_primitive {
  1367. if is_prefix_re(&field_name) {
  1368. field_name.clone()
  1369. } else {
  1370. format!("is{}", capitalize(&field_name))
  1371. }
  1372. } else {
  1373. format!("get{}", capitalize(&field_name))
  1374. };
  1375. let from = if field_getter {
  1376. "@Getter"
  1377. } else if is_data {
  1378. "@Data"
  1379. } else if is_value {
  1380. "@Value"
  1381. } else {
  1382. "@Getter"
  1383. };
  1384. emit_method!(g.clone(), name_node, format!("{type_text} {g}()"), from, None, return_type.clone());
  1385. }
  1386. if want_setter {
  1387. let base = if is_boolean_primitive && is_prefix_re(&field_name) {
  1388. field_name[2..].to_string()
  1389. } else {
  1390. field_name.clone()
  1391. };
  1392. let s = format!("set{}", capitalize(&base));
  1393. let from = if field_setter {
  1394. "@Setter"
  1395. } else if is_data {
  1396. "@Data"
  1397. } else {
  1398. "@Setter"
  1399. };
  1400. emit_method!(s.clone(), name_node, format!("void {s}({type_text} {field_name})"), from, None, None);
  1401. }
  1402. }
  1403. }
  1404. // Class-level synthesized methods.
  1405. if has_builder {
  1406. let from = if class_anns.contains("SuperBuilder") { "@SuperBuilder" } else { "@Builder" };
  1407. emit_method!(
  1408. "builder".to_string(),
  1409. class_name_node,
  1410. format!("static {class_name}.{class_name}Builder builder()"),
  1411. from,
  1412. Some(true),
  1413. Some(format!("{class_name}Builder"))
  1414. );
  1415. }
  1416. if has_to_string {
  1417. let from = if is_data { "@Data" } else if is_value { "@Value" } else { "@ToString" };
  1418. emit_method!("toString".to_string(), class_name_node, "String toString()".to_string(), from, None, None);
  1419. }
  1420. if has_equals {
  1421. let from = if is_data { "@Data" } else if is_value { "@Value" } else { "@EqualsAndHashCode" };
  1422. emit_method!("equals".to_string(), class_name_node, "boolean equals(Object o)".to_string(), from, None, None);
  1423. emit_method!("hashCode".to_string(), class_name_node, "int hashCode()".to_string(), from, None, None);
  1424. }
  1425. // Logger field (@Slf4j and friends).
  1426. if let Some(log_ann) = log_ann {
  1427. if !taken_fields.contains("log") {
  1428. self.create_node(
  1429. "field",
  1430. "log",
  1431. class_name_node,
  1432. Extra {
  1433. visibility: Some(2),
  1434. is_static: Some(true),
  1435. signature: Some("Logger log".to_string()),
  1436. docstring: Some(format!("Lombok-generated (@{log_ann})")),
  1437. decorators: Some(vec!["lombok".to_string()]),
  1438. ..Extra::default()
  1439. },
  1440. );
  1441. }
  1442. }
  1443. }
  1444. }
  1445. fn find_anonymous_class_body(node: Node) -> Option<Node> {
  1446. for i in 0..node.named_child_count() {
  1447. if let Some(child) = node.named_child(i) {
  1448. if matches!(child.kind(), "class_body" | "declaration_list") {
  1449. return Some(child);
  1450. }
  1451. }
  1452. }
  1453. None
  1454. }
  1455. /// The `new ns.Foo<T>()` name normalization shared by instantiation /
  1456. /// anonymous-class / decorator extraction: strip `<...` from the first `<`
  1457. /// (index > 0), keep the segment after the last `.`/`::`, strip ONE leading
  1458. /// `:` or `.`, trim.
  1459. fn strip_generic_and_qualifier(raw: &str) -> String {
  1460. let mut name = raw.to_string();
  1461. if let Some(lt) = name.find('<') {
  1462. if lt > 0 {
  1463. name.truncate(lt);
  1464. }
  1465. }
  1466. let last_dot = name
  1467. .rfind('.')
  1468. .map(|i| i as isize)
  1469. .unwrap_or(-1)
  1470. .max(name.rfind("::").map(|i| i as isize).unwrap_or(-1));
  1471. if last_dot >= 0 {
  1472. name = name[(last_dot as usize + 1)..].to_string();
  1473. if name.starts_with(':') || name.starts_with('.') {
  1474. name.remove(0);
  1475. }
  1476. }
  1477. name.trim().to_string()
  1478. }
  1479. fn capitalize(name: &str) -> String {
  1480. let mut chars = name.chars();
  1481. match chars.next() {
  1482. Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
  1483. None => String::new(),
  1484. }
  1485. }
  1486. /// `\bword\b` matcher (modifier keyword tests in languages/java.ts).
  1487. fn word_re(word: &'static str) -> &'static Regex {
  1488. static STATIC_RE: OnceLock<Regex> = OnceLock::new();
  1489. static FINAL_RE: OnceLock<Regex> = OnceLock::new();
  1490. match word {
  1491. "static" => STATIC_RE.get_or_init(|| Regex::new(r"\bstatic\b").unwrap()),
  1492. "final" => FINAL_RE.get_or_init(|| Regex::new(r"\bfinal\b").unwrap()),
  1493. _ => unreachable!("word_re only supports static/final"),
  1494. }
  1495. }
  1496. fn opt_str(arena: &mut Arena, s: Option<&str>) -> StrRef {
  1497. match s {
  1498. Some(s) => arena.put(s),
  1499. None => NONE_STR,
  1500. }
  1501. }