solidity_language_server/
references.rs1use serde_json::Value;
2use std::collections::{HashMap, HashSet};
3use tower_lsp::lsp_types::{Location, Position, Range, Url};
4
5use crate::goto::{NodeInfo, bytes_to_pos, cache_ids, pos_to_bytes};
6
7pub fn all_references(nodes: &HashMap<String, HashMap<u64, NodeInfo>>) -> HashMap<u64, Vec<u64>> {
8 let mut all_refs: HashMap<u64, Vec<u64>> = HashMap::new();
9 for file_nodes in nodes.values() {
10 for (id, node_info) in file_nodes {
11 if let Some(ref_id) = node_info.referenced_declaration {
12 all_refs.entry(ref_id).or_default().push(*id);
13 all_refs.entry(*id).or_default().push(ref_id);
14 }
15 }
16 }
17 all_refs
18}
19
20pub fn byte_to_id(
21 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
22 abs_path: &str,
23 byte_position: usize,
24) -> Option<u64> {
25 let file_nodes = nodes.get(abs_path)?;
26 let mut refs: HashMap<usize, u64> = HashMap::new();
27 for (id, node_info) in file_nodes {
28 let src_parts: Vec<&str> = node_info.src.split(':').collect();
29 if src_parts.len() != 3 {
30 continue;
31 }
32 let start: usize = src_parts[0].parse().ok()?;
33 let length: usize = src_parts[1].parse().ok()?;
34 let end = start + length;
35
36 if start <= byte_position && byte_position < end {
37 let diff = end - start;
38 refs.entry(diff).or_insert(*id);
39 }
40 }
41 refs.keys().min().map(|min_diff| refs[min_diff])
42}
43
44pub fn id_to_location(
45 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
46 id_to_path: &HashMap<String, String>,
47 node_id: u64,
48) -> Option<Location> {
49 id_to_location_with_index(nodes, id_to_path, node_id, None)
50}
51
52pub fn id_to_location_with_index(
53 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
54 id_to_path: &HashMap<String, String>,
55 node_id: u64,
56 name_location_index: Option<usize>,
57) -> Option<Location> {
58 let mut target_node: Option<&NodeInfo> = None;
59 for file_nodes in nodes.values() {
60 if let Some(node) = file_nodes.get(&node_id) {
61 target_node = Some(node);
62 break;
63 }
64 }
65 let node = target_node?;
66
67 let (byte_str, length_str, file_id) = if let Some(index) = name_location_index {
68 if let Some(name_loc) = node.name_locations.get(index) {
69 let parts: Vec<&str> = name_loc.split(':').collect();
70 if parts.len() == 3 {
71 (parts[0], parts[1], parts[2])
72 } else {
73 return None;
74 }
75 } else {
76 return None;
77 }
78 } else if let Some(name_location) = &node.name_location {
79 let parts: Vec<&str> = name_location.split(':').collect();
80 if parts.len() == 3 {
81 (parts[0], parts[1], parts[2])
82 } else {
83 return None;
84 }
85 } else {
86 let parts: Vec<&str> = node.src.split(':').collect();
88 if parts.len() == 3 {
89 (parts[0], parts[1], parts[2])
90 } else {
91 return None;
92 }
93 };
94
95 let byte_offset: usize = byte_str.parse().ok()?;
96 let length: usize = length_str.parse().ok()?;
97 let file_path = id_to_path.get(file_id)?;
98
99 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
100 std::path::PathBuf::from(file_path)
101 } else {
102 std::env::current_dir().ok()?.join(file_path)
103 };
104 let source_bytes = std::fs::read(&absolute_path).ok()?;
105 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
106 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
107 let uri = Url::from_file_path(&absolute_path).ok()?;
108
109 Some(Location {
110 uri,
111 range: Range {
112 start: start_pos,
113 end: end_pos,
114 },
115 })
116}
117
118pub fn goto_references(
119 ast_data: &Value,
120 file_uri: &Url,
121 position: Position,
122 source_bytes: &[u8],
123) -> Vec<Location> {
124 goto_references_with_index(ast_data, file_uri, position, source_bytes, None)
125}
126
127pub fn goto_references_with_index(
128 ast_data: &Value,
129 file_uri: &Url,
130 position: Position,
131 source_bytes: &[u8],
132 name_location_index: Option<usize>,
133) -> Vec<Location> {
134 let sources = match ast_data.get("sources") {
135 Some(s) => s,
136 None => return vec![],
137 };
138 let build_infos = match ast_data.get("build_infos").and_then(|v| v.as_array()) {
139 Some(infos) => infos,
140 None => return vec![],
141 };
142 let first_build_info = match build_infos.first() {
143 Some(info) => info,
144 None => return vec![],
145 };
146 let id_to_path = match first_build_info
147 .get("source_id_to_path")
148 .and_then(|v| v.as_object())
149 {
150 Some(map) => map,
151 None => return vec![],
152 };
153 let id_to_path_map: HashMap<String, String> = id_to_path
154 .iter()
155 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
156 .collect();
157
158 let (nodes, path_to_abs) = cache_ids(sources);
159 let all_refs = all_references(&nodes);
160 let path = match file_uri.to_file_path() {
161 Ok(p) => p,
162 Err(_) => return vec![],
163 };
164 let path_str = match path.to_str() {
165 Some(s) => s,
166 None => return vec![],
167 };
168 let abs_path = match path_to_abs.get(path_str) {
169 Some(ap) => ap,
170 None => return vec![],
171 };
172 let byte_position = pos_to_bytes(source_bytes, position);
173 let node_id = match byte_to_id(&nodes, abs_path, byte_position) {
174 Some(id) => id,
175 None => return vec![],
176 };
177 let target_node_id = {
178 let file_nodes = match nodes.get(abs_path) {
179 Some(nodes) => nodes,
180 None => return vec![],
181 };
182 if let Some(node_info) = file_nodes.get(&node_id) {
183 node_info.referenced_declaration.unwrap_or(node_id)
184 } else {
185 node_id
186 }
187 };
188
189 let mut results = HashSet::new();
190 results.insert(target_node_id);
191 if let Some(refs) = all_refs.get(&target_node_id) {
192 results.extend(refs.iter().copied());
193 }
194 let mut locations = Vec::new();
195 for id in results {
196 if let Some(location) =
197 id_to_location_with_index(&nodes, &id_to_path_map, id, name_location_index)
198 {
199 locations.push(location);
200 }
201 }
202 let mut unique_locations = Vec::new();
203 let mut seen = std::collections::HashSet::new();
204 for location in locations {
205 let key = (
206 location.uri.clone(),
207 location.range.start.line,
208 location.range.start.character,
209 location.range.end.line,
210 location.range.end.character,
211 );
212 if seen.insert(key) {
213 unique_locations.push(location);
214 }
215 }
216 unique_locations
217}