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
97#[derive(Debug, Clone)]
100pub struct CachedBuild {
101 pub ast: Value,
102 pub nodes: HashMap<String, HashMap<u64, NodeInfo>>,
103 pub path_to_abs: HashMap<String, String>,
104 pub external_refs: ExternalRefs,
105 pub id_to_path_map: HashMap<String, String>,
106}
107
108impl CachedBuild {
109 pub fn new(ast: Value) -> Self {
111 let (nodes, path_to_abs, external_refs) = if let Some(sources) = ast.get("sources") {
112 cache_ids(sources)
113 } else {
114 (HashMap::new(), HashMap::new(), HashMap::new())
115 };
116
117 let id_to_path_map = ast
118 .get("build_infos")
119 .and_then(|v| v.as_array())
120 .and_then(|arr| arr.first())
121 .and_then(|info| info.get("source_id_to_path"))
122 .and_then(|v| v.as_object())
123 .map(|obj| {
124 obj.iter()
125 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
126 .collect()
127 })
128 .unwrap_or_default();
129
130 Self {
131 ast,
132 nodes,
133 path_to_abs,
134 external_refs,
135 id_to_path_map,
136 }
137 }
138}
139
140type Type = (
141 HashMap<String, HashMap<u64, NodeInfo>>,
142 HashMap<String, String>,
143 ExternalRefs,
144);
145
146pub fn cache_ids(
147 sources: &Value,
148) -> Type {
149 let mut nodes: HashMap<String, HashMap<u64, NodeInfo>> = HashMap::new();
150 let mut path_to_abs: HashMap<String, String> = HashMap::new();
151 let mut external_refs: ExternalRefs = HashMap::new();
152
153 if let Some(sources_obj) = sources.as_object() {
154 for (path, contents) in sources_obj {
155 if let Some(contents_array) = contents.as_array()
156 && let Some(first_content) = contents_array.first()
157 && let Some(source_file) = first_content.get("source_file")
158 && let Some(ast) = source_file.get("ast")
159 {
160 let abs_path = ast
162 .get("absolutePath")
163 .and_then(|v| v.as_str())
164 .unwrap_or(path)
165 .to_string();
166
167 path_to_abs.insert(path.clone(), abs_path.clone());
168
169 if !nodes.contains_key(&abs_path) {
171 nodes.insert(abs_path.clone(), HashMap::new());
172 }
173
174 if let Some(id) = ast.get("id").and_then(|v| v.as_u64())
175 && let Some(src) = ast.get("src").and_then(|v| v.as_str())
176 {
177 nodes.get_mut(&abs_path).unwrap().insert(
178 id,
179 NodeInfo {
180 src: src.to_string(),
181 name_location: None,
182 name_locations: vec![],
183 referenced_declaration: None,
184 node_type: ast
185 .get("nodeType")
186 .and_then(|v| v.as_str())
187 .map(|s| s.to_string()),
188 member_location: None,
189 absolute_path: ast
190 .get("absolutePath")
191 .and_then(|v| v.as_str())
192 .map(|s| s.to_string()),
193 },
194 );
195 }
196
197 let mut stack = vec![ast];
198
199 while let Some(tree) = stack.pop() {
200 if let Some(id) = tree.get("id").and_then(|v| v.as_u64())
201 && let Some(src) = tree.get("src").and_then(|v| v.as_str())
202 {
203 let mut name_location = tree
205 .get("nameLocation")
206 .and_then(|v| v.as_str())
207 .map(|s| s.to_string());
208
209 if name_location.is_none()
213 && let Some(name_locations) = tree.get("nameLocations")
214 && let Some(locations_array) = name_locations.as_array()
215 && !locations_array.is_empty()
216 {
217 let node_type = tree.get("nodeType").and_then(|v| v.as_str());
218 if node_type == Some("IdentifierPath") {
219 name_location = locations_array
220 .last()
221 .and_then(|v| v.as_str())
222 .map(|s| s.to_string());
223 } else {
224 name_location = locations_array[0].as_str().map(|s| s.to_string());
225 }
226 }
227
228 let name_locations = if let Some(name_locations) = tree.get("nameLocations")
229 && let Some(locations_array) = name_locations.as_array()
230 {
231 locations_array
232 .iter()
233 .filter_map(|v| v.as_str().map(|s| s.to_string()))
234 .collect()
235 } else {
236 vec![]
237 };
238
239 let mut final_name_location = name_location;
240 if final_name_location.is_none()
241 && let Some(member_loc) =
242 tree.get("memberLocation").and_then(|v| v.as_str())
243 {
244 final_name_location = Some(member_loc.to_string());
245 }
246
247 let node_info = NodeInfo {
248 src: src.to_string(),
249 name_location: final_name_location,
250 name_locations,
251 referenced_declaration: tree
252 .get("referencedDeclaration")
253 .and_then(|v| v.as_u64()),
254 node_type: tree
255 .get("nodeType")
256 .and_then(|v| v.as_str())
257 .map(|s| s.to_string()),
258 member_location: tree
259 .get("memberLocation")
260 .and_then(|v| v.as_str())
261 .map(|s| s.to_string()),
262 absolute_path: tree
263 .get("absolutePath")
264 .and_then(|v| v.as_str())
265 .map(|s| s.to_string()),
266 };
267
268 nodes.get_mut(&abs_path).unwrap().insert(id, node_info);
269
270 if tree.get("nodeType").and_then(|v| v.as_str())
272 == Some("InlineAssembly")
273 && let Some(ext_refs) =
274 tree.get("externalReferences").and_then(|v| v.as_array())
275 {
276 for ext_ref in ext_refs {
277 if let Some(src_str) =
278 ext_ref.get("src").and_then(|v| v.as_str())
279 && let Some(decl_id) =
280 ext_ref.get("declaration").and_then(|v| v.as_u64())
281 {
282 external_refs
283 .insert(src_str.to_string(), decl_id);
284 }
285 }
286 }
287 }
288
289 for key in CHILD_KEYS {
290 push_if_node_or_array(tree, key, &mut stack);
291 }
292 }
293 }
294 }
295 }
296
297 (nodes, path_to_abs, external_refs)
298}
299
300pub fn pos_to_bytes(source_bytes: &[u8], position: Position) -> usize {
301 let text = String::from_utf8_lossy(source_bytes);
302 let lines: Vec<&str> = text.lines().collect();
303
304 let mut byte_offset = 0;
305
306 for (line_num, line_text) in lines.iter().enumerate() {
307 if line_num < position.line as usize {
308 byte_offset += line_text.len() + 1; } else if line_num == position.line as usize {
310 let char_offset = std::cmp::min(position.character as usize, line_text.len());
311 byte_offset += char_offset;
312 break;
313 }
314 }
315
316 byte_offset
317}
318
319pub fn bytes_to_pos(source_bytes: &[u8], byte_offset: usize) -> Option<Position> {
320 let text = String::from_utf8_lossy(source_bytes);
321 let mut curr_offset = 0;
322
323 for (line_num, line_text) in text.lines().enumerate() {
324 let line_bytes = line_text.len() + 1; if curr_offset + line_bytes > byte_offset {
326 let col = byte_offset - curr_offset;
327 return Some(Position::new(line_num as u32, col as u32));
328 }
329 curr_offset += line_bytes;
330 }
331
332 None
333}
334
335pub fn src_to_location(
337 src: &str,
338 id_to_path: &HashMap<String, String>,
339) -> Option<Location> {
340 let parts: Vec<&str> = src.split(':').collect();
341 if parts.len() != 3 {
342 return None;
343 }
344 let byte_offset: usize = parts[0].parse().ok()?;
345 let length: usize = parts[1].parse().ok()?;
346 let file_id = parts[2];
347 let file_path = id_to_path.get(file_id)?;
348
349 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
350 std::path::PathBuf::from(file_path)
351 } else {
352 std::env::current_dir().ok()?.join(file_path)
353 };
354
355 let source_bytes = std::fs::read(&absolute_path).ok()?;
356 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
357 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
358 let uri = Url::from_file_path(&absolute_path).ok()?;
359
360 Some(Location {
361 uri,
362 range: Range {
363 start: start_pos,
364 end: end_pos,
365 },
366 })
367}
368
369pub fn goto_bytes(
370 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
371 path_to_abs: &HashMap<String, String>,
372 id_to_path: &HashMap<String, String>,
373 external_refs: &ExternalRefs,
374 uri: &str,
375 position: usize,
376) -> Option<(String, usize, usize)> {
377 let path = match uri.starts_with("file://") {
378 true => &uri[7..],
379 false => uri,
380 };
381
382 let abs_path = path_to_abs.get(path)?;
384
385 let current_file_nodes = nodes.get(abs_path)?;
387
388 let path_to_file_id: HashMap<&str, &str> = id_to_path
390 .iter()
391 .map(|(id, p)| (p.as_str(), id.as_str()))
392 .collect();
393
394 let current_file_id = path_to_file_id.get(abs_path.as_str());
398
399 for (src_str, decl_id) in external_refs {
401 let src_parts: Vec<&str> = src_str.split(':').collect();
402 if src_parts.len() != 3 {
403 continue;
404 }
405
406 if let Some(file_id) = current_file_id {
408 if src_parts[2] != *file_id {
409 continue;
410 }
411 } else {
412 continue;
413 }
414
415 let start_b: usize = src_parts[0].parse().ok()?;
416 let length: usize = src_parts[1].parse().ok()?;
417 let end_b = start_b + length;
418
419 if start_b <= position && position < end_b {
420 let mut target_node: Option<&NodeInfo> = None;
422 for file_nodes in nodes.values() {
423 if let Some(node) = file_nodes.get(decl_id) {
424 target_node = Some(node);
425 break;
426 }
427 }
428 let node = target_node?;
429 let (location_str, length_str, file_id) =
430 if let Some(name_location) = &node.name_location {
431 let parts: Vec<&str> = name_location.split(':').collect();
432 if parts.len() == 3 {
433 (parts[0], parts[1], parts[2])
434 } else {
435 return None;
436 }
437 } else {
438 let parts: Vec<&str> = node.src.split(':').collect();
439 if parts.len() == 3 {
440 (parts[0], parts[1], parts[2])
441 } else {
442 return None;
443 }
444 };
445 let location: usize = location_str.parse().ok()?;
446 let len: usize = length_str.parse().ok()?;
447 let file_path = id_to_path.get(file_id)?.clone();
448 return Some((file_path, location, len));
449 }
450 }
451
452 let mut refs = HashMap::new();
453
454 for (id, content) in current_file_nodes {
456 if content.referenced_declaration.is_none() {
457 continue;
458 }
459
460 let src_parts: Vec<&str> = content.src.split(':').collect();
461 if src_parts.len() != 3 {
462 continue;
463 }
464
465 let start_b: usize = src_parts[0].parse().ok()?;
466 let length: usize = src_parts[1].parse().ok()?;
467 let end_b = start_b + length;
468
469 if start_b <= position && position < end_b {
470 let diff = end_b - start_b;
471 if !refs.contains_key(&diff) || refs[&diff] <= *id {
472 refs.insert(diff, *id);
473 }
474 }
475 }
476
477 if refs.is_empty() {
478 let tmp = current_file_nodes.iter();
481 for (_id, content) in tmp {
482 if content.node_type == Some("ImportDirective".to_string()) {
483 let src_parts: Vec<&str> = content.src.split(':').collect();
484 if src_parts.len() != 3 {
485 continue;
486 }
487
488 let start_b: usize = src_parts[0].parse().ok()?;
489 let length: usize = src_parts[1].parse().ok()?;
490 let end_b = start_b + length;
491
492 if start_b <= position
493 && position < end_b
494 && let Some(import_path) = &content.absolute_path
495 {
496 return Some((import_path.clone(), 0, 0));
497 }
498 }
499 }
500 return None;
501 }
502
503 let min_diff = *refs.keys().min()?;
505 let chosen_id = refs[&min_diff];
506 let ref_id = current_file_nodes[&chosen_id].referenced_declaration?;
507
508 let mut target_node: Option<&NodeInfo> = None;
510 for file_nodes in nodes.values() {
511 if let Some(node) = file_nodes.get(&ref_id) {
512 target_node = Some(node);
513 break;
514 }
515 }
516
517 let node = target_node?;
518
519 let (location_str, length_str, file_id) = if let Some(name_location) = &node.name_location {
521 let parts: Vec<&str> = name_location.split(':').collect();
522 if parts.len() == 3 {
523 (parts[0], parts[1], parts[2])
524 } else {
525 return None;
526 }
527 } else {
528 let parts: Vec<&str> = node.src.split(':').collect();
529 if parts.len() == 3 {
530 (parts[0], parts[1], parts[2])
531 } else {
532 return None;
533 }
534 };
535
536 let location: usize = location_str.parse().ok()?;
537 let len: usize = length_str.parse().ok()?;
538 let file_path = id_to_path.get(file_id)?.clone();
539
540 Some((file_path, location, len))
541}
542
543pub fn goto_declaration(
544 ast_data: &Value,
545 file_uri: &Url,
546 position: Position,
547 source_bytes: &[u8]
548) -> Option<Location> {
549 let sources = ast_data.get("sources")?;
550 let build_infos = ast_data.get("build_infos")?.as_array()?;
551 let first_build_info = build_infos.first()?;
552 let id_to_path = first_build_info.get("source_id_to_path")?.as_object()?;
553
554 let id_to_path_map: HashMap<String, String> = id_to_path
555 .iter()
556 .map(|(k,v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
557 .collect();
558
559 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
560 let byte_position = pos_to_bytes(source_bytes, position);
561
562 if let Some((file_path, location_bytes, length)) = goto_bytes(
563 &nodes,
564 &path_to_abs,
565 &id_to_path_map,
566 &external_refs,
567 file_uri.as_ref(),
568 byte_position,
569 ) {
570 let target_file_path = std::path::Path::new(&file_path);
571 let absolute_path = if target_file_path.is_absolute() {
572 target_file_path.to_path_buf()
573 } else {
574 std::env::current_dir().ok()?.join(target_file_path)
575 };
576
577 if let Ok(target_source_bytes) = std::fs::read(&absolute_path)
578 && let Some(start_pos) = bytes_to_pos(&target_source_bytes, location_bytes)
579 && let Some(end_pos) = bytes_to_pos(&target_source_bytes, location_bytes + length)
580 && let Ok(target_uri) = Url::from_file_path(&absolute_path)
581 {
582 return Some(Location {
583 uri: target_uri,
584 range: Range {
585 start: start_pos,
586 end: end_pos,
587 }
588 });
589 }
590 };
591
592 None
593}