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 CachedBuild, ExternalRefs, NodeInfo, bytes_to_pos, cache_ids, pos_to_bytes, src_to_location,
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
48 && byte_position < start + length
49 {
50 return Some(*decl_id);
51 }
52 }
53 None
54}
55
56pub fn byte_to_id(
57 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
58 abs_path: &str,
59 byte_position: usize,
60) -> Option<u64> {
61 let file_nodes = nodes.get(abs_path)?;
62 let mut refs: HashMap<usize, u64> = HashMap::new();
63 for (id, node_info) in file_nodes {
64 let src_parts: Vec<&str> = node_info.src.split(':').collect();
65 if src_parts.len() != 3 {
66 continue;
67 }
68 let start: usize = src_parts[0].parse().ok()?;
69 let length: usize = src_parts[1].parse().ok()?;
70 let end = start + length;
71
72 if start <= byte_position && byte_position < end {
73 let diff = end - start;
74 refs.entry(diff).or_insert(*id);
75 }
76 }
77 refs.keys().min().map(|min_diff| refs[min_diff])
78}
79
80pub fn id_to_location(
81 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
82 id_to_path: &HashMap<String, String>,
83 node_id: u64,
84) -> Option<Location> {
85 id_to_location_with_index(nodes, id_to_path, node_id, None)
86}
87
88pub fn id_to_location_with_index(
89 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
90 id_to_path: &HashMap<String, String>,
91 node_id: u64,
92 name_location_index: Option<usize>,
93) -> Option<Location> {
94 let mut target_node: Option<&NodeInfo> = None;
95 for file_nodes in nodes.values() {
96 if let Some(node) = file_nodes.get(&node_id) {
97 target_node = Some(node);
98 break;
99 }
100 }
101 let node = target_node?;
102
103 let (byte_str, length_str, file_id) = if let Some(index) = name_location_index
104 && let Some(name_loc) = node.name_locations.get(index)
105 {
106 let parts: Vec<&str> = name_loc.split(':').collect();
107 if parts.len() == 3 {
108 (parts[0], parts[1], parts[2])
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 include_declaration: bool,
158) -> Vec<Location> {
159 goto_references_with_index(
160 ast_data,
161 file_uri,
162 position,
163 source_bytes,
164 None,
165 include_declaration,
166 )
167}
168
169pub fn resolve_target_location(
174 build: &CachedBuild,
175 file_uri: &Url,
176 position: Position,
177 source_bytes: &[u8],
178) -> Option<(String, usize)> {
179 let path = file_uri.to_file_path().ok()?;
180 let path_str = path.to_str()?;
181 let abs_path = build.path_to_abs.get(path_str)?;
182 let byte_position = pos_to_bytes(source_bytes, position);
183
184 let target_node_id = if let Some(decl_id) = byte_to_decl_via_external_refs(
186 &build.external_refs,
187 &build.id_to_path_map,
188 abs_path,
189 byte_position,
190 ) {
191 decl_id
192 } else {
193 let node_id = byte_to_id(&build.nodes, abs_path, byte_position)?;
194 let file_nodes = build.nodes.get(abs_path)?;
195 if let Some(node_info) = file_nodes.get(&node_id) {
196 node_info.referenced_declaration.unwrap_or(node_id)
197 } else {
198 node_id
199 }
200 };
201
202 for (file_abs_path, file_nodes) in &build.nodes {
204 if let Some(node_info) = file_nodes.get(&target_node_id) {
205 let src_parts: Vec<&str> = node_info.src.split(':').collect();
206 if src_parts.len() == 3 {
207 let byte_offset: usize = src_parts[0].parse().ok()?;
208 return Some((file_abs_path.clone(), byte_offset));
209 }
210 }
211 }
212 None
213}
214
215pub fn goto_references_with_index(
216 ast_data: &Value,
217 file_uri: &Url,
218 position: Position,
219 source_bytes: &[u8],
220 name_location_index: Option<usize>,
221 include_declaration: bool,
222) -> Vec<Location> {
223 let sources = match ast_data.get("sources") {
224 Some(s) => s,
225 None => return vec![],
226 };
227 let build_infos = match ast_data.get("build_infos").and_then(|v| v.as_array()) {
228 Some(infos) => infos,
229 None => return vec![],
230 };
231 let first_build_info = match build_infos.first() {
232 Some(info) => info,
233 None => return vec![],
234 };
235 let id_to_path = match first_build_info
236 .get("source_id_to_path")
237 .and_then(|v| v.as_object())
238 {
239 Some(map) => map,
240 None => return vec![],
241 };
242 let id_to_path_map: HashMap<String, String> = id_to_path
243 .iter()
244 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
245 .collect();
246
247 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
248 let all_refs = all_references(&nodes);
249 let path = match file_uri.to_file_path() {
250 Ok(p) => p,
251 Err(_) => return vec![],
252 };
253 let path_str = match path.to_str() {
254 Some(s) => s,
255 None => return vec![],
256 };
257 let abs_path = match path_to_abs.get(path_str) {
258 Some(ap) => ap,
259 None => return vec![],
260 };
261 let byte_position = pos_to_bytes(source_bytes, position);
262
263 let target_node_id = if let Some(decl_id) =
265 byte_to_decl_via_external_refs(&external_refs, &id_to_path_map, abs_path, byte_position)
266 {
267 decl_id
268 } else {
269 let node_id = match byte_to_id(&nodes, abs_path, byte_position) {
270 Some(id) => id,
271 None => return vec![],
272 };
273 let file_nodes = match nodes.get(abs_path) {
274 Some(nodes) => nodes,
275 None => return vec![],
276 };
277 if let Some(node_info) = file_nodes.get(&node_id) {
278 node_info.referenced_declaration.unwrap_or(node_id)
279 } else {
280 node_id
281 }
282 };
283
284 let mut results = HashSet::new();
285 if include_declaration {
286 results.insert(target_node_id);
287 }
288 if let Some(refs) = all_refs.get(&target_node_id) {
289 results.extend(refs.iter().copied());
290 }
291 let mut locations = Vec::new();
292 for id in results {
293 if let Some(location) =
294 id_to_location_with_index(&nodes, &id_to_path_map, id, name_location_index)
295 {
296 locations.push(location);
297 }
298 }
299
300 for (src_str, decl_id) in &external_refs {
302 if *decl_id == target_node_id
303 && let Some(location) = src_to_location(src_str, &id_to_path_map)
304 {
305 locations.push(location);
306 }
307 }
308
309 let mut unique_locations = Vec::new();
310 let mut seen = std::collections::HashSet::new();
311 for location in locations {
312 let key = (
313 location.uri.clone(),
314 location.range.start.line,
315 location.range.start.character,
316 location.range.end.line,
317 location.range.end.character,
318 );
319 if seen.insert(key) {
320 unique_locations.push(location);
321 }
322 }
323 unique_locations
324}
325
326pub fn goto_references_for_target(
331 build: &CachedBuild,
332 def_abs_path: &str,
333 def_byte_offset: usize,
334 name_location_index: Option<usize>,
335 include_declaration: bool,
336) -> Vec<Location> {
337 let target_node_id = match byte_to_id(&build.nodes, def_abs_path, def_byte_offset) {
339 Some(id) => {
340 if let Some(file_nodes) = build.nodes.get(def_abs_path) {
342 if let Some(node_info) = file_nodes.get(&id) {
343 node_info.referenced_declaration.unwrap_or(id)
344 } else {
345 id
346 }
347 } else {
348 id
349 }
350 }
351 None => return vec![],
352 };
353
354 let mut results = HashSet::new();
356 if include_declaration {
357 results.insert(target_node_id);
358 }
359 for file_nodes in build.nodes.values() {
360 for (id, node_info) in file_nodes {
361 if node_info.referenced_declaration == Some(target_node_id) {
362 results.insert(*id);
363 }
364 }
365 }
366
367 let mut locations = Vec::new();
368 for id in results {
369 if let Some(location) =
370 id_to_location_with_index(&build.nodes, &build.id_to_path_map, id, name_location_index)
371 {
372 locations.push(location);
373 }
374 }
375
376 for (src_str, decl_id) in &build.external_refs {
378 if *decl_id == target_node_id
379 && let Some(location) = src_to_location(src_str, &build.id_to_path_map)
380 {
381 locations.push(location);
382 }
383 }
384
385 locations
386}