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