1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4use tower_lsp::lsp_types::{Location, Position, Range, Url};
5use tree_sitter::{Node, Parser};
6
7use crate::types::{NodeId, SourceLoc};
8use crate::utils::push_if_node_or_array;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct NodeInfo {
12 pub src: String,
13 pub name_location: Option<String>,
14 pub name_locations: Vec<String>,
15 pub referenced_declaration: Option<NodeId>,
16 pub node_type: Option<String>,
17 pub member_location: Option<String>,
18 pub absolute_path: Option<String>,
19}
20
21pub const CHILD_KEYS: &[&str] = &[
23 "AST",
24 "arguments",
25 "baseContracts",
26 "baseExpression",
27 "baseName",
28 "baseType",
29 "block",
30 "body",
31 "components",
32 "condition",
33 "declarations",
34 "endExpression",
35 "errorCall",
36 "eventCall",
37 "expression",
38 "externalCall",
39 "falseBody",
40 "falseExpression",
41 "file",
42 "foreign",
43 "functionName",
44 "indexExpression",
45 "initialValue",
46 "initializationExpression",
47 "keyType",
48 "leftExpression",
49 "leftHandSide",
50 "libraryName",
51 "literals",
52 "loopExpression",
53 "members",
54 "modifierName",
55 "modifiers",
56 "name",
57 "names",
58 "nodes",
59 "options",
60 "overrides",
61 "parameters",
62 "pathNode",
63 "post",
64 "pre",
65 "returnParameters",
66 "rightExpression",
67 "rightHandSide",
68 "startExpression",
69 "statements",
70 "storageLayout",
71 "subExpression",
72 "subdenomination",
73 "symbolAliases",
74 "trueBody",
75 "trueExpression",
76 "typeName",
77 "unitAlias",
78 "value",
79 "valueType",
80 "variableNames",
81 "variables",
82];
83
84pub type ExternalRefs = HashMap<String, NodeId>;
87
88#[derive(Debug, Clone)]
94pub struct CachedBuild {
95 pub nodes: HashMap<String, HashMap<NodeId, NodeInfo>>,
96 pub path_to_abs: HashMap<String, String>,
97 pub external_refs: ExternalRefs,
98 pub id_to_path_map: HashMap<String, String>,
99 pub decl_index: HashMap<i64, crate::solc_ast::DeclNode>,
103 pub node_id_to_source_path: HashMap<i64, String>,
107 pub gas_index: crate::gas::GasIndex,
110 pub hint_index: crate::inlay_hints::HintIndex,
113 pub doc_index: crate::hover::DocIndex,
116 pub completion_cache: std::sync::Arc<crate::completion::CompletionCache>,
119 pub build_version: i32,
122}
123
124impl CachedBuild {
125 pub fn new(ast: Value, build_version: i32) -> Self {
132 let (nodes, path_to_abs, external_refs) = if let Some(sources) = ast.get("sources") {
133 cache_ids(sources)
134 } else {
135 (HashMap::new(), HashMap::new(), HashMap::new())
136 };
137
138 let id_to_path_map = ast
139 .get("source_id_to_path")
140 .and_then(|v| v.as_object())
141 .map(|obj| {
142 obj.iter()
143 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
144 .collect()
145 })
146 .unwrap_or_default();
147
148 let gas_index = crate::gas::build_gas_index(&ast);
149
150 let doc_index = crate::hover::build_doc_index(&ast);
151
152 let (decl_index, node_id_to_source_path) = if let Some(sources) = ast.get("sources") {
159 match crate::solc_ast::extract_decl_nodes(sources) {
160 Some(extracted) => (extracted.decl_index, extracted.node_id_to_source_path),
161 None => (HashMap::new(), HashMap::new()),
162 }
163 } else {
164 (HashMap::new(), HashMap::new())
165 };
166
167 let constructor_index = crate::inlay_hints::build_constructor_index(&decl_index);
169 let hint_index = if let Some(sources) = ast.get("sources") {
170 crate::inlay_hints::build_hint_index(sources, &decl_index, &constructor_index)
171 } else {
172 HashMap::new()
173 };
174
175 let completion_cache = {
177 let sources = ast.get("sources");
178 let contracts = ast.get("contracts");
179 let cc = if let Some(s) = sources {
180 crate::completion::build_completion_cache(s, contracts)
181 } else {
182 crate::completion::build_completion_cache(
183 &serde_json::Value::Object(Default::default()),
184 contracts,
185 )
186 };
187 std::sync::Arc::new(cc)
188 };
189
190 Self {
194 nodes,
195 path_to_abs,
196 external_refs,
197 id_to_path_map,
198 decl_index,
199 node_id_to_source_path,
200 gas_index,
201 hint_index,
202 doc_index,
203 completion_cache,
204 build_version,
205 }
206 }
207
208 pub fn merge_missing_from(&mut self, other: &CachedBuild) {
215 for (abs_path, file_nodes) in &other.nodes {
216 if !self.nodes.contains_key(abs_path) {
217 self.nodes.insert(abs_path.clone(), file_nodes.clone());
218 }
219 }
220 for (k, v) in &other.path_to_abs {
221 self.path_to_abs
222 .entry(k.clone())
223 .or_insert_with(|| v.clone());
224 }
225 for (k, v) in &other.external_refs {
226 self.external_refs.entry(k.clone()).or_insert(*v);
227 }
228 for (k, v) in &other.id_to_path_map {
229 self.id_to_path_map
230 .entry(k.clone())
231 .or_insert_with(|| v.clone());
232 }
233 }
234
235 pub fn from_reference_index(
240 nodes: HashMap<String, HashMap<NodeId, NodeInfo>>,
241 path_to_abs: HashMap<String, String>,
242 external_refs: ExternalRefs,
243 id_to_path_map: HashMap<String, String>,
244 build_version: i32,
245 ) -> Self {
246 let completion_cache = std::sync::Arc::new(crate::completion::build_completion_cache(
247 &serde_json::Value::Object(Default::default()),
248 None,
249 ));
250
251 Self {
252 nodes,
253 path_to_abs,
254 external_refs,
255 id_to_path_map,
256 decl_index: HashMap::new(),
257 node_id_to_source_path: HashMap::new(),
258 gas_index: HashMap::new(),
259 hint_index: HashMap::new(),
260 doc_index: HashMap::new(),
261 completion_cache,
262 build_version,
263 }
264 }
265}
266
267type CachedIds = (
269 HashMap<String, HashMap<NodeId, NodeInfo>>,
270 HashMap<String, String>,
271 ExternalRefs,
272);
273
274pub fn cache_ids(sources: &Value) -> CachedIds {
275 let source_count = sources.as_object().map_or(0, |obj| obj.len());
276
277 let mut nodes: HashMap<String, HashMap<NodeId, NodeInfo>> =
280 HashMap::with_capacity(source_count);
281 let mut path_to_abs: HashMap<String, String> = HashMap::with_capacity(source_count);
282 let mut external_refs: ExternalRefs = HashMap::with_capacity(source_count * 10);
283
284 if let Some(sources_obj) = sources.as_object() {
285 for (path, source_data) in sources_obj {
286 if let Some(ast) = source_data.get("ast") {
287 let abs_path = ast
289 .get("absolutePath")
290 .and_then(|v| v.as_str())
291 .unwrap_or(path)
292 .to_string();
293
294 path_to_abs.insert(path.clone(), abs_path.clone());
295
296 let size_hint = ast
301 .get("nodes")
302 .and_then(|v| v.as_array())
303 .map_or(64, |arr| arr.len() * 8);
304 if !nodes.contains_key(&abs_path) {
305 nodes.insert(abs_path.clone(), HashMap::with_capacity(size_hint));
306 }
307
308 if let Some(id) = ast.get("id").and_then(|v| v.as_u64())
309 && let Some(src) = ast.get("src").and_then(|v| v.as_str())
310 {
311 nodes.get_mut(&abs_path).unwrap().insert(
312 NodeId(id),
313 NodeInfo {
314 src: src.to_string(),
315 name_location: None,
316 name_locations: vec![],
317 referenced_declaration: None,
318 node_type: ast
319 .get("nodeType")
320 .and_then(|v| v.as_str())
321 .map(|s| s.to_string()),
322 member_location: None,
323 absolute_path: ast
324 .get("absolutePath")
325 .and_then(|v| v.as_str())
326 .map(|s| s.to_string()),
327 },
328 );
329 }
330
331 let mut stack = vec![ast];
332
333 while let Some(tree) = stack.pop() {
334 if let Some(raw_id) = tree.get("id").and_then(|v| v.as_u64())
335 && let Some(src) = tree.get("src").and_then(|v| v.as_str())
336 {
337 let id = NodeId(raw_id);
338 let mut name_location = tree
340 .get("nameLocation")
341 .and_then(|v| v.as_str())
342 .map(|s| s.to_string());
343
344 if name_location.is_none()
348 && let Some(name_locations) = tree.get("nameLocations")
349 && let Some(locations_array) = name_locations.as_array()
350 && !locations_array.is_empty()
351 {
352 let node_type = tree.get("nodeType").and_then(|v| v.as_str());
353 if node_type == Some("IdentifierPath") {
354 name_location = locations_array
355 .last()
356 .and_then(|v| v.as_str())
357 .map(|s| s.to_string());
358 } else {
359 name_location = locations_array[0].as_str().map(|s| s.to_string());
360 }
361 }
362
363 let name_locations = if let Some(name_locations) = tree.get("nameLocations")
364 && let Some(locations_array) = name_locations.as_array()
365 {
366 locations_array
367 .iter()
368 .filter_map(|v| v.as_str().map(|s| s.to_string()))
369 .collect()
370 } else {
371 vec![]
372 };
373
374 let mut final_name_location = name_location;
375 if final_name_location.is_none()
376 && let Some(member_loc) =
377 tree.get("memberLocation").and_then(|v| v.as_str())
378 {
379 final_name_location = Some(member_loc.to_string());
380 }
381
382 let node_info = NodeInfo {
383 src: src.to_string(),
384 name_location: final_name_location,
385 name_locations,
386 referenced_declaration: tree
387 .get("referencedDeclaration")
388 .and_then(|v| v.as_u64())
389 .map(NodeId),
390 node_type: tree
391 .get("nodeType")
392 .and_then(|v| v.as_str())
393 .map(|s| s.to_string()),
394 member_location: tree
395 .get("memberLocation")
396 .and_then(|v| v.as_str())
397 .map(|s| s.to_string()),
398 absolute_path: tree
399 .get("absolutePath")
400 .and_then(|v| v.as_str())
401 .map(|s| s.to_string()),
402 };
403
404 nodes.get_mut(&abs_path).unwrap().insert(id, node_info);
405
406 if tree.get("nodeType").and_then(|v| v.as_str()) == Some("InlineAssembly")
408 && let Some(ext_refs) =
409 tree.get("externalReferences").and_then(|v| v.as_array())
410 {
411 for ext_ref in ext_refs {
412 if let Some(src_str) = ext_ref.get("src").and_then(|v| v.as_str())
413 && let Some(decl_id) =
414 ext_ref.get("declaration").and_then(|v| v.as_u64())
415 {
416 external_refs.insert(src_str.to_string(), NodeId(decl_id));
417 }
418 }
419 }
420 }
421
422 for key in CHILD_KEYS {
423 push_if_node_or_array(tree, key, &mut stack);
424 }
425 }
426 }
427 }
428 }
429
430 (nodes, path_to_abs, external_refs)
431}
432
433pub fn pos_to_bytes(source_bytes: &[u8], position: Position) -> usize {
434 let text = String::from_utf8_lossy(source_bytes);
435 crate::utils::position_to_byte_offset(&text, position)
436}
437
438pub fn bytes_to_pos(source_bytes: &[u8], byte_offset: usize) -> Option<Position> {
439 let text = String::from_utf8_lossy(source_bytes);
440 let pos = crate::utils::byte_offset_to_position(&text, byte_offset);
441 Some(pos)
442}
443
444pub fn src_to_location(src: &str, id_to_path: &HashMap<String, String>) -> Option<Location> {
446 let loc = SourceLoc::parse(src)?;
447 let file_path = id_to_path.get(&loc.file_id_str())?;
448
449 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
450 std::path::PathBuf::from(file_path)
451 } else {
452 std::env::current_dir().ok()?.join(file_path)
453 };
454
455 let source_bytes = std::fs::read(&absolute_path).ok()?;
456 let start_pos = bytes_to_pos(&source_bytes, loc.offset)?;
457 let end_pos = bytes_to_pos(&source_bytes, loc.end())?;
458 let uri = Url::from_file_path(&absolute_path).ok()?;
459
460 Some(Location {
461 uri,
462 range: Range {
463 start: start_pos,
464 end: end_pos,
465 },
466 })
467}
468
469pub fn goto_bytes(
470 nodes: &HashMap<String, HashMap<NodeId, NodeInfo>>,
471 path_to_abs: &HashMap<String, String>,
472 id_to_path: &HashMap<String, String>,
473 external_refs: &ExternalRefs,
474 uri: &str,
475 position: usize,
476) -> Option<(String, usize, usize)> {
477 let path = match uri.starts_with("file://") {
478 true => &uri[7..],
479 false => uri,
480 };
481
482 let abs_path = path_to_abs.get(path)?;
484
485 let current_file_nodes = nodes.get(abs_path)?;
487
488 let path_to_file_id: HashMap<&str, &str> = id_to_path
490 .iter()
491 .map(|(id, p)| (p.as_str(), id.as_str()))
492 .collect();
493
494 let current_file_id = path_to_file_id.get(abs_path.as_str());
498
499 for (src_str, decl_id) in external_refs {
501 let Some(src_loc) = SourceLoc::parse(src_str) else {
502 continue;
503 };
504
505 if let Some(file_id) = current_file_id {
507 if src_loc.file_id_str() != *file_id {
508 continue;
509 }
510 } else {
511 continue;
512 }
513
514 if src_loc.offset <= position && position < src_loc.end() {
515 let mut target_node: Option<&NodeInfo> = None;
517 for file_nodes in nodes.values() {
518 if let Some(node) = file_nodes.get(decl_id) {
519 target_node = Some(node);
520 break;
521 }
522 }
523 let node = target_node?;
524 let loc_str = node.name_location.as_deref().unwrap_or(&node.src);
525 let loc = SourceLoc::parse(loc_str)?;
526 let file_path = id_to_path.get(&loc.file_id_str())?.clone();
527 return Some((file_path, loc.offset, loc.length));
528 }
529 }
530
531 let mut refs = HashMap::new();
532
533 for (id, content) in current_file_nodes {
535 if content.referenced_declaration.is_none() {
536 continue;
537 }
538
539 let Some(src_loc) = SourceLoc::parse(&content.src) else {
540 continue;
541 };
542
543 if src_loc.offset <= position && position < src_loc.end() {
544 let diff = src_loc.length;
545 if !refs.contains_key(&diff) || refs[&diff] <= *id {
546 refs.insert(diff, *id);
547 }
548 }
549 }
550
551 if refs.is_empty() {
552 let tmp = current_file_nodes.iter();
555 for (_id, content) in tmp {
556 if content.node_type == Some("ImportDirective".to_string()) {
557 let Some(src_loc) = SourceLoc::parse(&content.src) else {
558 continue;
559 };
560
561 if src_loc.offset <= position
562 && position < src_loc.end()
563 && let Some(import_path) = &content.absolute_path
564 {
565 return Some((import_path.clone(), 0, 0));
566 }
567 }
568 }
569 return None;
570 }
571
572 let min_diff = *refs.keys().min()?;
574 let chosen_id = refs[&min_diff];
575 let ref_id = current_file_nodes[&chosen_id].referenced_declaration?;
576
577 let mut target_node: Option<&NodeInfo> = None;
579 for file_nodes in nodes.values() {
580 if let Some(node) = file_nodes.get(&ref_id) {
581 target_node = Some(node);
582 break;
583 }
584 }
585
586 let node = target_node?;
587
588 let loc_str = node.name_location.as_deref().unwrap_or(&node.src);
590 let loc = SourceLoc::parse(loc_str)?;
591 let file_path = id_to_path.get(&loc.file_id_str())?.clone();
592
593 Some((file_path, loc.offset, loc.length))
594}
595
596pub fn goto_declaration_cached(
599 build: &CachedBuild,
600 file_uri: &Url,
601 position: Position,
602 source_bytes: &[u8],
603) -> Option<Location> {
604 let byte_position = pos_to_bytes(source_bytes, position);
605
606 if let Some((file_path, location_bytes, length)) = goto_bytes(
607 &build.nodes,
608 &build.path_to_abs,
609 &build.id_to_path_map,
610 &build.external_refs,
611 file_uri.as_ref(),
612 byte_position,
613 ) {
614 let target_file_path = std::path::Path::new(&file_path);
615 let absolute_path = if target_file_path.is_absolute() {
616 target_file_path.to_path_buf()
617 } else {
618 let base = file_uri
623 .to_file_path()
624 .ok()
625 .and_then(|p| p.parent().map(|d| d.to_path_buf()))
626 .or_else(|| std::env::current_dir().ok())
627 .unwrap_or_default();
628 base.join(target_file_path)
629 };
630
631 if let Ok(target_source_bytes) = std::fs::read(&absolute_path)
632 && let Some(start_pos) = bytes_to_pos(&target_source_bytes, location_bytes)
633 && let Some(end_pos) = bytes_to_pos(&target_source_bytes, location_bytes + length)
634 && let Ok(target_uri) = Url::from_file_path(&absolute_path)
635 {
636 return Some(Location {
637 uri: target_uri,
638 range: Range {
639 start: start_pos,
640 end: end_pos,
641 },
642 });
643 }
644 };
645
646 None
647}
648
649pub fn goto_declaration_by_name(
660 cached_build: &CachedBuild,
661 file_uri: &Url,
662 name: &str,
663 byte_hint: usize,
664) -> Option<Location> {
665 let path = match file_uri.as_ref().starts_with("file://") {
666 true => &file_uri.as_ref()[7..],
667 false => file_uri.as_ref(),
668 };
669 let abs_path = cached_build.path_to_abs.get(path)?;
670 let built_source = std::fs::read_to_string(abs_path).ok()?;
672
673 let mut candidates: Vec<(usize, usize, NodeId)> = Vec::new();
675
676 let tmp = {
677 let this = cached_build.nodes.get(abs_path)?;
678 this.iter()
679 };
680 for (_id, node) in tmp {
681 let ref_id = match node.referenced_declaration {
682 Some(id) => id,
683 None => continue,
684 };
685
686 let Some(src_loc) = SourceLoc::parse(&node.src) else {
688 continue;
689 };
690 let start = src_loc.offset;
691 let length = src_loc.length;
692
693 if start + length > built_source.len() {
694 continue;
695 }
696
697 let node_text = &built_source[start..start + length];
698
699 let matches = node_text == name
704 || node_text.contains(&format!(".{name}("))
705 || node_text.ends_with(&format!(".{name}"));
706
707 if matches {
708 let distance = if byte_hint >= start && byte_hint < start + length {
712 0 } else if byte_hint < start {
714 start - byte_hint
715 } else {
716 byte_hint - (start + length)
717 };
718 candidates.push((distance, length, ref_id));
719 }
720 }
721
722 candidates.sort_by_key(|&(dist, span, _)| (dist, span));
724 let ref_id = candidates.first()?.2;
725
726 let mut target_node: Option<&NodeInfo> = None;
728 for file_nodes in cached_build.nodes.values() {
729 if let Some(node) = file_nodes.get(&ref_id) {
730 target_node = Some(node);
731 break;
732 }
733 }
734
735 let node = target_node?;
736
737 let loc_str = node.name_location.as_deref().unwrap_or(&node.src);
739 let loc = SourceLoc::parse(loc_str)?;
740
741 let file_path = cached_build.id_to_path_map.get(&loc.file_id_str())?;
742 let location_bytes = loc.offset;
743 let length = loc.length;
744
745 let target_file_path = std::path::Path::new(file_path);
746 let absolute_path = if target_file_path.is_absolute() {
747 target_file_path.to_path_buf()
748 } else {
749 let base = file_uri
750 .to_file_path()
751 .ok()
752 .and_then(|p| p.parent().map(|d| d.to_path_buf()))
753 .or_else(|| std::env::current_dir().ok())
754 .unwrap_or_default();
755 base.join(target_file_path)
756 };
757
758 let target_source_bytes = std::fs::read(&absolute_path).ok()?;
759 let start_pos = bytes_to_pos(&target_source_bytes, location_bytes)?;
760 let end_pos = bytes_to_pos(&target_source_bytes, location_bytes + length)?;
761 let target_uri = Url::from_file_path(&absolute_path).ok()?;
762
763 Some(Location {
764 uri: target_uri,
765 range: Range {
766 start: start_pos,
767 end: end_pos,
768 },
769 })
770}
771
772#[derive(Debug, Clone)]
776pub struct CursorContext {
777 pub name: String,
779 pub function: Option<String>,
781 pub contract: Option<String>,
783 pub object: Option<String>,
787 pub arg_count: Option<usize>,
790 pub arg_types: Vec<Option<String>>,
793}
794
795fn ts_parse(source: &str) -> Option<tree_sitter::Tree> {
797 let mut parser = Parser::new();
798 parser
799 .set_language(&tree_sitter_solidity::LANGUAGE.into())
800 .expect("failed to load Solidity grammar");
801 parser.parse(source, None)
802}
803
804pub fn validate_goto_target(target_source: &str, location: &Location, expected_name: &str) -> bool {
810 let line = location.range.start.line as usize;
811 let start_col = location.range.start.character as usize;
812 let end_col = location.range.end.character as usize;
813
814 if let Some(line_text) = target_source.lines().nth(line)
815 && end_col <= line_text.len()
816 {
817 return &line_text[start_col..end_col] == expected_name;
818 }
819 true
821}
822
823fn ts_node_at_byte(node: Node, byte: usize) -> Option<Node> {
825 if byte < node.start_byte() || byte >= node.end_byte() {
826 return None;
827 }
828 let mut cursor = node.walk();
829 for child in node.children(&mut cursor) {
830 if child.start_byte() <= byte
831 && byte < child.end_byte()
832 && let Some(deeper) = ts_node_at_byte(child, byte)
833 {
834 return Some(deeper);
835 }
836 }
837 Some(node)
838}
839
840fn ts_child_id_text<'a>(node: Node<'a>, source: &'a str) -> Option<&'a str> {
842 let mut cursor = node.walk();
843 node.children(&mut cursor)
844 .find(|c| c.kind() == "identifier" && c.is_named())
845 .map(|c| &source[c.byte_range()])
846}
847
848fn infer_argument_type<'a>(arg_node: Node<'a>, source: &'a str) -> Option<String> {
854 let expr = if arg_node.kind() == "call_argument" {
856 let mut c = arg_node.walk();
857 arg_node.children(&mut c).find(|ch| ch.is_named())?
858 } else {
859 arg_node
860 };
861
862 match expr.kind() {
863 "identifier" => {
864 let var_name = &source[expr.byte_range()];
865 find_variable_type(expr, source, var_name)
867 }
868 "number_literal" | "decimal_number" | "hex_number" => Some("uint256".into()),
869 "boolean_literal" => Some("bool".into()),
870 "string_literal" | "hex_string_literal" => Some("string".into()),
871 _ => None,
872 }
873}
874
875fn find_variable_type(from: Node, source: &str, var_name: &str) -> Option<String> {
880 let mut scope = from.parent();
881 while let Some(node) = scope {
882 match node.kind() {
883 "function_definition" | "modifier_definition" | "constructor_definition" => {
884 let mut c = node.walk();
886 for child in node.children(&mut c) {
887 if child.kind() == "parameter"
888 && let Some(id) = ts_child_id_text(child, source)
889 && id == var_name
890 {
891 let mut pc = child.walk();
893 return child
894 .children(&mut pc)
895 .find(|c| {
896 matches!(
897 c.kind(),
898 "type_name"
899 | "primitive_type"
900 | "user_defined_type"
901 | "mapping"
902 )
903 })
904 .map(|t| source[t.byte_range()].trim().to_string());
905 }
906 }
907 }
908 "function_body" | "block_statement" | "unchecked_block" => {
909 let mut c = node.walk();
911 for child in node.children(&mut c) {
912 if (child.kind() == "variable_declaration_statement"
913 || child.kind() == "variable_declaration")
914 && let Some(id) = ts_child_id_text(child, source)
915 && id == var_name
916 {
917 let mut pc = child.walk();
918 return child
919 .children(&mut pc)
920 .find(|c| {
921 matches!(
922 c.kind(),
923 "type_name"
924 | "primitive_type"
925 | "user_defined_type"
926 | "mapping"
927 )
928 })
929 .map(|t| source[t.byte_range()].trim().to_string());
930 }
931 }
932 }
933 "contract_declaration" | "library_declaration" | "interface_declaration" => {
934 if let Some(body) = ts_find_child(node, "contract_body") {
936 let mut c = body.walk();
937 for child in body.children(&mut c) {
938 if child.kind() == "state_variable_declaration"
939 && let Some(id) = ts_child_id_text(child, source)
940 && id == var_name
941 {
942 let mut pc = child.walk();
943 return child
944 .children(&mut pc)
945 .find(|c| {
946 matches!(
947 c.kind(),
948 "type_name"
949 | "primitive_type"
950 | "user_defined_type"
951 | "mapping"
952 )
953 })
954 .map(|t| source[t.byte_range()].trim().to_string());
955 }
956 }
957 }
958 }
959 _ => {}
960 }
961 scope = node.parent();
962 }
963 None
964}
965
966fn infer_call_arg_types(call_node: Node, source: &str) -> Vec<Option<String>> {
968 let mut cursor = call_node.walk();
969 call_node
970 .children(&mut cursor)
971 .filter(|c| c.kind() == "call_argument")
972 .map(|arg| infer_argument_type(arg, source))
973 .collect()
974}
975
976fn best_overload<'a>(
984 decls: &'a [TsDeclaration],
985 arg_count: Option<usize>,
986 arg_types: &[Option<String>],
987) -> Option<&'a TsDeclaration> {
988 if decls.len() == 1 {
989 return decls.first();
990 }
991 if decls.is_empty() {
992 return None;
993 }
994
995 let func_decls: Vec<&TsDeclaration> =
997 decls.iter().filter(|d| d.param_count.is_some()).collect();
998
999 if func_decls.is_empty() {
1000 return decls.first();
1001 }
1002
1003 let count_matched: Vec<&&TsDeclaration> = if let Some(ac) = arg_count {
1005 let matched: Vec<_> = func_decls
1006 .iter()
1007 .filter(|d| d.param_count == Some(ac))
1008 .collect();
1009 if matched.len() == 1 {
1010 return Some(matched[0]);
1011 }
1012 if matched.is_empty() {
1013 func_decls.iter().collect()
1015 } else {
1016 matched
1017 }
1018 } else {
1019 func_decls.iter().collect()
1020 };
1021
1022 if !arg_types.is_empty() {
1024 let mut best: Option<(&TsDeclaration, usize)> = None;
1025 for &&decl in &count_matched {
1026 let score = arg_types
1027 .iter()
1028 .zip(decl.param_types.iter())
1029 .filter(|(arg_ty, param_ty)| {
1030 if let Some(at) = arg_ty {
1031 at == param_ty.as_str()
1032 } else {
1033 false
1034 }
1035 })
1036 .count();
1037 if best.is_none() || score > best.unwrap().1 {
1038 best = Some((decl, score));
1039 }
1040 }
1041 if let Some((decl, _)) = best {
1042 return Some(decl);
1043 }
1044 }
1045
1046 count_matched.first().map(|d| **d).or(decls.first())
1048}
1049
1050pub fn cursor_context(source: &str, position: Position) -> Option<CursorContext> {
1054 let tree = ts_parse(source)?;
1055 let byte = pos_to_bytes(source.as_bytes(), position);
1056 let leaf = ts_node_at_byte(tree.root_node(), byte)?;
1057
1058 let id_node = if leaf.kind() == "identifier" {
1060 leaf
1061 } else {
1062 let parent = leaf.parent()?;
1064 if parent.kind() == "identifier" {
1065 parent
1066 } else {
1067 return None;
1068 }
1069 };
1070
1071 let name = source[id_node.byte_range()].to_string();
1072 let mut function = None;
1073 let mut contract = None;
1074
1075 let object = id_node.parent().and_then(|parent| {
1079 if parent.kind() == "member_expression" {
1080 let prop = parent.child_by_field_name("property")?;
1081 if prop.id() == id_node.id() {
1083 let obj = parent.child_by_field_name("object")?;
1084 Some(source[obj.byte_range()].to_string())
1085 } else {
1086 None
1087 }
1088 } else {
1089 None
1090 }
1091 });
1092
1093 let (arg_count, arg_types) = {
1097 let mut node = id_node.parent();
1098 let mut result = (None, vec![]);
1099 while let Some(n) = node {
1100 if n.kind() == "call_expression" {
1101 let types = infer_call_arg_types(n, source);
1102 result = (Some(types.len()), types);
1103 break;
1104 }
1105 node = n.parent();
1106 }
1107 result
1108 };
1109
1110 let mut current = id_node.parent();
1112 while let Some(node) = current {
1113 match node.kind() {
1114 "function_definition" | "modifier_definition" if function.is_none() => {
1115 function = ts_child_id_text(node, source).map(String::from);
1116 }
1117 "constructor_definition" if function.is_none() => {
1118 function = Some("constructor".into());
1119 }
1120 "contract_declaration" | "interface_declaration" | "library_declaration"
1121 if contract.is_none() =>
1122 {
1123 contract = ts_child_id_text(node, source).map(String::from);
1124 }
1125 _ => {}
1126 }
1127 current = node.parent();
1128 }
1129
1130 Some(CursorContext {
1131 name,
1132 function,
1133 contract,
1134 object,
1135 arg_count,
1136 arg_types,
1137 })
1138}
1139
1140#[derive(Debug, Clone)]
1142pub struct TsDeclaration {
1143 pub range: Range,
1145 pub kind: &'static str,
1147 pub container: Option<String>,
1149 pub param_count: Option<usize>,
1151 pub param_types: Vec<String>,
1154}
1155
1156pub fn find_declarations_by_name(source: &str, name: &str) -> Vec<TsDeclaration> {
1161 let tree = match ts_parse(source) {
1162 Some(t) => t,
1163 None => return vec![],
1164 };
1165 let mut results = Vec::new();
1166 collect_declarations(tree.root_node(), source, name, None, &mut results);
1167 results
1168}
1169
1170fn collect_declarations(
1171 node: Node,
1172 source: &str,
1173 name: &str,
1174 container: Option<&str>,
1175 out: &mut Vec<TsDeclaration>,
1176) {
1177 let mut cursor = node.walk();
1178 for child in node.children(&mut cursor) {
1179 if !child.is_named() {
1180 continue;
1181 }
1182 match child.kind() {
1183 "contract_declaration" | "interface_declaration" | "library_declaration" => {
1184 if let Some(id_name) = ts_child_id_text(child, source) {
1185 if id_name == name {
1186 out.push(TsDeclaration {
1187 range: id_range(child),
1188 kind: child.kind(),
1189 container: container.map(String::from),
1190 param_count: None,
1191 param_types: vec![],
1192 });
1193 }
1194 if let Some(body) = ts_find_child(child, "contract_body") {
1196 collect_declarations(body, source, name, Some(id_name), out);
1197 }
1198 }
1199 }
1200 "function_definition" | "modifier_definition" => {
1201 if let Some(id_name) = ts_child_id_text(child, source) {
1202 if id_name == name {
1203 let types = parameter_type_signature(child, source);
1204 out.push(TsDeclaration {
1205 range: id_range(child),
1206 kind: child.kind(),
1207 container: container.map(String::from),
1208 param_count: Some(types.len()),
1209 param_types: types.into_iter().map(String::from).collect(),
1210 });
1211 }
1212 collect_parameters(child, source, name, container, out);
1214 if let Some(body) = ts_find_child(child, "function_body") {
1216 collect_declarations(body, source, name, container, out);
1217 }
1218 }
1219 }
1220 "constructor_definition" => {
1221 if name == "constructor" {
1222 let types = parameter_type_signature(child, source);
1223 out.push(TsDeclaration {
1224 range: ts_range(child),
1225 kind: "constructor_definition",
1226 container: container.map(String::from),
1227 param_count: Some(types.len()),
1228 param_types: types.into_iter().map(String::from).collect(),
1229 });
1230 }
1231 collect_parameters(child, source, name, container, out);
1233 if let Some(body) = ts_find_child(child, "function_body") {
1234 collect_declarations(body, source, name, container, out);
1235 }
1236 }
1237 "state_variable_declaration" | "variable_declaration" => {
1238 if let Some(id_name) = ts_child_id_text(child, source)
1239 && id_name == name
1240 {
1241 out.push(TsDeclaration {
1242 range: id_range(child),
1243 kind: child.kind(),
1244 container: container.map(String::from),
1245 param_count: None,
1246 param_types: vec![],
1247 });
1248 }
1249 }
1250 "struct_declaration" => {
1251 if let Some(id_name) = ts_child_id_text(child, source) {
1252 if id_name == name {
1253 out.push(TsDeclaration {
1254 range: id_range(child),
1255 kind: "struct_declaration",
1256 container: container.map(String::from),
1257 param_count: None,
1258 param_types: vec![],
1259 });
1260 }
1261 if let Some(body) = ts_find_child(child, "struct_body") {
1262 collect_declarations(body, source, name, Some(id_name), out);
1263 }
1264 }
1265 }
1266 "enum_declaration" => {
1267 if let Some(id_name) = ts_child_id_text(child, source) {
1268 if id_name == name {
1269 out.push(TsDeclaration {
1270 range: id_range(child),
1271 kind: "enum_declaration",
1272 container: container.map(String::from),
1273 param_count: None,
1274 param_types: vec![],
1275 });
1276 }
1277 if let Some(body) = ts_find_child(child, "enum_body") {
1279 let mut ecur = body.walk();
1280 for val in body.children(&mut ecur) {
1281 if val.kind() == "enum_value" && &source[val.byte_range()] == name {
1282 out.push(TsDeclaration {
1283 range: ts_range(val),
1284 kind: "enum_value",
1285 container: Some(id_name.to_string()),
1286 param_count: None,
1287 param_types: vec![],
1288 });
1289 }
1290 }
1291 }
1292 }
1293 }
1294 "event_definition" | "error_declaration" => {
1295 if let Some(id_name) = ts_child_id_text(child, source)
1296 && id_name == name
1297 {
1298 out.push(TsDeclaration {
1299 range: id_range(child),
1300 kind: child.kind(),
1301 container: container.map(String::from),
1302 param_count: None,
1303 param_types: vec![],
1304 });
1305 }
1306 }
1307 "user_defined_type_definition" => {
1308 if let Some(id_name) = ts_child_id_text(child, source)
1309 && id_name == name
1310 {
1311 out.push(TsDeclaration {
1312 range: id_range(child),
1313 kind: "user_defined_type_definition",
1314 container: container.map(String::from),
1315 param_count: None,
1316 param_types: vec![],
1317 });
1318 }
1319 }
1320 _ => {
1322 collect_declarations(child, source, name, container, out);
1323 }
1324 }
1325 }
1326}
1327
1328fn parameter_type_signature<'a>(node: Node<'a>, source: &'a str) -> Vec<&'a str> {
1334 let mut cursor = node.walk();
1335 node.children(&mut cursor)
1336 .filter(|c| c.kind() == "parameter")
1337 .filter_map(|param| {
1338 let mut pc = param.walk();
1339 param
1340 .children(&mut pc)
1341 .find(|c| {
1342 matches!(
1343 c.kind(),
1344 "type_name" | "primitive_type" | "user_defined_type" | "mapping"
1345 )
1346 })
1347 .map(|t| source[t.byte_range()].trim())
1348 })
1349 .collect()
1350}
1351
1352fn collect_parameters(
1354 node: Node,
1355 source: &str,
1356 name: &str,
1357 container: Option<&str>,
1358 out: &mut Vec<TsDeclaration>,
1359) {
1360 let mut cursor = node.walk();
1361 for child in node.children(&mut cursor) {
1362 if child.kind() == "parameter"
1363 && let Some(id_name) = ts_child_id_text(child, source)
1364 && id_name == name
1365 {
1366 out.push(TsDeclaration {
1367 range: id_range(child),
1368 kind: "parameter",
1369 container: container.map(String::from),
1370 param_count: None,
1371 param_types: vec![],
1372 });
1373 }
1374 }
1375}
1376
1377fn ts_range(node: Node) -> Range {
1379 let s = node.start_position();
1380 let e = node.end_position();
1381 Range {
1382 start: Position::new(s.row as u32, s.column as u32),
1383 end: Position::new(e.row as u32, e.column as u32),
1384 }
1385}
1386
1387fn id_range(node: Node) -> Range {
1389 let mut cursor = node.walk();
1390 node.children(&mut cursor)
1391 .find(|c| c.kind() == "identifier" && c.is_named())
1392 .map(|c| ts_range(c))
1393 .unwrap_or_else(|| ts_range(node))
1394}
1395
1396fn ts_find_child<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
1397 let mut cursor = node.walk();
1398 node.children(&mut cursor).find(|c| c.kind() == kind)
1399}
1400
1401pub fn goto_definition_ts(
1409 source: &str,
1410 position: Position,
1411 file_uri: &Url,
1412 completion_cache: &crate::completion::CompletionCache,
1413 text_cache: &HashMap<String, (i32, String)>,
1414) -> Option<Location> {
1415 let ctx = cursor_context(source, position)?;
1416
1417 if let Some(obj_name) = &ctx.object {
1422 if let Some(path) = find_file_for_contract(completion_cache, obj_name, file_uri) {
1423 let target_source = read_target_source(&path, text_cache)?;
1424 let target_uri = Url::from_file_path(&path).ok()?;
1425 let decls = find_declarations_by_name(&target_source, &ctx.name);
1426 if let Some(d) = best_overload(&decls, ctx.arg_count, &ctx.arg_types) {
1427 return Some(Location {
1428 uri: target_uri,
1429 range: d.range,
1430 });
1431 }
1432 }
1433 let decls = find_declarations_by_name(source, &ctx.name);
1435 if let Some(d) = best_overload(&decls, ctx.arg_count, &ctx.arg_types) {
1436 return Some(Location {
1437 uri: file_uri.clone(),
1438 range: d.range,
1439 });
1440 }
1441 }
1442
1443 let resolved = resolve_via_cache(&ctx, file_uri, completion_cache);
1446
1447 match resolved {
1448 Some(ResolvedTarget::SameFile) => {
1449 find_best_declaration(source, &ctx, file_uri)
1451 }
1452 Some(ResolvedTarget::OtherFile { path, name }) => {
1453 let target_source = read_target_source(&path, text_cache);
1455 let target_source = target_source?;
1456 let target_uri = Url::from_file_path(&path).ok()?;
1457 let decls = find_declarations_by_name(&target_source, &name);
1458 decls.first().map(|d| Location {
1459 uri: target_uri,
1460 range: d.range,
1461 })
1462 }
1463 None => {
1464 find_best_declaration(source, &ctx, file_uri)
1466 }
1467 }
1468}
1469
1470#[derive(Debug)]
1471enum ResolvedTarget {
1472 SameFile,
1474 OtherFile { path: String, name: String },
1476}
1477
1478fn resolve_via_cache(
1484 ctx: &CursorContext,
1485 file_uri: &Url,
1486 cache: &crate::completion::CompletionCache,
1487) -> Option<ResolvedTarget> {
1488 let contract_scope = ctx
1490 .contract
1491 .as_ref()
1492 .and_then(|name| cache.name_to_node_id.get(name.as_str()))
1493 .copied();
1494
1495 if let Some(contract_id) = contract_scope {
1497 if let Some(func_name) = &ctx.function {
1499 if let Some(func_scope_id) = find_function_scope(cache, contract_id, func_name) {
1502 if let Some(decls) = cache.scope_declarations.get(&func_scope_id)
1504 && decls.iter().any(|d| d.name == ctx.name)
1505 {
1506 return Some(ResolvedTarget::SameFile);
1507 }
1508 }
1509 }
1510
1511 if let Some(decls) = cache.scope_declarations.get(&contract_id)
1513 && decls.iter().any(|d| d.name == ctx.name)
1514 {
1515 return Some(ResolvedTarget::SameFile);
1516 }
1517
1518 if let Some(bases) = cache.linearized_base_contracts.get(&contract_id) {
1520 for &base_id in bases.iter().skip(1) {
1521 if let Some(decls) = cache.scope_declarations.get(&base_id)
1522 && decls.iter().any(|d| d.name == ctx.name)
1523 {
1524 let base_name = cache
1527 .name_to_node_id
1528 .iter()
1529 .find(|&(_, &id)| id == base_id)
1530 .map(|(name, _)| name.clone());
1531
1532 if let Some(base_name) = base_name
1533 && let Some(path) = find_file_for_contract(cache, &base_name, file_uri)
1534 {
1535 return Some(ResolvedTarget::OtherFile {
1536 path,
1537 name: ctx.name.clone(),
1538 });
1539 }
1540 return Some(ResolvedTarget::SameFile);
1542 }
1543 }
1544 }
1545 }
1546
1547 if cache.name_to_node_id.contains_key(&ctx.name) {
1549 if let Some(path) = find_file_for_contract(cache, &ctx.name, file_uri) {
1551 let current_path = file_uri.to_file_path().ok()?;
1552 let current_str = current_path.to_str()?;
1553 if path == current_str || path.ends_with(current_str) || current_str.ends_with(&path) {
1554 return Some(ResolvedTarget::SameFile);
1555 }
1556 return Some(ResolvedTarget::OtherFile {
1557 path,
1558 name: ctx.name.clone(),
1559 });
1560 }
1561 return Some(ResolvedTarget::SameFile);
1562 }
1563
1564 if cache.name_to_type.contains_key(&ctx.name) {
1566 return Some(ResolvedTarget::SameFile);
1567 }
1568
1569 None
1570}
1571
1572fn find_function_scope(
1574 cache: &crate::completion::CompletionCache,
1575 contract_id: NodeId,
1576 func_name: &str,
1577) -> Option<NodeId> {
1578 for (&scope_id, &parent_id) in &cache.scope_parent {
1582 if parent_id == contract_id {
1583 if let Some(contract_decls) = cache.scope_declarations.get(&contract_id)
1587 && contract_decls.iter().any(|d| d.name == func_name)
1588 {
1589 if cache.scope_declarations.contains_key(&scope_id)
1593 || cache.scope_parent.values().any(|&p| p == scope_id)
1594 {
1595 return Some(scope_id);
1596 }
1597 }
1598 }
1599 }
1600 None
1601}
1602
1603fn find_file_for_contract(
1605 cache: &crate::completion::CompletionCache,
1606 contract_name: &str,
1607 _file_uri: &Url,
1608) -> Option<String> {
1609 let node_id = cache.name_to_node_id.get(contract_name)?;
1614 let scope_range = cache.scope_ranges.iter().find(|r| r.node_id == *node_id)?;
1615 let file_id = scope_range.file_id;
1616
1617 cache
1619 .path_to_file_id
1620 .iter()
1621 .find(|&(_, &fid)| fid == file_id)
1622 .map(|(path, _)| path.clone())
1623}
1624
1625fn read_target_source(path: &str, text_cache: &HashMap<String, (i32, String)>) -> Option<String> {
1627 let uri = Url::from_file_path(path).ok()?;
1629 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
1630 return Some(content.clone());
1631 }
1632 std::fs::read_to_string(path).ok()
1634}
1635
1636fn find_best_declaration(source: &str, ctx: &CursorContext, file_uri: &Url) -> Option<Location> {
1638 let decls = find_declarations_by_name(source, &ctx.name);
1639 if decls.is_empty() {
1640 return None;
1641 }
1642
1643 if decls.len() == 1 {
1645 return Some(Location {
1646 uri: file_uri.clone(),
1647 range: decls[0].range,
1648 });
1649 }
1650
1651 if let Some(contract_name) = &ctx.contract
1653 && let Some(d) = decls
1654 .iter()
1655 .find(|d| d.container.as_deref() == Some(contract_name))
1656 {
1657 return Some(Location {
1658 uri: file_uri.clone(),
1659 range: d.range,
1660 });
1661 }
1662
1663 Some(Location {
1665 uri: file_uri.clone(),
1666 range: decls[0].range,
1667 })
1668}
1669
1670#[cfg(test)]
1671mod ts_tests {
1672 use super::*;
1673
1674 #[test]
1675 fn test_cursor_context_state_var() {
1676 let source = r#"
1677contract Token {
1678 uint256 public totalSupply;
1679 function mint(uint256 amount) public {
1680 totalSupply += amount;
1681 }
1682}
1683"#;
1684 let ctx = cursor_context(source, Position::new(4, 8)).unwrap();
1686 assert_eq!(ctx.name, "totalSupply");
1687 assert_eq!(ctx.function.as_deref(), Some("mint"));
1688 assert_eq!(ctx.contract.as_deref(), Some("Token"));
1689 }
1690
1691 #[test]
1692 fn test_cursor_context_top_level() {
1693 let source = r#"
1694contract Foo {}
1695contract Bar {}
1696"#;
1697 let ctx = cursor_context(source, Position::new(1, 9)).unwrap();
1699 assert_eq!(ctx.name, "Foo");
1700 assert!(ctx.function.is_none());
1701 assert_eq!(ctx.contract.as_deref(), Some("Foo"));
1703 }
1704
1705 #[test]
1706 fn test_find_declarations() {
1707 let source = r#"
1708contract Token {
1709 uint256 public totalSupply;
1710 function mint(uint256 amount) public {
1711 totalSupply += amount;
1712 }
1713}
1714"#;
1715 let decls = find_declarations_by_name(source, "totalSupply");
1716 assert_eq!(decls.len(), 1);
1717 assert_eq!(decls[0].kind, "state_variable_declaration");
1718 assert_eq!(decls[0].container.as_deref(), Some("Token"));
1719 }
1720
1721 #[test]
1722 fn test_find_declarations_multiple_contracts() {
1723 let source = r#"
1724contract A {
1725 uint256 public value;
1726}
1727contract B {
1728 uint256 public value;
1729}
1730"#;
1731 let decls = find_declarations_by_name(source, "value");
1732 assert_eq!(decls.len(), 2);
1733 assert_eq!(decls[0].container.as_deref(), Some("A"));
1734 assert_eq!(decls[1].container.as_deref(), Some("B"));
1735 }
1736
1737 #[test]
1738 fn test_find_declarations_enum_value() {
1739 let source = "contract Foo { enum Status { Active, Paused } }";
1740 let decls = find_declarations_by_name(source, "Active");
1741 assert_eq!(decls.len(), 1);
1742 assert_eq!(decls[0].kind, "enum_value");
1743 assert_eq!(decls[0].container.as_deref(), Some("Status"));
1744 }
1745
1746 #[test]
1747 fn test_cursor_context_short_param() {
1748 let source = r#"
1749contract Shop {
1750 uint256 public TAX;
1751 constructor(uint256 price, uint16 tax, uint16 taxBase) {
1752 TAX = tax;
1753 }
1754}
1755"#;
1756 let ctx = cursor_context(source, Position::new(4, 14)).unwrap();
1758 assert_eq!(ctx.name, "tax");
1759 assert_eq!(ctx.contract.as_deref(), Some("Shop"));
1760
1761 let ctx2 = cursor_context(source, Position::new(4, 8)).unwrap();
1763 assert_eq!(ctx2.name, "TAX");
1764
1765 let decls = find_declarations_by_name(source, "tax");
1767 assert_eq!(decls.len(), 1);
1768 assert_eq!(decls[0].kind, "parameter");
1769
1770 let decls_tax_base = find_declarations_by_name(source, "taxBase");
1771 assert_eq!(decls_tax_base.len(), 1);
1772 assert_eq!(decls_tax_base[0].kind, "parameter");
1773
1774 let decls_price = find_declarations_by_name(source, "price");
1775 assert_eq!(decls_price.len(), 1);
1776 assert_eq!(decls_price[0].kind, "parameter");
1777
1778 let decls_tax_upper = find_declarations_by_name(source, "TAX");
1780 assert_eq!(decls_tax_upper.len(), 1);
1781 assert_eq!(decls_tax_upper[0].kind, "state_variable_declaration");
1782 }
1783
1784 #[test]
1785 fn test_find_best_declaration_same_contract() {
1786 let source = r#"
1787contract A { uint256 public x; }
1788contract B { uint256 public x; }
1789"#;
1790 let ctx = CursorContext {
1791 name: "x".into(),
1792 function: None,
1793 contract: Some("B".into()),
1794 object: None,
1795 arg_count: None,
1796 arg_types: vec![],
1797 };
1798 let uri = Url::parse("file:///test.sol").unwrap();
1799 let loc = find_best_declaration(source, &ctx, &uri).unwrap();
1800 assert_eq!(loc.range.start.line, 2);
1802 }
1803}