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::{
6 bytes_to_pos, cache_ids, pos_to_bytes, src_to_location, CachedBuild, ExternalRefs, NodeInfo,
7};
8
9pub fn all_references(nodes: &HashMap<String, HashMap<u64, NodeInfo>>) -> HashMap<u64, Vec<u64>> {
10 let mut all_refs: HashMap<u64, Vec<u64>> = HashMap::new();
11 for file_nodes in nodes.values() {
12 for (id, node_info) in file_nodes {
13 if let Some(ref_id) = node_info.referenced_declaration {
14 all_refs.entry(ref_id).or_default().push(*id);
15 all_refs.entry(*id).or_default().push(ref_id);
16 }
17 }
18 }
19 all_refs
20}
21
22pub fn byte_to_decl_via_external_refs(
25 external_refs: &ExternalRefs,
26 id_to_path: &HashMap<String, String>,
27 abs_path: &str,
28 byte_position: usize,
29) -> Option<u64> {
30 let path_to_file_id: HashMap<&str, &str> = id_to_path
32 .iter()
33 .map(|(id, p)| (p.as_str(), id.as_str()))
34 .collect();
35 let current_file_id = path_to_file_id.get(abs_path)?;
36
37 for (src_str, decl_id) in external_refs {
38 let parts: Vec<&str> = src_str.split(':').collect();
39 if parts.len() != 3 {
40 continue;
41 }
42 if parts[2] != *current_file_id {
44 continue;
45 }
46 if let (Ok(start), Ok(length)) = (parts[0].parse::<usize>(), parts[1].parse::<usize>())
47 && start <= byte_position && byte_position < start + length {
48 return Some(*decl_id);
49 }
50 }
51 None
52}
53
54pub fn byte_to_id(
55 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
56 abs_path: &str,
57 byte_position: usize,
58) -> Option<u64> {
59 let file_nodes = nodes.get(abs_path)?;
60 let mut refs: HashMap<usize, u64> = HashMap::new();
61 for (id, node_info) in file_nodes {
62 let src_parts: Vec<&str> = node_info.src.split(':').collect();
63 if src_parts.len() != 3 {
64 continue;
65 }
66 let start: usize = src_parts[0].parse().ok()?;
67 let length: usize = src_parts[1].parse().ok()?;
68 let end = start + length;
69
70 if start <= byte_position && byte_position < end {
71 let diff = end - start;
72 refs.entry(diff).or_insert(*id);
73 }
74 }
75 refs.keys().min().map(|min_diff| refs[min_diff])
76}
77
78pub fn id_to_location(
79 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
80 id_to_path: &HashMap<String, String>,
81 node_id: u64,
82) -> Option<Location> {
83 id_to_location_with_index(nodes, id_to_path, node_id, None)
84}
85
86pub fn id_to_location_with_index(
87 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
88 id_to_path: &HashMap<String, String>,
89 node_id: u64,
90 name_location_index: Option<usize>,
91) -> Option<Location> {
92 let mut target_node: Option<&NodeInfo> = None;
93 for file_nodes in nodes.values() {
94 if let Some(node) = file_nodes.get(&node_id) {
95 target_node = Some(node);
96 break;
97 }
98 }
99 let node = target_node?;
100
101 let (byte_str, length_str, file_id) = if let Some(index) = name_location_index {
102 if let Some(name_loc) = node.name_locations.get(index) {
103 let parts: Vec<&str> = name_loc.split(':').collect();
104 if parts.len() == 3 {
105 (parts[0], parts[1], parts[2])
106 } else {
107 return None;
108 }
109 } else {
110 return None;
111 }
112 } else if let Some(name_location) = &node.name_location {
113 let parts: Vec<&str> = name_location.split(':').collect();
114 if parts.len() == 3 {
115 (parts[0], parts[1], parts[2])
116 } else {
117 return None;
118 }
119 } else {
120 let parts: Vec<&str> = node.src.split(':').collect();
122 if parts.len() == 3 {
123 (parts[0], parts[1], parts[2])
124 } else {
125 return None;
126 }
127 };
128
129 let byte_offset: usize = byte_str.parse().ok()?;
130 let length: usize = length_str.parse().ok()?;
131 let file_path = id_to_path.get(file_id)?;
132
133 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
134 std::path::PathBuf::from(file_path)
135 } else {
136 std::env::current_dir().ok()?.join(file_path)
137 };
138 let source_bytes = std::fs::read(&absolute_path).ok()?;
139 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
140 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
141 let uri = Url::from_file_path(&absolute_path).ok()?;
142
143 Some(Location {
144 uri,
145 range: Range {
146 start: start_pos,
147 end: end_pos,
148 },
149 })
150}
151
152pub fn goto_references(
153 ast_data: &Value,
154 file_uri: &Url,
155 position: Position,
156 source_bytes: &[u8],
157) -> Vec<Location> {
158 goto_references_with_index(ast_data, file_uri, position, source_bytes, None)
159}
160
161pub fn resolve_target_location(
166 build: &CachedBuild,
167 file_uri: &Url,
168 position: Position,
169 source_bytes: &[u8],
170) -> Option<(String, usize)> {
171 let path = file_uri.to_file_path().ok()?;
172 let path_str = path.to_str()?;
173 let abs_path = build.path_to_abs.get(path_str)?;
174 let byte_position = pos_to_bytes(source_bytes, position);
175
176 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
178 &build.external_refs,
179 &build.id_to_path_map,
180 abs_path,
181 byte_position,
182 ) {
183 decl_id
184 } else {
185 let node_id = byte_to_id(&build.nodes, abs_path, byte_position)?;
186 let file_nodes = build.nodes.get(abs_path)?;
187 if let Some(node_info) = file_nodes.get(&node_id) {
188 node_info.referenced_declaration.unwrap_or(node_id)
189 } else {
190 node_id
191 }
192 };
193
194 for (file_abs_path, file_nodes) in &build.nodes {
196 if let Some(node_info) = file_nodes.get(&target_node_id) {
197 let src_parts: Vec<&str> = node_info.src.split(':').collect();
198 if src_parts.len() == 3 {
199 let byte_offset: usize = src_parts[0].parse().ok()?;
200 return Some((file_abs_path.clone(), byte_offset));
201 }
202 }
203 }
204 None
205}
206
207pub fn goto_references_with_index(
208 ast_data: &Value,
209 file_uri: &Url,
210 position: Position,
211 source_bytes: &[u8],
212 name_location_index: Option<usize>,
213) -> Vec<Location> {
214 let sources = match ast_data.get("sources") {
215 Some(s) => s,
216 None => return vec![],
217 };
218 let build_infos = match ast_data.get("build_infos").and_then(|v| v.as_array()) {
219 Some(infos) => infos,
220 None => return vec![],
221 };
222 let first_build_info = match build_infos.first() {
223 Some(info) => info,
224 None => return vec![],
225 };
226 let id_to_path = match first_build_info
227 .get("source_id_to_path")
228 .and_then(|v| v.as_object())
229 {
230 Some(map) => map,
231 None => return vec![],
232 };
233 let id_to_path_map: HashMap<String, String> = id_to_path
234 .iter()
235 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
236 .collect();
237
238 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
239 let all_refs = all_references(&nodes);
240 let path = match file_uri.to_file_path() {
241 Ok(p) => p,
242 Err(_) => return vec![],
243 };
244 let path_str = match path.to_str() {
245 Some(s) => s,
246 None => return vec![],
247 };
248 let abs_path = match path_to_abs.get(path_str) {
249 Some(ap) => ap,
250 None => return vec![],
251 };
252 let byte_position = pos_to_bytes(source_bytes, position);
253
254 let target_node_id = if let Some(decl_id) =
256 byte_to_decl_via_external_refs(&external_refs, &id_to_path_map, abs_path, byte_position)
257 {
258 decl_id
259 } else {
260 let node_id = match byte_to_id(&nodes, abs_path, byte_position) {
261 Some(id) => id,
262 None => return vec![],
263 };
264 let file_nodes = match nodes.get(abs_path) {
265 Some(nodes) => nodes,
266 None => return vec![],
267 };
268 if let Some(node_info) = file_nodes.get(&node_id) {
269 node_info.referenced_declaration.unwrap_or(node_id)
270 } else {
271 node_id
272 }
273 };
274
275 let mut results = HashSet::new();
276 results.insert(target_node_id);
277 if let Some(refs) = all_refs.get(&target_node_id) {
278 results.extend(refs.iter().copied());
279 }
280 let mut locations = Vec::new();
281 for id in results {
282 if let Some(location) =
283 id_to_location_with_index(&nodes, &id_to_path_map, id, name_location_index)
284 {
285 locations.push(location);
286 }
287 }
288
289 for (src_str, decl_id) in &external_refs {
291 if *decl_id == target_node_id
292 && let Some(location) = src_to_location(src_str, &id_to_path_map) {
293 locations.push(location);
294 }
295 }
296
297 let mut unique_locations = Vec::new();
298 let mut seen = std::collections::HashSet::new();
299 for location in locations {
300 let key = (
301 location.uri.clone(),
302 location.range.start.line,
303 location.range.start.character,
304 location.range.end.line,
305 location.range.end.character,
306 );
307 if seen.insert(key) {
308 unique_locations.push(location);
309 }
310 }
311 unique_locations
312}
313
314pub fn goto_references_for_target(
319 build: &CachedBuild,
320 def_abs_path: &str,
321 def_byte_offset: usize,
322 name_location_index: Option<usize>,
323) -> Vec<Location> {
324 let target_node_id = match byte_to_id(&build.nodes, def_abs_path, def_byte_offset) {
326 Some(id) => {
327 if let Some(file_nodes) = build.nodes.get(def_abs_path) {
329 if let Some(node_info) = file_nodes.get(&id) {
330 node_info.referenced_declaration.unwrap_or(id)
331 } else {
332 id
333 }
334 } else {
335 id
336 }
337 }
338 None => return vec![],
339 };
340
341 let mut results = HashSet::new();
343 results.insert(target_node_id);
344 for file_nodes in build.nodes.values() {
345 for (id, node_info) in file_nodes {
346 if node_info.referenced_declaration == Some(target_node_id) {
347 results.insert(*id);
348 }
349 }
350 }
351
352 let mut locations = Vec::new();
353 for id in results {
354 if let Some(location) =
355 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
356 {
357 locations.push(location);
358 }
359 }
360
361 for (src_str, decl_id) in &build.external_refs {
363 if *decl_id == target_node_id
364 && let Some(location) = src_to_location(src_str, &build.id_to_path_map) {
365 locations.push(location);
366 }
367 }
368
369 locations
370}