solidity_language_server/
references.rs

1use 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        // Fallback to src location for nodes without nameLocation
87        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}