code_moniker_core/lang/java/
mod.rs1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use tree_sitter::{Language, Parser, Tree};
5
6use crate::core::code_graph::CodeGraph;
7use crate::core::moniker::Moniker;
8
9use crate::lang::canonical_walker::CanonicalWalker;
10
11pub mod build;
12mod canonicalize;
13mod kinds;
14mod strategy;
15
16use canonicalize::{compute_module_moniker, read_package_name};
17use strategy::{Strategy, collect_callable_table, collect_type_table};
18
19#[derive(Clone, Debug, Default)]
20pub struct Presets {
21 pub external_packages: Vec<String>,
22}
23
24pub fn parse(source: &str) -> Tree {
25 let mut parser = Parser::new();
26 let language: Language = tree_sitter_java::LANGUAGE.into();
27 parser
28 .set_language(&language)
29 .expect("failed to load tree-sitter Java grammar");
30 parser
31 .parse(source, None)
32 .expect("tree-sitter parse returned None on a non-cancelled call")
33}
34
35pub fn extract(
36 uri: &str,
37 source: &str,
38 anchor: &Moniker,
39 deep: bool,
40 presets: &Presets,
41) -> CodeGraph {
42 let tree = parse(source);
43 let pkg = read_package_name(tree.root_node(), source.as_bytes());
44 let pieces: Vec<&str> = pkg.split('.').filter(|s| !s.is_empty()).collect();
45 let module = compute_module_moniker(anchor, uri, &pieces);
46 let (def_cap, ref_cap) = CodeGraph::capacity_for_source(source.len());
47 let mut graph = CodeGraph::with_capacity(module.clone(), kinds::MODULE, def_cap, ref_cap);
48 let mut type_table: HashMap<&[u8], Moniker> = HashMap::new();
49 collect_type_table(
50 tree.root_node(),
51 source.as_bytes(),
52 &module,
53 &mut type_table,
54 );
55 let mut callable_table: HashMap<(Moniker, Vec<u8>), Vec<u8>> = HashMap::new();
56 collect_callable_table(
57 tree.root_node(),
58 source.as_bytes(),
59 &module,
60 &mut callable_table,
61 );
62 let strat = Strategy {
63 module: module.clone(),
64 source_bytes: source.as_bytes(),
65 deep,
66 presets,
67 imports: RefCell::new(HashMap::<Vec<u8>, &'static [u8]>::new()),
68 import_targets: RefCell::new(HashMap::<Vec<u8>, _>::new()),
69 local_scope: RefCell::new(Vec::new()),
70 type_table,
71 callable_table,
72 };
73 let walker = CanonicalWalker::new(&strat, source.as_bytes());
74 walker.walk(tree.root_node(), &module, &mut graph);
75 graph
76}
77
78pub struct Lang;
79
80impl crate::lang::LangExtractor for Lang {
81 type Presets = Presets;
82 const LANG_TAG: &'static str = "java";
83 const ALLOWED_KINDS: &'static [&'static str] = &[
84 "class",
85 "interface",
86 "enum",
87 "record",
88 "annotation_type",
89 "method",
90 "constructor",
91 "field",
92 "enum_constant",
93 ];
94 const ALLOWED_VISIBILITIES: &'static [&'static str] =
95 &["public", "protected", "package", "private"];
96
97 fn extract(
98 uri: &str,
99 source: &str,
100 anchor: &Moniker,
101 deep: bool,
102 presets: &Self::Presets,
103 ) -> CodeGraph {
104 extract(uri, source, anchor, deep, presets)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::core::moniker::MonikerBuilder;
112 use crate::lang::assert_conformance;
113
114 fn make_anchor() -> Moniker {
115 MonikerBuilder::new().project(b"app").build()
116 }
117
118 fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
119 let g = extract(uri, source, anchor, deep, &Presets::default());
120 assert_conformance::<super::Lang>(&g, anchor);
121 g
122 }
123
124 #[test]
125 fn parse_empty_returns_program() {
126 let tree = parse("");
127 assert_eq!(tree.root_node().kind(), "program");
128 }
129
130 #[test]
131 fn extract_default_package_skips_package_segments() {
132 let g = extract_default("Foo.java", "class Foo {}", &make_anchor(), false);
133 let expected = MonikerBuilder::new()
134 .project(b"app")
135 .segment(b"lang", b"java")
136 .segment(b"module", b"Foo")
137 .build();
138 assert_eq!(g.root(), &expected);
139 }
140
141 #[test]
142 fn extract_class_emits_class_def_with_package_visibility_default() {
143 let g = extract_default("Foo.java", "class Foo {}", &make_anchor(), false);
144 let foo = g.defs().find(|d| d.kind == b"class").expect("class def");
145 assert_eq!(foo.visibility, b"package".to_vec());
146 }
147
148 #[test]
149 fn extract_field_one_def_per_declarator() {
150 let src = "class Foo { int a, b; private String name; }";
151 let g = extract_default("Foo.java", src, &make_anchor(), false);
152 let fields: Vec<_> = g.defs().filter(|d| d.kind == b"field").collect();
153 assert_eq!(
154 fields.len(),
155 3,
156 "got {:?}",
157 fields.iter().map(|d| &d.moniker).collect::<Vec<_>>()
158 );
159 let private_field = fields
160 .iter()
161 .find(|d| d.moniker.as_view().segments().last().unwrap().name == b"name")
162 .unwrap();
163 assert_eq!(private_field.visibility, b"private".to_vec());
164 }
165
166 #[test]
167 fn extract_enum_emits_enum_constants() {
168 let g = extract_default(
169 "Color.java",
170 "public enum Color { RED, GREEN }",
171 &make_anchor(),
172 false,
173 );
174 let red = MonikerBuilder::new()
175 .project(b"app")
176 .segment(b"lang", b"java")
177 .segment(b"module", b"Color")
178 .segment(b"enum", b"Color")
179 .segment(b"enum_constant", b"RED")
180 .build();
181 assert!(
182 g.contains(&red),
183 "missing RED, defs: {:?}",
184 g.def_monikers()
185 );
186 }
187
188 #[test]
189 fn extract_wildcard_import_emits_imports_module() {
190 let src = "import com.acme.*;\nclass Foo {}";
191 let g = extract_default("Foo.java", src, &make_anchor(), false);
192 let r = g
193 .refs()
194 .find(|r| r.kind == b"imports_module")
195 .expect("imports_module ref");
196 assert_eq!(r.confidence, b"imported".to_vec());
197 }
198
199 #[test]
200 fn extract_method_call_carries_receiver_hint() {
201 let src = r#"
202 class Foo {
203 void m() { this.bar(); }
204 void bar() {}
205 }
206 "#;
207 let g = extract_default("Foo.java", src, &make_anchor(), false);
208 let r = g
209 .refs()
210 .find(|r| r.kind == b"method_call")
211 .expect("method_call ref");
212 assert_eq!(r.receiver_hint, b"this".to_vec());
213 }
214
215 #[test]
216 fn method_call_on_imported_class_carries_imported_confidence() {
217 let src = r#"
218 import com.acme.Util;
219 class Foo {
220 void m() { Util.run(); }
221 }
222 "#;
223 let g = extract_default("src/Foo.java", src, &make_anchor(), false);
224 let r = g
225 .refs()
226 .find(|r| r.kind == b"method_call" && r.receiver_hint == b"Util")
227 .expect("method_call on Util");
228 assert_eq!(r.confidence, b"imported");
229 }
230
231 #[test]
232 fn method_call_on_non_imported_identifier_stays_name_match() {
233 let src = r#"
234 class Foo {
235 void m() { obj.bar(); }
236 }
237 "#;
238 let g = extract_default("src/Foo.java", src, &make_anchor(), false);
239 let r = g
240 .refs()
241 .find(|r| r.kind == b"method_call" && r.receiver_hint == b"obj")
242 .expect("method_call on obj");
243 assert_eq!(r.confidence, b"name_match");
244 }
245
246 #[test]
247 fn this_call_resolves_to_full_slot_signature() {
248 let src = r#"
249 class Foo {
250 void m() { this.bar(); }
251 void bar() {}
252 }
253 "#;
254 let g = extract_default("Foo.java", src, &make_anchor(), false);
255 let r = g
256 .refs()
257 .find(|r| r.kind == b"method_call")
258 .expect("method_call ref");
259 let last = r.target.as_view().segments().last().unwrap();
260 assert_eq!(last.kind, b"method");
261 assert_eq!(
262 last.name, b"bar()",
263 "this.bar() must resolve to the def's slot signature, not to a name-only fallback"
264 );
265 }
266
267 #[test]
268 fn method_call_on_unresolved_receiver_falls_back_to_name_only() {
269 let src = r#"
270 class Foo {
271 void m() { obj.bar(1); }
272 }
273 "#;
274 let g = extract_default("Foo.java", src, &make_anchor(), false);
275 let r = g
276 .refs()
277 .find(|r| r.kind == b"method_call")
278 .expect("method_call ref");
279 let last = r.target.as_view().segments().last().unwrap();
280 assert_eq!(
281 last.name, b"bar",
282 "unresolved receiver must produce a name-only target (no parens, no arity)"
283 );
284 }
285
286 #[test]
287 fn extract_imported_call_marks_confidence_imported() {
288 let src = r#"
289 import com.acme.Helpers;
290 class Foo { void m() { Helpers.go(); } }
291 "#;
292 let g = extract_default("Foo.java", src, &make_anchor(), false);
293 let reads_helpers = g.refs().find(|r| {
294 r.kind == b"reads" && r.target.as_view().segments().last().unwrap().name == b"Helpers"
295 });
296 if let Some(r) = reads_helpers {
297 assert_eq!(r.confidence, b"imported".to_vec());
298 }
299 }
300
301 #[test]
302 fn extract_deep_catch_param_emits_local_def() {
303 let src = r#"
304 class Foo {
305 void m() { try {} catch (IOException e) { e.toString(); } }
306 }
307 "#;
308 let g = extract_default("Foo.java", src, &make_anchor(), true);
309 let monikers = g.def_monikers();
310 let e = monikers.iter().find(|m| {
311 let last = m.as_view().segments().last().unwrap();
312 last.kind == b"param" && last.name == b"e"
313 });
314 assert!(
315 e.is_some(),
316 "catch param should be emitted as a param def in deep mode"
317 );
318 }
319
320 #[test]
321 fn extract_deep_enhanced_for_var_is_local() {
322 let src = r#"
323 class Foo {
324 void m(java.util.List<String> xs) { for (String x : xs) { x.length(); } }
325 }
326 "#;
327 let g = extract_default("Foo.java", src, &make_anchor(), true);
328 assert!(
329 g.defs().any(|d| d.kind == b"local"
330 && d.moniker.as_view().segments().last().unwrap().name == b"x"),
331 "enhanced-for var should be a local def"
332 );
333 }
334}