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