1use tree_sitter::Node;
4
5use super::{field_text, line, named_children, spring, text, LangSpec, RawContract, Target};
6use crate::facts::{Import, ImportItem, RefKind, TypeSource, ValueExpr};
7use crate::model::SymbolKind;
8
9pub struct Kotlin;
10
11impl LangSpec for Kotlin {
12 fn receiver(&self) -> &'static str {
13 "this"
14 }
15
16 fn contract(&self, node: Node, src: &[u8]) -> Option<RawContract> {
17 if let Some(r) = spring::route(node, src) {
18 return Some(r);
19 }
20 if node.kind() != "call_expression" {
21 return None;
22 }
23 let kids = named_children(node);
24 let callee = kids
25 .first()
26 .filter(|k| k.kind() == "navigation_expression")?;
27 let parts = named_children(*callee);
28 if parts.last().is_some_and(|l| text(*l, src) == "uri") {
29 let inner = parts.first().filter(|p| p.kind() == "call_expression")?;
30 let inner_nav = named_children(*inner)
31 .into_iter()
32 .find(|k| k.kind() == "navigation_expression")?;
33 let verb = spring::webclient_verb(text(*named_children(inner_nav).last()?, src))?;
34 let list = kids.iter().find(|k| k.kind() == "value_arguments")?;
35 return Some(RawContract::Op {
36 receiver: vec![spring::WEBCLIENT.into()],
37 method: verb,
38 args: spring::args(*list, src),
39 });
40 }
41 let mut receiver = path(*callee, src)?;
42 let method = receiver.pop()?;
43 if !spring::REST_TEMPLATE.iter().any(|(m, _)| *m == method) {
44 return None;
45 }
46 let list = kids.iter().find(|k| k.kind() == "value_arguments")?;
47 Some(RawContract::Op {
48 receiver,
49 method,
50 args: spring::args(*list, src),
51 })
52 }
53
54 fn value(&self, node: Node, src: &[u8]) -> Option<(String, ValueExpr)> {
55 if node.kind() != "property_declaration" {
56 return None;
57 }
58 let name = property_name(node, src)?;
59 let init = named_children(node).into_iter().last()?;
60 Some((name, spring::value(init, src)?))
61 }
62
63 fn package(&self, root: Node, src: &[u8]) -> Option<String> {
64 let header = named_children(root)
65 .into_iter()
66 .find(|n| n.kind() == "package_header")?;
67 named_children(header)
68 .into_iter()
69 .find(|n| n.kind() == "qualified_identifier")
70 .map(|n| text(n, src).to_string())
71 }
72
73 fn def(&self, node: Node, src: &[u8], in_class: bool) -> Option<(SymbolKind, String)> {
74 let kind = match node.kind() {
75 "class_declaration" if has_token(node, "interface") => SymbolKind::Interface,
76 "class_declaration" | "object_declaration" => SymbolKind::Class,
77 "function_declaration" if in_class => SymbolKind::Method,
78 "function_declaration" => SymbolKind::Function,
79 "enum_entry" => {
80 let name = named_children(node)
81 .into_iter()
82 .find(|n| n.kind() == "identifier")?;
83 return Some((SymbolKind::Constant, text(name, src).to_string()));
84 }
85 "infix_expression" => {
87 let ids: Vec<&str> = named_children(node)
88 .into_iter()
89 .filter(|n| n.kind() == "identifier")
90 .map(|n| text(n, src))
91 .collect();
92 let at = ids.iter().position(|t| *t == "class")?;
93 return Some((SymbolKind::Class, ids.get(at + 1)?.to_string()));
94 }
95 "ERROR" if has_token(node, "class") || has_token(node, "object") => {
97 let name = named_children(node)
98 .into_iter()
99 .find(|n| n.kind() == "identifier")?;
100 return Some((SymbolKind::Class, text(name, src).to_string()));
101 }
102 _ => return None,
103 };
104 Some((kind, field_text(node, "name", src)?.to_string()))
105 }
106
107 fn variable(&self, node: Node, src: &[u8]) -> Option<String> {
108 (node.kind() == "property_declaration").then(|| property_name(node, src))?
109 }
110
111 fn import(&self, node: Node, src: &[u8]) -> Option<Vec<Import>> {
112 if node.kind() != "import" {
113 return None;
114 }
115 let kids = named_children(node);
116 let full = kids
117 .iter()
118 .find(|n| n.kind() == "qualified_identifier")
119 .map(|n| text(*n, src).to_string())?;
120 let alias = kids
121 .iter()
122 .rev()
123 .find(|n| n.kind() == "identifier")
124 .map(|n| text(*n, src).to_string());
125 let (module, items) = if has_token(node, "*") {
126 (full, vec![ImportItem::All])
127 } else {
128 let (m, name) = full.rsplit_once('.')?;
129 (
130 m.to_string(),
131 vec![ImportItem::Name {
132 name: name.to_string(),
133 alias,
134 }],
135 )
136 };
137 Some(vec![Import {
138 module,
139 level: 0,
140 items,
141 line: line(node),
142 file: None,
143 }])
144 }
145
146 fn reference(&self, node: Node, src: &[u8]) -> Option<(RefKind, Vec<String>)> {
147 if node.kind() != "call_expression" {
148 return None;
149 }
150 Some((
151 RefKind::Call,
152 path(named_children(node).into_iter().next()?, src)?,
153 ))
154 }
155
156 fn assignment(&self, node: Node, src: &[u8]) -> Option<(Target, TypeSource)> {
157 match node.kind() {
158 "property_declaration" => {
159 let name = property_name(node, src)?;
160 let in_class = node.parent().is_some_and(|p| p.kind() == "class_body");
161 let target = if in_class {
162 Target::Attr(name)
163 } else {
164 Target::Var(name)
165 };
166 let decl = named_children(node)
167 .into_iter()
168 .find(|n| n.kind() == "variable_declaration")?;
169 if let Some(t) = named_children(decl)
170 .into_iter()
171 .find_map(|t| type_path(t, src))
172 {
173 return Some((target, TypeSource::Annotation(t)));
174 }
175 let value = named_children(node)
176 .into_iter()
177 .find(|n| n.kind() == "call_expression")?;
178 let callee = path(named_children(value).into_iter().next()?, src)?;
179 Some((target, TypeSource::Construct(callee)))
180 }
181 "class_parameter" if has_token(node, "val") || has_token(node, "var") => {
183 let kids = named_children(node);
184 let name = kids.iter().find(|n| n.kind() == "identifier")?;
185 let ty = kids.iter().find_map(|t| type_path(*t, src))?;
186 Some((
187 Target::Attr(text(*name, src).to_string()),
188 TypeSource::Annotation(ty),
189 ))
190 }
191 _ => None,
192 }
193 }
194
195 fn bases(&self, def: Node, src: &[u8]) -> Vec<(RefKind, Vec<String>)> {
196 let Some(specs) = named_children(def)
197 .into_iter()
198 .find(|n| n.kind() == "delegation_specifiers")
199 else {
200 return Vec::new();
201 };
202 named_children(specs)
203 .into_iter()
204 .filter_map(|spec| {
205 let inner = named_children(spec).into_iter().next()?;
206 match inner.kind() {
207 "constructor_invocation" => Some((
208 RefKind::Inherits,
209 type_path(named_children(inner).into_iter().next()?, src)?,
210 )),
211 _ => Some((RefKind::Implements, type_path(inner, src)?)),
212 }
213 })
214 .collect()
215 }
216
217 fn returns(&self, def: Node, src: &[u8]) -> Option<Vec<String>> {
218 let kids = named_children(def);
219 let params = kids
220 .iter()
221 .position(|n| n.kind() == "function_value_parameters")?;
222 kids[params + 1..]
223 .iter()
224 .take_while(|n| n.kind() != "function_body")
225 .find_map(|t| type_path(*t, src))
226 }
227
228 fn params(&self, def: Node, src: &[u8]) -> Vec<(String, Vec<String>, bool)> {
229 let Some(ps) = named_children(def)
230 .into_iter()
231 .find(|n| n.kind() == "function_value_parameters")
232 else {
233 return Vec::new();
234 };
235 named_children(ps)
236 .into_iter()
237 .filter(|p| p.kind() == "parameter")
238 .filter_map(|p| {
239 let kids = named_children(p);
240 let name = kids.iter().find(|n| n.kind() == "identifier")?;
241 let ty = kids.iter().find_map(|t| type_path(*t, src))?;
242 Some((text(*name, src).to_string(), ty, false))
243 })
244 .collect()
245 }
246}
247
248fn has_token(node: Node, kind: &str) -> bool {
249 (0..node.child_count())
250 .filter_map(|i| node.child(i))
251 .any(|c| c.kind() == kind)
252}
253
254fn property_name(node: Node, src: &[u8]) -> Option<String> {
255 let decl = named_children(node)
256 .into_iter()
257 .find(|n| n.kind() == "variable_declaration")?;
258 named_children(decl)
259 .into_iter()
260 .find(|n| n.kind() == "identifier")
261 .map(|n| text(n, src).to_string())
262}
263
264fn path(node: Node, src: &[u8]) -> Option<Vec<String>> {
265 match node.kind() {
266 "identifier" => Some(vec![text(node, src).to_string()]),
267 "this_expression" => Some(vec!["this".into()]),
268 "navigation_expression" => {
269 let kids = named_children(node);
270 let mut p = path(*kids.first()?, src)?;
271 let last = kids.last()?;
272 if last.kind() != "identifier" {
273 return None;
274 }
275 p.push(text(*last, src).to_string());
276 Some(p)
277 }
278 _ => None,
279 }
280}
281
282fn type_path(node: Node, src: &[u8]) -> Option<Vec<String>> {
283 match node.kind() {
284 "user_type" => {
285 let ids: Vec<String> = named_children(node)
286 .into_iter()
287 .filter(|n| n.kind() == "identifier")
288 .map(|n| text(n, src).to_string())
289 .collect();
290 (!ids.is_empty()).then_some(ids)
291 }
292 "nullable_type" | "type" | "parenthesized_type" => named_children(node)
293 .into_iter()
294 .find_map(|t| type_path(t, src)),
295 _ => None,
296 }
297}