1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{Location, Position, Range, Url};
4
5#[derive(Debug, Clone)]
6pub struct NodeInfo {
7 pub src: String,
8 pub name_location: Option<String>,
9 pub name_locations: Vec<String>,
10 pub referenced_declaration: Option<u64>,
11 pub node_type: Option<String>,
12 pub member_location: Option<String>,
13 pub absolute_path: Option<String>,
14}
15
16pub const CHILD_KEYS: &[&str] = &[
18 "AST",
19 "arguments",
20 "baseContracts",
21 "baseExpression",
22 "baseName",
23 "baseType",
24 "block",
25 "body",
26 "components",
27 "condition",
28 "declarations",
29 "endExpression",
30 "errorCall",
31 "eventCall",
32 "expression",
33 "externalCall",
34 "falseBody",
35 "falseExpression",
36 "file",
37 "foreign",
38 "functionName",
39 "indexExpression",
40 "initialValue",
41 "initializationExpression",
42 "keyType",
43 "leftExpression",
44 "leftHandSide",
45 "libraryName",
46 "literals",
47 "loopExpression",
48 "members",
49 "modifierName",
50 "modifiers",
51 "name",
52 "names",
53 "nodes",
54 "options",
55 "overrides",
56 "parameters",
57 "pathNode",
58 "post",
59 "pre",
60 "returnParameters",
61 "rightExpression",
62 "rightHandSide",
63 "startExpression",
64 "statements",
65 "storageLayout",
66 "subExpression",
67 "subdenomination",
68 "symbolAliases",
69 "trueBody",
70 "trueExpression",
71 "typeName",
72 "unitAlias",
73 "value",
74 "valueType",
75 "variableNames",
76 "variables",
77];
78
79fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
80 if let Some(value) = tree.get(key) {
81 match value {
82 Value::Array(arr) => {
83 stack.extend(arr);
84 }
85 Value::Object(_) => {
86 stack.push(value);
87 }
88 _ => {}
89 }
90 }
91}
92
93pub type ExternalRefs = HashMap<String, u64>;
96
97pub fn cache_ids(
98 sources: &Value,
99) -> (
100 HashMap<String, HashMap<u64, NodeInfo>>,
101 HashMap<String, String>,
102 ExternalRefs,
103) {
104 let mut nodes: HashMap<String, HashMap<u64, NodeInfo>> = HashMap::new();
105 let mut path_to_abs: HashMap<String, String> = HashMap::new();
106 let mut external_refs: ExternalRefs = HashMap::new();
107
108 if let Some(sources_obj) = sources.as_object() {
109 for (path, contents) in sources_obj {
110 if let Some(contents_array) = contents.as_array()
111 && let Some(first_content) = contents_array.first()
112 && let Some(source_file) = first_content.get("source_file")
113 && let Some(ast) = source_file.get("ast")
114 {
115 let abs_path = ast
117 .get("absolutePath")
118 .and_then(|v| v.as_str())
119 .unwrap_or(path)
120 .to_string();
121
122 path_to_abs.insert(path.clone(), abs_path.clone());
123
124 if !nodes.contains_key(&abs_path) {
126 nodes.insert(abs_path.clone(), HashMap::new());
127 }
128
129 if let Some(id) = ast.get("id").and_then(|v| v.as_u64())
130 && let Some(src) = ast.get("src").and_then(|v| v.as_str())
131 {
132 nodes.get_mut(&abs_path).unwrap().insert(
133 id,
134 NodeInfo {
135 src: src.to_string(),
136 name_location: None,
137 name_locations: vec![],
138 referenced_declaration: None,
139 node_type: ast
140 .get("nodeType")
141 .and_then(|v| v.as_str())
142 .map(|s| s.to_string()),
143 member_location: None,
144 absolute_path: ast
145 .get("absolutePath")
146 .and_then(|v| v.as_str())
147 .map(|s| s.to_string()),
148 },
149 );
150 }
151
152 let mut stack = vec![ast];
153
154 while let Some(tree) = stack.pop() {
155 if let Some(id) = tree.get("id").and_then(|v| v.as_u64())
156 && let Some(src) = tree.get("src").and_then(|v| v.as_str())
157 {
158 let mut name_location = tree
160 .get("nameLocation")
161 .and_then(|v| v.as_str())
162 .map(|s| s.to_string());
163
164 if name_location.is_none()
168 && let Some(name_locations) = tree.get("nameLocations")
169 && let Some(locations_array) = name_locations.as_array()
170 && !locations_array.is_empty()
171 {
172 let node_type = tree.get("nodeType").and_then(|v| v.as_str());
173 if node_type == Some("IdentifierPath") {
174 name_location = locations_array
175 .last()
176 .and_then(|v| v.as_str())
177 .map(|s| s.to_string());
178 } else {
179 name_location = locations_array[0].as_str().map(|s| s.to_string());
180 }
181 }
182
183 let name_locations = if let Some(name_locations) = tree.get("nameLocations")
184 && let Some(locations_array) = name_locations.as_array()
185 {
186 locations_array
187 .iter()
188 .filter_map(|v| v.as_str().map(|s| s.to_string()))
189 .collect()
190 } else {
191 vec![]
192 };
193
194 let mut final_name_location = name_location;
195 if final_name_location.is_none()
196 && let Some(member_loc) =
197 tree.get("memberLocation").and_then(|v| v.as_str())
198 {
199 final_name_location = Some(member_loc.to_string());
200 }
201
202 let node_info = NodeInfo {
203 src: src.to_string(),
204 name_location: final_name_location,
205 name_locations,
206 referenced_declaration: tree
207 .get("referencedDeclaration")
208 .and_then(|v| v.as_u64()),
209 node_type: tree
210 .get("nodeType")
211 .and_then(|v| v.as_str())
212 .map(|s| s.to_string()),
213 member_location: tree
214 .get("memberLocation")
215 .and_then(|v| v.as_str())
216 .map(|s| s.to_string()),
217 absolute_path: tree
218 .get("absolutePath")
219 .and_then(|v| v.as_str())
220 .map(|s| s.to_string()),
221 };
222
223 nodes.get_mut(&abs_path).unwrap().insert(id, node_info);
224
225 if tree.get("nodeType").and_then(|v| v.as_str())
227 == Some("InlineAssembly")
228 {
229 if let Some(ext_refs) =
230 tree.get("externalReferences").and_then(|v| v.as_array())
231 {
232 for ext_ref in ext_refs {
233 if let Some(src_str) =
234 ext_ref.get("src").and_then(|v| v.as_str())
235 {
236 if let Some(decl_id) =
237 ext_ref.get("declaration").and_then(|v| v.as_u64())
238 {
239 external_refs
240 .insert(src_str.to_string(), decl_id);
241 }
242 }
243 }
244 }
245 }
246 }
247
248 for key in CHILD_KEYS {
249 push_if_node_or_array(tree, key, &mut stack);
250 }
251 }
252 }
253 }
254 }
255
256 (nodes, path_to_abs, external_refs)
257}
258
259pub fn pos_to_bytes(source_bytes: &[u8], position: Position) -> usize {
260 let text = String::from_utf8_lossy(source_bytes);
261 let lines: Vec<&str> = text.lines().collect();
262
263 let mut byte_offset = 0;
264
265 for (line_num, line_text) in lines.iter().enumerate() {
266 if line_num < position.line as usize {
267 byte_offset += line_text.len() + 1; } else if line_num == position.line as usize {
269 let char_offset = std::cmp::min(position.character as usize, line_text.len());
270 byte_offset += char_offset;
271 break;
272 }
273 }
274
275 byte_offset
276}
277
278pub fn bytes_to_pos(source_bytes: &[u8], byte_offset: usize) -> Option<Position> {
279 let text = String::from_utf8_lossy(source_bytes);
280 let mut curr_offset = 0;
281
282 for (line_num, line_text) in text.lines().enumerate() {
283 let line_bytes = line_text.len() + 1; if curr_offset + line_bytes > byte_offset {
285 let col = byte_offset - curr_offset;
286 return Some(Position::new(line_num as u32, col as u32));
287 }
288 curr_offset += line_bytes;
289 }
290
291 None
292}
293
294pub fn src_to_location(
296 src: &str,
297 id_to_path: &HashMap<String, String>,
298) -> Option<Location> {
299 let parts: Vec<&str> = src.split(':').collect();
300 if parts.len() != 3 {
301 return None;
302 }
303 let byte_offset: usize = parts[0].parse().ok()?;
304 let length: usize = parts[1].parse().ok()?;
305 let file_id = parts[2];
306 let file_path = id_to_path.get(file_id)?;
307
308 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
309 std::path::PathBuf::from(file_path)
310 } else {
311 std::env::current_dir().ok()?.join(file_path)
312 };
313
314 let source_bytes = std::fs::read(&absolute_path).ok()?;
315 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
316 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
317 let uri = Url::from_file_path(&absolute_path).ok()?;
318
319 Some(Location {
320 uri,
321 range: Range {
322 start: start_pos,
323 end: end_pos,
324 },
325 })
326}
327
328pub fn goto_bytes(
329 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
330 path_to_abs: &HashMap<String, String>,
331 id_to_path: &HashMap<String, String>,
332 external_refs: &ExternalRefs,
333 uri: &str,
334 position: usize,
335) -> Option<(String, usize)> {
336 let path = match uri.starts_with("file://") {
337 true => &uri[7..],
338 false => uri,
339 };
340
341 let abs_path = path_to_abs.get(path)?;
343
344 let current_file_nodes = nodes.get(abs_path)?;
346
347 let path_to_file_id: HashMap<&str, &str> = id_to_path
349 .iter()
350 .map(|(id, p)| (p.as_str(), id.as_str()))
351 .collect();
352
353 let current_file_id = path_to_file_id.get(abs_path.as_str());
357
358 for (src_str, decl_id) in external_refs {
360 let src_parts: Vec<&str> = src_str.split(':').collect();
361 if src_parts.len() != 3 {
362 continue;
363 }
364
365 if let Some(file_id) = current_file_id {
367 if src_parts[2] != *file_id {
368 continue;
369 }
370 } else {
371 continue;
372 }
373
374 let start_b: usize = src_parts[0].parse().ok()?;
375 let length: usize = src_parts[1].parse().ok()?;
376 let end_b = start_b + length;
377
378 if start_b <= position && position < end_b {
379 let mut target_node: Option<&NodeInfo> = None;
381 for file_nodes in nodes.values() {
382 if let Some(node) = file_nodes.get(decl_id) {
383 target_node = Some(node);
384 break;
385 }
386 }
387 let node = target_node?;
388 let (location_str, file_id) = if let Some(name_location) = &node.name_location {
389 let parts: Vec<&str> = name_location.split(':').collect();
390 if parts.len() == 3 {
391 (parts[0], parts[2])
392 } else {
393 return None;
394 }
395 } else {
396 let parts: Vec<&str> = node.src.split(':').collect();
397 if parts.len() == 3 {
398 (parts[0], parts[2])
399 } else {
400 return None;
401 }
402 };
403 let location: usize = location_str.parse().ok()?;
404 let file_path = id_to_path.get(file_id)?.clone();
405 return Some((file_path, location));
406 }
407 }
408
409 let mut refs = HashMap::new();
410
411 for (id, content) in current_file_nodes {
413 if content.referenced_declaration.is_none() {
414 continue;
415 }
416
417 let src_parts: Vec<&str> = content.src.split(':').collect();
418 if src_parts.len() != 3 {
419 continue;
420 }
421
422 let start_b: usize = src_parts[0].parse().ok()?;
423 let length: usize = src_parts[1].parse().ok()?;
424 let end_b = start_b + length;
425
426 if start_b <= position && position < end_b {
427 let diff = end_b - start_b;
428 if !refs.contains_key(&diff) || refs[&diff] <= *id {
429 refs.insert(diff, *id);
430 }
431 }
432 }
433
434 if refs.is_empty() {
435 let tmp = current_file_nodes.iter();
438 for (_id, content) in tmp {
439 if content.node_type == Some("ImportDirective".to_string()) {
440 let src_parts: Vec<&str> = content.src.split(':').collect();
441 if src_parts.len() != 3 {
442 continue;
443 }
444
445 let start_b: usize = src_parts[0].parse().ok()?;
446 let length: usize = src_parts[1].parse().ok()?;
447 let end_b = start_b + length;
448
449 if start_b <= position
450 && position < end_b
451 && let Some(import_path) = &content.absolute_path
452 {
453 return Some((import_path.clone(), 0));
454 }
455 }
456 }
457 return None;
458 }
459
460 let min_diff = *refs.keys().min()?;
462 let chosen_id = refs[&min_diff];
463 let ref_id = current_file_nodes[&chosen_id].referenced_declaration?;
464
465 let mut target_node: Option<&NodeInfo> = None;
467 for file_nodes in nodes.values() {
468 if let Some(node) = file_nodes.get(&ref_id) {
469 target_node = Some(node);
470 break;
471 }
472 }
473
474 let node = target_node?;
475
476 let (location_str, file_id) = if let Some(name_location) = &node.name_location {
478 let parts: Vec<&str> = name_location.split(':').collect();
479 if parts.len() == 3 {
480 (parts[0], parts[2])
481 } else {
482 return None;
483 }
484 } else {
485 let parts: Vec<&str> = node.src.split(':').collect();
486 if parts.len() == 3 {
487 (parts[0], parts[2])
488 } else {
489 return None;
490 }
491 };
492
493 let location: usize = location_str.parse().ok()?;
494 let file_path = id_to_path.get(file_id)?.clone();
495
496 Some((file_path, location))
497}
498
499pub fn goto_declaration(
500 ast_data: &Value,
501 file_uri: &Url,
502 position: Position,
503 source_bytes: &[u8]
504) -> Option<Location> {
505 let sources = ast_data.get("sources")?;
506 let build_infos = ast_data.get("build_infos")?.as_array()?;
507 let first_build_info = build_infos.first()?;
508 let id_to_path = first_build_info.get("source_id_to_path")?.as_object()?;
509
510 let id_to_path_map: HashMap<String, String> = id_to_path
511 .iter()
512 .map(|(k,v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
513 .collect();
514
515 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
516 let byte_position = pos_to_bytes(source_bytes, position);
517
518 if let Some((file_path, location_bytes)) = goto_bytes(
519 &nodes,
520 &path_to_abs,
521 &id_to_path_map,
522 &external_refs,
523 file_uri.as_ref(),
524 byte_position,
525 ) {
526 let target_file_path = std::path::Path::new(&file_path);
527 let absolute_path = if target_file_path.is_absolute() {
528 target_file_path.to_path_buf()
529 } else {
530 std::env::current_dir().ok()?.join(target_file_path)
531 };
532
533 if let Ok(target_source_bytes) = std::fs::read(&absolute_path)
534 && let Some(target_position) = bytes_to_pos(&target_source_bytes, location_bytes)
535 && let Ok(target_uri) = Url::from_file_path(&absolute_path)
536 {
537 return Some(Location {
538 uri: target_uri,
539 range: Range {
540 start: target_position,
541 end: target_position,
542 }
543 });
544 }
545
546 };
547
548 None
549
550
551}