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::{bytes_to_pos, cache_ids, pos_to_bytes, src_to_location, ExternalRefs, NodeInfo};
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_decl_via_external_refs(
23 external_refs: &ExternalRefs,
24 id_to_path: &HashMap<String, String>,
25 abs_path: &str,
26 byte_position: usize,
27) -> Option<u64> {
28 let path_to_file_id: HashMap<&str, &str> = id_to_path
30 .iter()
31 .map(|(id, p)| (p.as_str(), id.as_str()))
32 .collect();
33 let current_file_id = path_to_file_id.get(abs_path)?;
34
35 for (src_str, decl_id) in external_refs {
36 let parts: Vec<&str> = src_str.split(':').collect();
37 if parts.len() != 3 {
38 continue;
39 }
40 if parts[2] != *current_file_id {
42 continue;
43 }
44 if let (Ok(start), Ok(length)) = (parts[0].parse::<usize>(), parts[1].parse::<usize>()) {
45 if start <= byte_position && byte_position < start + length {
46 return Some(*decl_id);
47 }
48 }
49 }
50 None
51}
52
53pub fn byte_to_id(
54 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
55 abs_path: &str,
56 byte_position: usize,
57) -> Option<u64> {
58 let file_nodes = nodes.get(abs_path)?;
59 let mut refs: HashMap<usize, u64> = HashMap::new();
60 for (id, node_info) in file_nodes {
61 let src_parts: Vec<&str> = node_info.src.split(':').collect();
62 if src_parts.len() != 3 {
63 continue;
64 }
65 let start: usize = src_parts[0].parse().ok()?;
66 let length: usize = src_parts[1].parse().ok()?;
67 let end = start + length;
68
69 if start <= byte_position && byte_position < end {
70 let diff = end - start;
71 refs.entry(diff).or_insert(*id);
72 }
73 }
74 refs.keys().min().map(|min_diff| refs[min_diff])
75}
76
77pub fn id_to_location(
78 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
79 id_to_path: &HashMap<String, String>,
80 node_id: u64,
81) -> Option<Location> {
82 id_to_location_with_index(nodes, id_to_path, node_id, None)
83}
84
85pub fn id_to_location_with_index(
86 nodes: &HashMap<String, HashMap<u64, NodeInfo>>,
87 id_to_path: &HashMap<String, String>,
88 node_id: u64,
89 name_location_index: Option<usize>,
90) -> Option<Location> {
91 let mut target_node: Option<&NodeInfo> = None;
92 for file_nodes in nodes.values() {
93 if let Some(node) = file_nodes.get(&node_id) {
94 target_node = Some(node);
95 break;
96 }
97 }
98 let node = target_node?;
99
100 let (byte_str, length_str, file_id) = if let Some(index) = name_location_index {
101 if let Some(name_loc) = node.name_locations.get(index) {
102 let parts: Vec<&str> = name_loc.split(':').collect();
103 if parts.len() == 3 {
104 (parts[0], parts[1], parts[2])
105 } else {
106 return None;
107 }
108 } else {
109 return None;
110 }
111 } else if let Some(name_location) = &node.name_location {
112 let parts: Vec<&str> = name_location.split(':').collect();
113 if parts.len() == 3 {
114 (parts[0], parts[1], parts[2])
115 } else {
116 return None;
117 }
118 } else {
119 let parts: Vec<&str> = node.src.split(':').collect();
121 if parts.len() == 3 {
122 (parts[0], parts[1], parts[2])
123 } else {
124 return None;
125 }
126 };
127
128 let byte_offset: usize = byte_str.parse().ok()?;
129 let length: usize = length_str.parse().ok()?;
130 let file_path = id_to_path.get(file_id)?;
131
132 let absolute_path = if std::path::Path::new(file_path).is_absolute() {
133 std::path::PathBuf::from(file_path)
134 } else {
135 std::env::current_dir().ok()?.join(file_path)
136 };
137 let source_bytes = std::fs::read(&absolute_path).ok()?;
138 let start_pos = bytes_to_pos(&source_bytes, byte_offset)?;
139 let end_pos = bytes_to_pos(&source_bytes, byte_offset + length)?;
140 let uri = Url::from_file_path(&absolute_path).ok()?;
141
142 Some(Location {
143 uri,
144 range: Range {
145 start: start_pos,
146 end: end_pos,
147 },
148 })
149}
150
151pub fn goto_references(
152 ast_data: &Value,
153 file_uri: &Url,
154 position: Position,
155 source_bytes: &[u8],
156) -> Vec<Location> {
157 goto_references_with_index(ast_data, file_uri, position, source_bytes, None)
158}
159
160pub fn goto_references_with_index(
161 ast_data: &Value,
162 file_uri: &Url,
163 position: Position,
164 source_bytes: &[u8],
165 name_location_index: Option<usize>,
166) -> Vec<Location> {
167 let sources = match ast_data.get("sources") {
168 Some(s) => s,
169 None => return vec![],
170 };
171 let build_infos = match ast_data.get("build_infos").and_then(|v| v.as_array()) {
172 Some(infos) => infos,
173 None => return vec![],
174 };
175 let first_build_info = match build_infos.first() {
176 Some(info) => info,
177 None => return vec![],
178 };
179 let id_to_path = match first_build_info
180 .get("source_id_to_path")
181 .and_then(|v| v.as_object())
182 {
183 Some(map) => map,
184 None => return vec![],
185 };
186 let id_to_path_map: HashMap<String, String> = id_to_path
187 .iter()
188 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
189 .collect();
190
191 let (nodes, path_to_abs, external_refs) = cache_ids(sources);
192 let all_refs = all_references(&nodes);
193 let path = match file_uri.to_file_path() {
194 Ok(p) => p,
195 Err(_) => return vec![],
196 };
197 let path_str = match path.to_str() {
198 Some(s) => s,
199 None => return vec![],
200 };
201 let abs_path = match path_to_abs.get(path_str) {
202 Some(ap) => ap,
203 None => return vec![],
204 };
205 let byte_position = pos_to_bytes(source_bytes, position);
206
207 let target_node_id = if let Some(decl_id) =
209 byte_to_decl_via_external_refs(&external_refs, &id_to_path_map, abs_path, byte_position)
210 {
211 decl_id
212 } else {
213 let node_id = match byte_to_id(&nodes, abs_path, byte_position) {
214 Some(id) => id,
215 None => return vec![],
216 };
217 let file_nodes = match nodes.get(abs_path) {
218 Some(nodes) => nodes,
219 None => return vec![],
220 };
221 if let Some(node_info) = file_nodes.get(&node_id) {
222 node_info.referenced_declaration.unwrap_or(node_id)
223 } else {
224 node_id
225 }
226 };
227
228 let mut results = HashSet::new();
229 results.insert(target_node_id);
230 if let Some(refs) = all_refs.get(&target_node_id) {
231 results.extend(refs.iter().copied());
232 }
233 let mut locations = Vec::new();
234 for id in results {
235 if let Some(location) =
236 id_to_location_with_index(&nodes, &id_to_path_map, id, name_location_index)
237 {
238 locations.push(location);
239 }
240 }
241
242 for (src_str, decl_id) in &external_refs {
244 if *decl_id == target_node_id {
245 if let Some(location) = src_to_location(src_str, &id_to_path_map) {
246 locations.push(location);
247 }
248 }
249 }
250
251 let mut unique_locations = Vec::new();
252 let mut seen = std::collections::HashSet::new();
253 for location in locations {
254 let key = (
255 location.uri.clone(),
256 location.range.start.line,
257 location.range.start.character,
258 location.range.end.line,
259 location.range.end.character,
260 );
261 if seen.insert(key) {
262 unique_locations.push(location);
263 }
264 }
265 unique_locations
266}