1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{Location, Position, Range, Url};
4use tree_sitter::{Node, Parser};
5
6#[derive(Debug, Clone)]
7pub struct NodeInfo {
8 pub src: String,
9 pub name_location: Option<String>,
10 pub name_locations: Vec<String>,
11 pub referenced_declaration: Option<u64>,
12 pub node_type: Option<String>,
13 pub member_location: Option<String>,
14 pub absolute_path: Option<String>,
15}
16
17pub const CHILD_KEYS: &[&str] = &[
19 "AST",
20 "arguments",
21 "baseContracts",
22 "baseExpression",
23 "baseName",
24 "baseType",
25 "block",
26 "body",
27 "components",
28 "condition",
29 "declarations",
30 "endExpression",
31 "errorCall",
32 "eventCall",
33 "expression",
34 "externalCall",
35 "falseBody",
36 "falseExpression",
37 "file",
38 "foreign",
39 "functionName",
40 "indexExpression",
41 "initialValue",
42 "initializationExpression",
43 "keyType",
44 "leftExpression",
45 "leftHandSide",
46 "libraryName",
47 "literals",
48 "loopExpression",
49 "members",
50 "modifierName",
51 "modifiers",
52 "name",
53 "names",
54 "nodes",
55 "options",
56 "overrides",
57 "parameters",
58 "pathNode",
59 "post",
60 "pre",
61 "returnParameters",
62 "rightExpression",
63 "rightHandSide",
64 "startExpression",
65 "statements",
66 "storageLayout",
67 "subExpression",
68 "subdenomination",
69 "symbolAliases",
70 "trueBody",
71 "trueExpression",
72 "typeName",
73 "unitAlias",
74 "value",
75 "valueType",
76 "variableNames",
77 "variables",
78];
79
80fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
81 if let Some(value) = tree.get(key) {
82 match value {
83 Value::Array(arr) => {
84 stack.extend(arr);
85 }
86 Value::Object(_) => {
87 stack.push(value);
88 }
89 _ => {}
90 }
91 }
92}
93
94pub type ExternalRefs = HashMap<String, u64>;
97
98#[derive(Debug, Clone)]
101pub struct CachedBuild {
102 pub ast: Value,
103 pub nodes: HashMap<String, HashMap<u64, NodeInfo>>,
104 pub path_to_abs: HashMap<String, String>,
105 pub external_refs: ExternalRefs,
106 pub id_to_path_map: HashMap<String, String>,
107 pub build_version: i32,
110}
111
112impl CachedBuild {
113 pub fn new(ast: Value, build_version: i32) -> Self {
115 let (nodes, path_to_abs, external_refs) = if let Some(sources) = ast.get("sources") {
116 cache_ids(sources)
117 } else {
118 (HashMap::new(), HashMap::new(), HashMap::new())
119 };
120
121 let id_to_path_map = ast
122 .get("build_infos")
123 .and_then(|v| v.as_array())
124 .and_then(|arr| arr.first())
125 .and_then(|info| info.get("source_id_to_path"))
126 .and_then(|v| v.as_object())
127 .map(|obj| {
128 obj.iter()
129 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
130 .collect()
131 })
132 .unwrap_or_default();
133
134 Self {
135 ast,
136 nodes,
137 path_to_abs,
138 external_refs,
139 id_to_path_map,
140 build_version,
141 }
142 }
143}
144
145type Type = (
146 HashMap<String, HashMap<u64, NodeInfo>>,
147 HashMap<String, String>,
148 ExternalRefs,
149);
150
151pub fn cache_ids(sources: &Value) -> Type {
152 let mut nodes: HashMap<String, HashMap<u64, NodeInfo>> = HashMap::new();
153 let mut path_to_abs: HashMap<String, String> = HashMap::new();
154 let mut external_refs: ExternalRefs = HashMap::new();
155
156 if let Some(sources_obj) = sources.as_object() {
157 for (path, contents) in sources_obj {
158 if let Some(contents_array) = contents.as_array()
159 && let Some(first_content) = contents_array.first()
160 && let Some(source_file) = first_content.get("source_file")
161 && let Some(ast) = source_file.get("ast")
162 {
163 let abs_path = ast
165 .get("absolutePath")
166 .and_then(|v| v.as_str())
167 .unwrap_or(path)
168 .to_string();
169
170 path_to_abs.insert(path.clone(), abs_path.clone());
171
172 if !nodes.contains_key(&abs_path) {
174 nodes.insert(abs_path.clone(), HashMap::new());
175 }
176
177 if let Some(id) = ast.get("id").and_then(|v| v.as_u64())
178 && let Some(src) = ast.get("src").and_then(|v| v.as_str())
179 {
180 nodes.get_mut(&abs_path).unwrap().insert(
181 id,
182 NodeInfo {
183 src: src.to_string(),
184 name_location: None,
185 name_locations: vec![],
186 referenced_declaration: None,
187 node_type: ast
188 .get("nodeType")
189 .and_then(|v| v.as_str())
190 .map(|s| s.to_string()),
191 member_location: None,
192 absolute_path: ast
193 .get("absolutePath")
194 .and_then(|v| v.as_str())
195 .map(|s| s.to_string()),
196 },
197 );
198 }
199
200 let mut stack = vec![ast];
201
202 while let Some(tree) = stack.pop() {
203 if let Some(id) = tree.get("id").and_then(|v| v.as_u64())
204 && let Some(src) = tree.get("src").and_then(|v| v.as_str())
205 {
206 let mut name_location = tree
208 .get("nameLocation")
209 .and_then(|v| v.as_str())
210 .map(|s| s.to_string());
211
212 if name_location.is_none()
216 && let Some(name_locations) = tree.get("nameLocations")
217 && let Some(locations_array) = name_locations.as_array()
218 && !locations_array.is_empty()
219 {
220 let node_type = tree.get("nodeType").and_then(|v| v.as_str());
221 if node_type == Some("IdentifierPath") {
222 name_location = locations_array
223 .last()
224 .and_then(|v| v.as_str())
225 .map(|s| s.to_string());
226 } else {
227 name_location = locations_array[0].as_str().map(|s| s.to_string());
228 }
229 }
230
231 let name_locations = if let Some(name_locations) = tree.get("nameLocations")
232 && let Some(locations_array) = name_locations.as_array()
233 {
234 locations_array
235 .iter()
236 .filter_map(|v| v.as_str().map(|s| s.to_string()))
237 .collect()
238 } else {
239 vec![]
240 };
241
242 let mut final_name_location = name_location;
243 if final_name_location.is_none()
244 && let Some(member_loc) =
245 tree.get("memberLocation").and_then(|v| v.as_str())
246 {
247 final_name_location = Some(member_loc.to_string());
248 }
249
250 let node_info = NodeInfo {
251 src: src.to_string(),
252 name_location: final_name_location,
253 name_locations,
254 referenced_declaration: tree
255 .get("referencedDeclaration")
256 .and_then(|v| v.as_u64()),
257 node_type: tree
258 .get("nodeType")
259 .and_then(|v| v.as_str())
260 .map(|s| s.to_string()),
261 member_location: tree
262 .get("memberLocation")
263 .and_then(|v| v.as_str())
264 .map(|s| s.to_string()),
265 absolute_path: tree
266 .get("absolutePath")
267 .and_then(|v| v.as_str())
268 .map(|s| s.to_string()),
269 };
270
271 nodes.get_mut(&abs_path).unwrap().insert(id, node_info);
272
273 if tree.get("nodeType").and_then(|v| v.as_str()) == Some("InlineAssembly")
275 && let Some(ext_refs) =
276 tree.get("externalReferences").and_then(|v| v.as_array())
277 {
278 for ext_ref in ext_refs {
279 if let Some(src_str) = ext_ref.get("src").and_then(|v| v.as_str())
280 && let Some(decl_id) =
281 ext_ref.get("declaration").and_then(|v| v.as_u64())
282 {
283 external_refs.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 crate::utils::position_to_byte_offset(&text, position)
303}
304
305pub fn bytes_to_pos(source_bytes: &[u8], byte_offset: usize) -> Option<Position> {
306 let text = String::from_utf8_lossy(source_bytes);
307 let pos = crate::utils::byte_offset_to_position(&text, byte_offset);
308 Some(pos)
309}
310
311pub fn src_to_location(src: &str, id_to_path: &HashMap<String, String>) -> Option<Location> {
313 let parts: Vec<&str> = src.split(':').collect();
314 if parts.len() != 3 {
315 return None;
316 }
317 let byte_offset: usize = parts[0].parse().ok()?;
318 let length: usize = parts[1].parse().ok()?;
319 let file_id = parts[2];
320 let file_path = id_to_path.get(file_id)?;
321
322 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
323 std::path::PathBuf::from(file_path)
324 } else {
325 std::env::current_dir().ok()?.join(file_path)
326 };
327
328 let source_bytes = std::fs::read(&absolute_path).ok()?;
329 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
330 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
331 let uri = Url::from_file_path(&absolute_path).ok()?;
332
333 Some(Location {
334 uri,
335 range: Range {
336 start: start_pos,
337 end: end_pos,
338 },
339 })
340}
341
342pub fn goto_bytes(
343 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
344 path_to_abs: &HashMap<String, String>,
345 id_to_path: &HashMap<String, String>,
346 external_refs: &ExternalRefs,
347 uri: &str,
348 position: usize,
349) -> Option<(String, usize, usize)> {
350 let path = match uri.starts_with("file://") {
351 true => &uri[7..],
352 false => uri,
353 };
354
355 let abs_path = path_to_abs.get(path)?;
357
358 let current_file_nodes = nodes.get(abs_path)?;
360
361 let path_to_file_id: HashMap<&str, &str> = id_to_path
363 .iter()
364 .map(|(id, p)| (p.as_str(), id.as_str()))
365 .collect();
366
367 let current_file_id = path_to_file_id.get(abs_path.as_str());
371
372 for (src_str, decl_id) in external_refs {
374 let src_parts: Vec<&str> = src_str.split(':').collect();
375 if src_parts.len() != 3 {
376 continue;
377 }
378
379 if let Some(file_id) = current_file_id {
381 if src_parts[2] != *file_id {
382 continue;
383 }
384 } else {
385 continue;
386 }
387
388 let start_b: usize = src_parts[0].parse().ok()?;
389 let length: usize = src_parts[1].parse().ok()?;
390 let end_b = start_b + length;
391
392 if start_b <= position && position < end_b {
393 let mut target_node: Option<&NodeInfo> = None;
395 for file_nodes in nodes.values() {
396 if let Some(node) = file_nodes.get(decl_id) {
397 target_node = Some(node);
398 break;
399 }
400 }
401 let node = target_node?;
402 let (location_str, length_str, file_id) =
403 if let Some(name_location) = &node.name_location {
404 let parts: Vec<&str> = name_location.split(':').collect();
405 if parts.len() == 3 {
406 (parts[0], parts[1], parts[2])
407 } else {
408 return None;
409 }
410 } else {
411 let parts: Vec<&str> = node.src.split(':').collect();
412 if parts.len() == 3 {
413 (parts[0], parts[1], parts[2])
414 } else {
415 return None;
416 }
417 };
418 let location: usize = location_str.parse().ok()?;
419 let len: usize = length_str.parse().ok()?;
420 let file_path = id_to_path.get(file_id)?.clone();
421 return Some((file_path, location, len));
422 }
423 }
424
425 let mut refs = HashMap::new();
426
427 for (id, content) in current_file_nodes {
429 if content.referenced_declaration.is_none() {
430 continue;
431 }
432
433 let src_parts: Vec<&str> = content.src.split(':').collect();
434 if src_parts.len() != 3 {
435 continue;
436 }
437
438 let start_b: usize = src_parts[0].parse().ok()?;
439 let length: usize = src_parts[1].parse().ok()?;
440 let end_b = start_b + length;
441
442 if start_b <= position && position < end_b {
443 let diff = end_b - start_b;
444 if !refs.contains_key(&diff) || refs[&diff] <= *id {
445 refs.insert(diff, *id);
446 }
447 }
448 }
449
450 if refs.is_empty() {
451 let tmp = current_file_nodes.iter();
454 for (_id, content) in tmp {
455 if content.node_type == Some("ImportDirective".to_string()) {
456 let src_parts: Vec<&str> = content.src.split(':').collect();
457 if src_parts.len() != 3 {
458 continue;
459 }
460
461 let start_b: usize = src_parts[0].parse().ok()?;
462 let length: usize = src_parts[1].parse().ok()?;
463 let end_b = start_b + length;
464
465 if start_b <= position
466 && position < end_b
467 && let Some(import_path) = &content.absolute_path
468 {
469 return Some((import_path.clone(), 0, 0));
470 }
471 }
472 }
473 return None;
474 }
475
476 let min_diff = *refs.keys().min()?;
478 let chosen_id = refs[&min_diff];
479 let ref_id = current_file_nodes[&chosen_id].referenced_declaration?;
480
481 let mut target_node: Option<&NodeInfo> = None;
483 for file_nodes in nodes.values() {
484 if let Some(node) = file_nodes.get(&ref_id) {
485 target_node = Some(node);
486 break;
487 }
488 }
489
490 let node = target_node?;
491
492 let (location_str, length_str, file_id) = if let Some(name_location) = &node.name_location {
494 let parts: Vec<&str> = name_location.split(':').collect();
495 if parts.len() == 3 {
496 (parts[0], parts[1], parts[2])
497 } else {
498 return None;
499 }
500 } else {
501 let parts: Vec<&str> = node.src.split(':').collect();
502 if parts.len() == 3 {
503 (parts[0], parts[1], parts[2])
504 } else {
505 return None;
506 }
507 };
508
509 let location: usize = location_str.parse().ok()?;
510 let len: usize = length_str.parse().ok()?;
511 let file_path = id_to_path.get(file_id)?.clone();
512
513 Some((file_path, location, len))
514}
515
516pub fn goto_declaration(
517 ast_data: &Value,
518 file_uri: &Url,
519 position: Position,
520 source_bytes: &[u8],
521) -> Option<Location> {
522 let sources = ast_data.get("sources")?;
523 let build_infos = ast_data.get("build_infos")?.as_array()?;
524 let first_build_info = build_infos.first()?;
525 let id_to_path = first_build_info.get("source_id_to_path")?.as_object()?;
526
527 let id_to_path_map: HashMap<String, String> = id_to_path
528 .iter()
529 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
530 .collect();
531
532 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
533 let byte_position = pos_to_bytes(source_bytes, position);
534
535 if let Some((file_path, location_bytes, length)) = goto_bytes(
536 &nodes,
537 &path_to_abs,
538 &id_to_path_map,
539 &external_refs,
540 file_uri.as_ref(),
541 byte_position,
542 ) {
543 let target_file_path = std::path::Path::new(&file_path);
544 let absolute_path = if target_file_path.is_absolute() {
545 target_file_path.to_path_buf()
546 } else {
547 std::env::current_dir().ok()?.join(target_file_path)
548 };
549
550 if let Ok(target_source_bytes) = std::fs::read(&absolute_path)
551 && let Some(start_pos) = bytes_to_pos(&target_source_bytes, location_bytes)
552 && let Some(end_pos) = bytes_to_pos(&target_source_bytes, location_bytes + length)
553 && let Ok(target_uri) = Url::from_file_path(&absolute_path)
554 {
555 return Some(Location {
556 uri: target_uri,
557 range: Range {
558 start: start_pos,
559 end: end_pos,
560 },
561 });
562 }
563 };
564
565 None
566}
567
568pub fn goto_declaration_by_name(
579 cached_build: &CachedBuild,
580 file_uri: &Url,
581 name: &str,
582 byte_hint: usize,
583) -> Option<Location> {
584 let path = match file_uri.as_ref().starts_with("file://") {
585 true => &file_uri.as_ref()[7..],
586 false => file_uri.as_ref(),
587 };
588 let abs_path = cached_build.path_to_abs.get(path)?;
589 let current_file_nodes = cached_build.nodes.get(abs_path)?;
590
591 let built_source = std::fs::read_to_string(abs_path).ok()?;
593
594 let mut candidates: Vec<(usize, usize, u64)> = Vec::new();
596
597 for (_id, node) in current_file_nodes {
598 let ref_id = match node.referenced_declaration {
599 Some(id) => id,
600 None => continue,
601 };
602
603 let src_parts: Vec<&str> = node.src.split(':').collect();
605 if src_parts.len() != 3 {
606 continue;
607 }
608 let start: usize = match src_parts[0].parse() {
609 Ok(v) => v,
610 Err(_) => continue,
611 };
612 let length: usize = match src_parts[1].parse() {
613 Ok(v) => v,
614 Err(_) => continue,
615 };
616
617 if start + length > built_source.len() {
618 continue;
619 }
620
621 let node_text = &built_source[start..start + length];
622
623 let matches = node_text == name
628 || node_text.contains(&format!(".{name}("))
629 || node_text.ends_with(&format!(".{name}"));
630
631 if matches {
632 let distance = if byte_hint >= start && byte_hint < start + length {
636 0 } else if byte_hint < start {
638 start - byte_hint
639 } else {
640 byte_hint - (start + length)
641 };
642 candidates.push((distance, length, ref_id));
643 }
644 }
645
646 candidates.sort_by_key(|&(dist, span, _)| (dist, span));
648 let ref_id = candidates.first()?.2;
649
650 let mut target_node: Option<&NodeInfo> = None;
652 for file_nodes in cached_build.nodes.values() {
653 if let Some(node) = file_nodes.get(&ref_id) {
654 target_node = Some(node);
655 break;
656 }
657 }
658
659 let node = target_node?;
660
661 let loc_str = node.name_location.as_deref().unwrap_or(&node.src);
663 let parts: Vec<&str> = loc_str.split(':').collect();
664 if parts.len() != 3 {
665 return None;
666 }
667 let location_bytes: usize = parts[0].parse().ok()?;
668 let length: usize = parts[1].parse().ok()?;
669 let file_id = parts[2];
670
671 let file_path = cached_build.id_to_path_map.get(file_id)?;
672
673 let target_file_path = std::path::Path::new(file_path);
674 let absolute_path = if target_file_path.is_absolute() {
675 target_file_path.to_path_buf()
676 } else {
677 std::env::current_dir().ok()?.join(target_file_path)
678 };
679
680 let target_source_bytes = std::fs::read(&absolute_path).ok()?;
681 let start_pos = bytes_to_pos(&target_source_bytes, location_bytes)?;
682 let end_pos = bytes_to_pos(&target_source_bytes, location_bytes + length)?;
683 let target_uri = Url::from_file_path(&absolute_path).ok()?;
684
685 Some(Location {
686 uri: target_uri,
687 range: Range {
688 start: start_pos,
689 end: end_pos,
690 },
691 })
692}
693
694#[derive(Debug, Clone)]
698pub struct CursorContext {
699 pub name: String,
701 pub function: Option<String>,
703 pub contract: Option<String>,
705 pub object: Option<String>,
709 pub arg_count: Option<usize>,
712 pub arg_types: Vec<Option<String>>,
715}
716
717fn ts_parse(source: &str) -> Option<tree_sitter::Tree> {
719 let mut parser = Parser::new();
720 parser
721 .set_language(&tree_sitter_solidity::LANGUAGE.into())
722 .expect("failed to load Solidity grammar");
723 parser.parse(source, None)
724}
725
726pub fn validate_goto_target(target_source: &str, location: &Location, expected_name: &str) -> bool {
732 let line = location.range.start.line as usize;
733 let start_col = location.range.start.character as usize;
734 let end_col = location.range.end.character as usize;
735
736 if let Some(line_text) = target_source.lines().nth(line) {
737 if end_col <= line_text.len() {
738 return &line_text[start_col..end_col] == expected_name;
739 }
740 }
741 true
743}
744
745fn ts_node_at_byte(node: Node, byte: usize) -> Option<Node> {
747 if byte < node.start_byte() || byte >= node.end_byte() {
748 return None;
749 }
750 let mut cursor = node.walk();
751 for child in node.children(&mut cursor) {
752 if child.start_byte() <= byte
753 && byte < child.end_byte()
754 && let Some(deeper) = ts_node_at_byte(child, byte)
755 {
756 return Some(deeper);
757 }
758 }
759 Some(node)
760}
761
762fn ts_child_id_text<'a>(node: Node<'a>, source: &'a str) -> Option<&'a str> {
764 let mut cursor = node.walk();
765 node.children(&mut cursor)
766 .find(|c| c.kind() == "identifier" && c.is_named())
767 .map(|c| &source[c.byte_range()])
768}
769
770fn infer_argument_type<'a>(arg_node: Node<'a>, source: &'a str) -> Option<String> {
776 let expr = if arg_node.kind() == "call_argument" {
778 let mut c = arg_node.walk();
779 arg_node.children(&mut c).find(|ch| ch.is_named())?
780 } else {
781 arg_node
782 };
783
784 match expr.kind() {
785 "identifier" => {
786 let var_name = &source[expr.byte_range()];
787 find_variable_type(expr, source, var_name)
789 }
790 "number_literal" | "decimal_number" | "hex_number" => Some("uint256".into()),
791 "boolean_literal" => Some("bool".into()),
792 "string_literal" | "hex_string_literal" => Some("string".into()),
793 _ => None,
794 }
795}
796
797fn find_variable_type(from: Node, source: &str, var_name: &str) -> Option<String> {
802 let mut scope = from.parent();
803 while let Some(node) = scope {
804 match node.kind() {
805 "function_definition" | "modifier_definition" | "constructor_definition" => {
806 let mut c = node.walk();
808 for child in node.children(&mut c) {
809 if child.kind() == "parameter" {
810 if let Some(id) = ts_child_id_text(child, source) {
811 if id == var_name {
812 let mut pc = child.walk();
814 return child
815 .children(&mut pc)
816 .find(|c| {
817 matches!(
818 c.kind(),
819 "type_name"
820 | "primitive_type"
821 | "user_defined_type"
822 | "mapping"
823 )
824 })
825 .map(|t| source[t.byte_range()].trim().to_string());
826 }
827 }
828 }
829 }
830 }
831 "function_body" | "block_statement" | "unchecked_block" => {
832 let mut c = node.walk();
834 for child in node.children(&mut c) {
835 if child.kind() == "variable_declaration_statement"
836 || child.kind() == "variable_declaration"
837 {
838 if let Some(id) = ts_child_id_text(child, source) {
839 if id == var_name {
840 let mut pc = child.walk();
841 return child
842 .children(&mut pc)
843 .find(|c| {
844 matches!(
845 c.kind(),
846 "type_name"
847 | "primitive_type"
848 | "user_defined_type"
849 | "mapping"
850 )
851 })
852 .map(|t| source[t.byte_range()].trim().to_string());
853 }
854 }
855 }
856 }
857 }
858 "contract_declaration" | "library_declaration" | "interface_declaration" => {
859 if let Some(body) = ts_find_child(node, "contract_body") {
861 let mut c = body.walk();
862 for child in body.children(&mut c) {
863 if child.kind() == "state_variable_declaration" {
864 if let Some(id) = ts_child_id_text(child, source) {
865 if id == var_name {
866 let mut pc = child.walk();
867 return child
868 .children(&mut pc)
869 .find(|c| {
870 matches!(
871 c.kind(),
872 "type_name"
873 | "primitive_type"
874 | "user_defined_type"
875 | "mapping"
876 )
877 })
878 .map(|t| source[t.byte_range()].trim().to_string());
879 }
880 }
881 }
882 }
883 }
884 }
885 _ => {}
886 }
887 scope = node.parent();
888 }
889 None
890}
891
892fn infer_call_arg_types(call_node: Node, source: &str) -> Vec<Option<String>> {
894 let mut cursor = call_node.walk();
895 call_node
896 .children(&mut cursor)
897 .filter(|c| c.kind() == "call_argument")
898 .map(|arg| infer_argument_type(arg, source))
899 .collect()
900}
901
902fn best_overload<'a>(
910 decls: &'a [TsDeclaration],
911 arg_count: Option<usize>,
912 arg_types: &[Option<String>],
913) -> Option<&'a TsDeclaration> {
914 if decls.len() == 1 {
915 return decls.first();
916 }
917 if decls.is_empty() {
918 return None;
919 }
920
921 let func_decls: Vec<&TsDeclaration> =
923 decls.iter().filter(|d| d.param_count.is_some()).collect();
924
925 if func_decls.is_empty() {
926 return decls.first();
927 }
928
929 let count_matched: Vec<&&TsDeclaration> = if let Some(ac) = arg_count {
931 let matched: Vec<_> = func_decls
932 .iter()
933 .filter(|d| d.param_count == Some(ac))
934 .collect();
935 if matched.len() == 1 {
936 return Some(matched[0]);
937 }
938 if matched.is_empty() {
939 func_decls.iter().collect()
941 } else {
942 matched
943 }
944 } else {
945 func_decls.iter().collect()
946 };
947
948 if !arg_types.is_empty() {
950 let mut best: Option<(&TsDeclaration, usize)> = None;
951 for &&decl in &count_matched {
952 let score = arg_types
953 .iter()
954 .zip(decl.param_types.iter())
955 .filter(|(arg_ty, param_ty)| {
956 if let Some(at) = arg_ty {
957 at == param_ty.as_str()
958 } else {
959 false
960 }
961 })
962 .count();
963 if best.is_none() || score > best.unwrap().1 {
964 best = Some((decl, score));
965 }
966 }
967 if let Some((decl, _)) = best {
968 return Some(decl);
969 }
970 }
971
972 count_matched.first().map(|d| **d).or(decls.first())
974}
975
976pub fn cursor_context(source: &str, position: Position) -> Option<CursorContext> {
980 let tree = ts_parse(source)?;
981 let byte = pos_to_bytes(source.as_bytes(), position);
982 let leaf = ts_node_at_byte(tree.root_node(), byte)?;
983
984 let id_node = if leaf.kind() == "identifier" {
986 leaf
987 } else {
988 let parent = leaf.parent()?;
990 if parent.kind() == "identifier" {
991 parent
992 } else {
993 return None;
994 }
995 };
996
997 let name = source[id_node.byte_range()].to_string();
998 let mut function = None;
999 let mut contract = None;
1000
1001 let object = id_node.parent().and_then(|parent| {
1005 if parent.kind() == "member_expression" {
1006 let prop = parent.child_by_field_name("property")?;
1007 if prop.id() == id_node.id() {
1009 let obj = parent.child_by_field_name("object")?;
1010 Some(source[obj.byte_range()].to_string())
1011 } else {
1012 None
1013 }
1014 } else {
1015 None
1016 }
1017 });
1018
1019 let (arg_count, arg_types) = {
1023 let mut node = id_node.parent();
1024 let mut result = (None, vec![]);
1025 while let Some(n) = node {
1026 if n.kind() == "call_expression" {
1027 let types = infer_call_arg_types(n, source);
1028 result = (Some(types.len()), types);
1029 break;
1030 }
1031 node = n.parent();
1032 }
1033 result
1034 };
1035
1036 let mut current = id_node.parent();
1038 while let Some(node) = current {
1039 match node.kind() {
1040 "function_definition" | "modifier_definition" if function.is_none() => {
1041 function = ts_child_id_text(node, source).map(String::from);
1042 }
1043 "constructor_definition" if function.is_none() => {
1044 function = Some("constructor".into());
1045 }
1046 "contract_declaration" | "interface_declaration" | "library_declaration"
1047 if contract.is_none() =>
1048 {
1049 contract = ts_child_id_text(node, source).map(String::from);
1050 }
1051 _ => {}
1052 }
1053 current = node.parent();
1054 }
1055
1056 Some(CursorContext {
1057 name,
1058 function,
1059 contract,
1060 object,
1061 arg_count,
1062 arg_types,
1063 })
1064}
1065
1066#[derive(Debug, Clone)]
1068pub struct TsDeclaration {
1069 pub range: Range,
1071 pub kind: &'static str,
1073 pub container: Option<String>,
1075 pub param_count: Option<usize>,
1077 pub param_types: Vec<String>,
1080}
1081
1082pub fn find_declarations_by_name(source: &str, name: &str) -> Vec<TsDeclaration> {
1087 let tree = match ts_parse(source) {
1088 Some(t) => t,
1089 None => return vec![],
1090 };
1091 let mut results = Vec::new();
1092 collect_declarations(tree.root_node(), source, name, None, &mut results);
1093 results
1094}
1095
1096fn collect_declarations(
1097 node: Node,
1098 source: &str,
1099 name: &str,
1100 container: Option<&str>,
1101 out: &mut Vec<TsDeclaration>,
1102) {
1103 let mut cursor = node.walk();
1104 for child in node.children(&mut cursor) {
1105 if !child.is_named() {
1106 continue;
1107 }
1108 match child.kind() {
1109 "contract_declaration" | "interface_declaration" | "library_declaration" => {
1110 if let Some(id_name) = ts_child_id_text(child, source) {
1111 if id_name == name {
1112 out.push(TsDeclaration {
1113 range: id_range(child),
1114 kind: child.kind(),
1115 container: container.map(String::from),
1116 param_count: None,
1117 param_types: vec![],
1118 });
1119 }
1120 if let Some(body) = ts_find_child(child, "contract_body") {
1122 collect_declarations(body, source, name, Some(id_name), out);
1123 }
1124 }
1125 }
1126 "function_definition" | "modifier_definition" => {
1127 if let Some(id_name) = ts_child_id_text(child, source) {
1128 if id_name == name {
1129 let types = parameter_type_signature(child, source);
1130 out.push(TsDeclaration {
1131 range: id_range(child),
1132 kind: child.kind(),
1133 container: container.map(String::from),
1134 param_count: Some(types.len()),
1135 param_types: types.into_iter().map(String::from).collect(),
1136 });
1137 }
1138 collect_parameters(child, source, name, container, out);
1140 if let Some(body) = ts_find_child(child, "function_body") {
1142 collect_declarations(body, source, name, container, out);
1143 }
1144 }
1145 }
1146 "constructor_definition" => {
1147 if name == "constructor" {
1148 let types = parameter_type_signature(child, source);
1149 out.push(TsDeclaration {
1150 range: ts_range(child),
1151 kind: "constructor_definition",
1152 container: container.map(String::from),
1153 param_count: Some(types.len()),
1154 param_types: types.into_iter().map(String::from).collect(),
1155 });
1156 }
1157 collect_parameters(child, source, name, container, out);
1159 if let Some(body) = ts_find_child(child, "function_body") {
1160 collect_declarations(body, source, name, container, out);
1161 }
1162 }
1163 "state_variable_declaration" | "variable_declaration" => {
1164 if let Some(id_name) = ts_child_id_text(child, source)
1165 && id_name == name
1166 {
1167 out.push(TsDeclaration {
1168 range: id_range(child),
1169 kind: child.kind(),
1170 container: container.map(String::from),
1171 param_count: None,
1172 param_types: vec![],
1173 });
1174 }
1175 }
1176 "struct_declaration" => {
1177 if let Some(id_name) = ts_child_id_text(child, source) {
1178 if id_name == name {
1179 out.push(TsDeclaration {
1180 range: id_range(child),
1181 kind: "struct_declaration",
1182 container: container.map(String::from),
1183 param_count: None,
1184 param_types: vec![],
1185 });
1186 }
1187 if let Some(body) = ts_find_child(child, "struct_body") {
1188 collect_declarations(body, source, name, Some(id_name), out);
1189 }
1190 }
1191 }
1192 "enum_declaration" => {
1193 if let Some(id_name) = ts_child_id_text(child, source) {
1194 if id_name == name {
1195 out.push(TsDeclaration {
1196 range: id_range(child),
1197 kind: "enum_declaration",
1198 container: container.map(String::from),
1199 param_count: None,
1200 param_types: vec![],
1201 });
1202 }
1203 if let Some(body) = ts_find_child(child, "enum_body") {
1205 let mut ecur = body.walk();
1206 for val in body.children(&mut ecur) {
1207 if val.kind() == "enum_value" && &source[val.byte_range()] == name {
1208 out.push(TsDeclaration {
1209 range: ts_range(val),
1210 kind: "enum_value",
1211 container: Some(id_name.to_string()),
1212 param_count: None,
1213 param_types: vec![],
1214 });
1215 }
1216 }
1217 }
1218 }
1219 }
1220 "event_definition" | "error_declaration" => {
1221 if let Some(id_name) = ts_child_id_text(child, source)
1222 && id_name == name
1223 {
1224 out.push(TsDeclaration {
1225 range: id_range(child),
1226 kind: child.kind(),
1227 container: container.map(String::from),
1228 param_count: None,
1229 param_types: vec![],
1230 });
1231 }
1232 }
1233 "user_defined_type_definition" => {
1234 if let Some(id_name) = ts_child_id_text(child, source)
1235 && id_name == name
1236 {
1237 out.push(TsDeclaration {
1238 range: id_range(child),
1239 kind: "user_defined_type_definition",
1240 container: container.map(String::from),
1241 param_count: None,
1242 param_types: vec![],
1243 });
1244 }
1245 }
1246 _ => {
1248 collect_declarations(child, source, name, container, out);
1249 }
1250 }
1251 }
1252}
1253
1254fn parameter_type_signature<'a>(node: Node<'a>, source: &'a str) -> Vec<&'a str> {
1260 let mut cursor = node.walk();
1261 node.children(&mut cursor)
1262 .filter(|c| c.kind() == "parameter")
1263 .filter_map(|param| {
1264 let mut pc = param.walk();
1265 param
1266 .children(&mut pc)
1267 .find(|c| {
1268 matches!(
1269 c.kind(),
1270 "type_name" | "primitive_type" | "user_defined_type" | "mapping"
1271 )
1272 })
1273 .map(|t| source[t.byte_range()].trim())
1274 })
1275 .collect()
1276}
1277
1278fn collect_parameters(
1280 node: Node,
1281 source: &str,
1282 name: &str,
1283 container: Option<&str>,
1284 out: &mut Vec<TsDeclaration>,
1285) {
1286 let mut cursor = node.walk();
1287 for child in node.children(&mut cursor) {
1288 if child.kind() == "parameter"
1289 && let Some(id_name) = ts_child_id_text(child, source)
1290 && id_name == name
1291 {
1292 out.push(TsDeclaration {
1293 range: id_range(child),
1294 kind: "parameter",
1295 container: container.map(String::from),
1296 param_count: None,
1297 param_types: vec![],
1298 });
1299 }
1300 }
1301}
1302
1303fn ts_range(node: Node) -> Range {
1305 let s = node.start_position();
1306 let e = node.end_position();
1307 Range {
1308 start: Position::new(s.row as u32, s.column as u32),
1309 end: Position::new(e.row as u32, e.column as u32),
1310 }
1311}
1312
1313fn id_range(node: Node) -> Range {
1315 let mut cursor = node.walk();
1316 node.children(&mut cursor)
1317 .find(|c| c.kind() == "identifier" && c.is_named())
1318 .map(|c| ts_range(c))
1319 .unwrap_or_else(|| ts_range(node))
1320}
1321
1322fn ts_find_child<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
1323 let mut cursor = node.walk();
1324 node.children(&mut cursor).find(|c| c.kind() == kind)
1325}
1326
1327pub fn goto_definition_ts(
1335 source: &str,
1336 position: Position,
1337 file_uri: &Url,
1338 completion_cache: &crate::completion::CompletionCache,
1339 text_cache: &HashMap<String, (i32, String)>,
1340) -> Option<Location> {
1341 let ctx = cursor_context(source, position)?;
1342
1343 if let Some(obj_name) = &ctx.object {
1348 if let Some(path) = find_file_for_contract(completion_cache, obj_name, file_uri) {
1349 let target_source = read_target_source(&path, text_cache)?;
1350 let target_uri = Url::from_file_path(&path).ok()?;
1351 let decls = find_declarations_by_name(&target_source, &ctx.name);
1352 if let Some(d) = best_overload(&decls, ctx.arg_count, &ctx.arg_types) {
1353 return Some(Location {
1354 uri: target_uri,
1355 range: d.range,
1356 });
1357 }
1358 }
1359 let decls = find_declarations_by_name(source, &ctx.name);
1361 if let Some(d) = best_overload(&decls, ctx.arg_count, &ctx.arg_types) {
1362 return Some(Location {
1363 uri: file_uri.clone(),
1364 range: d.range,
1365 });
1366 }
1367 }
1368
1369 let resolved = resolve_via_cache(&ctx, file_uri, completion_cache);
1372
1373 match resolved {
1374 Some(ResolvedTarget::SameFile) => {
1375 find_best_declaration(source, &ctx, file_uri)
1377 }
1378 Some(ResolvedTarget::OtherFile { path, name }) => {
1379 let target_source = read_target_source(&path, text_cache);
1381 let target_source = target_source?;
1382 let target_uri = Url::from_file_path(&path).ok()?;
1383 let decls = find_declarations_by_name(&target_source, &name);
1384 decls.first().map(|d| Location {
1385 uri: target_uri,
1386 range: d.range,
1387 })
1388 }
1389 None => {
1390 find_best_declaration(source, &ctx, file_uri)
1392 }
1393 }
1394}
1395
1396#[derive(Debug)]
1397enum ResolvedTarget {
1398 SameFile,
1400 OtherFile { path: String, name: String },
1402}
1403
1404fn resolve_via_cache(
1410 ctx: &CursorContext,
1411 file_uri: &Url,
1412 cache: &crate::completion::CompletionCache,
1413) -> Option<ResolvedTarget> {
1414 let contract_scope = ctx
1416 .contract
1417 .as_ref()
1418 .and_then(|name| cache.name_to_node_id.get(name.as_str()))
1419 .copied();
1420
1421 if let Some(contract_id) = contract_scope {
1423 if let Some(func_name) = &ctx.function {
1425 if let Some(func_scope_id) = find_function_scope(cache, contract_id, func_name) {
1428 if let Some(decls) = cache.scope_declarations.get(&func_scope_id)
1430 && decls.iter().any(|d| d.name == ctx.name)
1431 {
1432 return Some(ResolvedTarget::SameFile);
1433 }
1434 }
1435 }
1436
1437 if let Some(decls) = cache.scope_declarations.get(&contract_id)
1439 && decls.iter().any(|d| d.name == ctx.name)
1440 {
1441 return Some(ResolvedTarget::SameFile);
1442 }
1443
1444 if let Some(bases) = cache.linearized_base_contracts.get(&contract_id) {
1446 for &base_id in bases.iter().skip(1) {
1447 if let Some(decls) = cache.scope_declarations.get(&base_id)
1448 && decls.iter().any(|d| d.name == ctx.name)
1449 {
1450 let base_name = cache
1453 .name_to_node_id
1454 .iter()
1455 .find(|&(_, &id)| id == base_id)
1456 .map(|(name, _)| name.clone());
1457
1458 if let Some(base_name) = base_name
1459 && let Some(path) = find_file_for_contract(cache, &base_name, file_uri)
1460 {
1461 return Some(ResolvedTarget::OtherFile {
1462 path,
1463 name: ctx.name.clone(),
1464 });
1465 }
1466 return Some(ResolvedTarget::SameFile);
1468 }
1469 }
1470 }
1471 }
1472
1473 if cache.name_to_node_id.contains_key(&ctx.name) {
1475 if let Some(path) = find_file_for_contract(cache, &ctx.name, file_uri) {
1477 let current_path = file_uri.to_file_path().ok()?;
1478 let current_str = current_path.to_str()?;
1479 if path == current_str || path.ends_with(current_str) || current_str.ends_with(&path) {
1480 return Some(ResolvedTarget::SameFile);
1481 }
1482 return Some(ResolvedTarget::OtherFile {
1483 path,
1484 name: ctx.name.clone(),
1485 });
1486 }
1487 return Some(ResolvedTarget::SameFile);
1488 }
1489
1490 if cache.name_to_type.contains_key(&ctx.name) {
1492 return Some(ResolvedTarget::SameFile);
1493 }
1494
1495 None
1496}
1497
1498fn find_function_scope(
1500 cache: &crate::completion::CompletionCache,
1501 contract_id: u64,
1502 func_name: &str,
1503) -> Option<u64> {
1504 for (&scope_id, &parent_id) in &cache.scope_parent {
1508 if parent_id == contract_id {
1509 if let Some(contract_decls) = cache.scope_declarations.get(&contract_id)
1513 && contract_decls.iter().any(|d| d.name == func_name)
1514 {
1515 if cache.scope_declarations.contains_key(&scope_id)
1519 || cache.scope_parent.values().any(|&p| p == scope_id)
1520 {
1521 return Some(scope_id);
1522 }
1523 }
1524 }
1525 }
1526 None
1527}
1528
1529fn find_file_for_contract(
1531 cache: &crate::completion::CompletionCache,
1532 contract_name: &str,
1533 _file_uri: &Url,
1534) -> Option<String> {
1535 let node_id = cache.name_to_node_id.get(contract_name)?;
1540 let scope_range = cache.scope_ranges.iter().find(|r| r.node_id == *node_id)?;
1541 let file_id = scope_range.file_id;
1542
1543 cache
1545 .path_to_file_id
1546 .iter()
1547 .find(|&(_, &fid)| fid == file_id)
1548 .map(|(path, _)| path.clone())
1549}
1550
1551fn read_target_source(path: &str, text_cache: &HashMap<String, (i32, String)>) -> Option<String> {
1553 let uri = Url::from_file_path(path).ok()?;
1555 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
1556 return Some(content.clone());
1557 }
1558 std::fs::read_to_string(path).ok()
1560}
1561
1562fn find_best_declaration(source: &str, ctx: &CursorContext, file_uri: &Url) -> Option<Location> {
1564 let decls = find_declarations_by_name(source, &ctx.name);
1565 if decls.is_empty() {
1566 return None;
1567 }
1568
1569 if decls.len() == 1 {
1571 return Some(Location {
1572 uri: file_uri.clone(),
1573 range: decls[0].range,
1574 });
1575 }
1576
1577 if let Some(contract_name) = &ctx.contract
1579 && let Some(d) = decls
1580 .iter()
1581 .find(|d| d.container.as_deref() == Some(contract_name))
1582 {
1583 return Some(Location {
1584 uri: file_uri.clone(),
1585 range: d.range,
1586 });
1587 }
1588
1589 Some(Location {
1591 uri: file_uri.clone(),
1592 range: decls[0].range,
1593 })
1594}
1595
1596#[cfg(test)]
1597mod ts_tests {
1598 use super::*;
1599
1600 #[test]
1601 fn test_cursor_context_state_var() {
1602 let source = r#"
1603contract Token {
1604 uint256 public totalSupply;
1605 function mint(uint256 amount) public {
1606 totalSupply += amount;
1607 }
1608}
1609"#;
1610 let ctx = cursor_context(source, Position::new(4, 8)).unwrap();
1612 assert_eq!(ctx.name, "totalSupply");
1613 assert_eq!(ctx.function.as_deref(), Some("mint"));
1614 assert_eq!(ctx.contract.as_deref(), Some("Token"));
1615 }
1616
1617 #[test]
1618 fn test_cursor_context_top_level() {
1619 let source = r#"
1620contract Foo {}
1621contract Bar {}
1622"#;
1623 let ctx = cursor_context(source, Position::new(1, 9)).unwrap();
1625 assert_eq!(ctx.name, "Foo");
1626 assert!(ctx.function.is_none());
1627 assert_eq!(ctx.contract.as_deref(), Some("Foo"));
1629 }
1630
1631 #[test]
1632 fn test_find_declarations() {
1633 let source = r#"
1634contract Token {
1635 uint256 public totalSupply;
1636 function mint(uint256 amount) public {
1637 totalSupply += amount;
1638 }
1639}
1640"#;
1641 let decls = find_declarations_by_name(source, "totalSupply");
1642 assert_eq!(decls.len(), 1);
1643 assert_eq!(decls[0].kind, "state_variable_declaration");
1644 assert_eq!(decls[0].container.as_deref(), Some("Token"));
1645 }
1646
1647 #[test]
1648 fn test_find_declarations_multiple_contracts() {
1649 let source = r#"
1650contract A {
1651 uint256 public value;
1652}
1653contract B {
1654 uint256 public value;
1655}
1656"#;
1657 let decls = find_declarations_by_name(source, "value");
1658 assert_eq!(decls.len(), 2);
1659 assert_eq!(decls[0].container.as_deref(), Some("A"));
1660 assert_eq!(decls[1].container.as_deref(), Some("B"));
1661 }
1662
1663 #[test]
1664 fn test_find_declarations_enum_value() {
1665 let source = "contract Foo { enum Status { Active, Paused } }";
1666 let decls = find_declarations_by_name(source, "Active");
1667 assert_eq!(decls.len(), 1);
1668 assert_eq!(decls[0].kind, "enum_value");
1669 assert_eq!(decls[0].container.as_deref(), Some("Status"));
1670 }
1671
1672 #[test]
1673 fn test_cursor_context_short_param() {
1674 let source = r#"
1675contract Shop {
1676 uint256 public TAX;
1677 constructor(uint256 price, uint16 tax, uint16 taxBase) {
1678 TAX = tax;
1679 }
1680}
1681"#;
1682 let ctx = cursor_context(source, Position::new(4, 14)).unwrap();
1684 assert_eq!(ctx.name, "tax");
1685 assert_eq!(ctx.contract.as_deref(), Some("Shop"));
1686
1687 let ctx2 = cursor_context(source, Position::new(4, 8)).unwrap();
1689 assert_eq!(ctx2.name, "TAX");
1690
1691 let decls = find_declarations_by_name(source, "tax");
1693 assert_eq!(decls.len(), 1);
1694 assert_eq!(decls[0].kind, "parameter");
1695
1696 let decls_tax_base = find_declarations_by_name(source, "taxBase");
1697 assert_eq!(decls_tax_base.len(), 1);
1698 assert_eq!(decls_tax_base[0].kind, "parameter");
1699
1700 let decls_price = find_declarations_by_name(source, "price");
1701 assert_eq!(decls_price.len(), 1);
1702 assert_eq!(decls_price[0].kind, "parameter");
1703
1704 let decls_tax_upper = find_declarations_by_name(source, "TAX");
1706 assert_eq!(decls_tax_upper.len(), 1);
1707 assert_eq!(decls_tax_upper[0].kind, "state_variable_declaration");
1708 }
1709
1710 #[test]
1711 fn test_find_best_declaration_same_contract() {
1712 let source = r#"
1713contract A { uint256 public x; }
1714contract B { uint256 public x; }
1715"#;
1716 let ctx = CursorContext {
1717 name: "x".into(),
1718 function: None,
1719 contract: Some("B".into()),
1720 object: None,
1721 arg_count: None,
1722 arg_types: vec![],
1723 };
1724 let uri = Url::parse("file:///test.sol").unwrap();
1725 let loc = find_best_declaration(source, &ctx, &uri).unwrap();
1726 assert_eq!(loc.range.start.line, 2);
1728 }
1729}