1use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10
11use harn_lexer::{Lexer, Span, Token, TokenKind};
12use harn_parser::{
13 lexical::{resolved_identifier_bindings, BindingId},
14 visit::walk_program_interpolated,
15 Node,
16};
17
18use super::{DefKind, DefSite, ModuleGraph, ParsedModuleSource};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct RefSite {
23 pub file: PathBuf,
24 pub span: Span,
25 pub name: String,
26}
27
28#[derive(Debug, Clone, Default)]
34pub struct ReferenceIndex {
35 pub files: Vec<PathBuf>,
38 pub has_unsaved_buffers: bool,
41 by_def: HashMap<DefKey, Vec<RefSite>>,
42 definitions: HashMap<DefKey, DefSite>,
43 site_defs: Vec<(RefSite, DefKey)>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47struct DefKey {
48 file: PathBuf,
49 name: String,
50 start: usize,
51 end: usize,
52}
53
54impl DefKey {
55 fn from_def(def: &DefSite) -> Self {
56 Self {
57 file: def.file.clone(),
58 name: def.name.clone(),
59 start: def.span.start,
60 end: def.span.end,
61 }
62 }
63}
64
65impl ReferenceIndex {
66 pub fn definition_at(
71 &self,
72 file: &std::path::Path,
73 name: &str,
74 offset: usize,
75 ) -> Option<DefSite> {
76 let file = super::canonical_path(file);
77 self.site_defs
78 .iter()
79 .filter(|(site, _)| {
80 site.file == file
81 && site.name == name
82 && offset >= site.span.start
83 && offset <= site.span.end
84 })
85 .min_by_key(|(site, _)| site.span.end.saturating_sub(site.span.start))
86 .and_then(|(_, key)| self.definitions.get(key))
87 .cloned()
88 }
89
90 pub fn references_to(&self, def: &DefSite) -> Vec<RefSite> {
92 self.by_def
93 .get(&DefKey::from_def(def))
94 .cloned()
95 .unwrap_or_default()
96 }
97
98 pub fn edges(&self) -> Vec<ReferenceEdge> {
101 let mut edges = Vec::new();
102 for (key, refs) in &self.by_def {
103 for site in refs {
104 edges.push(ReferenceEdge {
105 from: site.clone(),
106 to_file: key.file.clone(),
107 to_name: key.name.clone(),
108 });
109 }
110 }
111 edges.sort_by(|left, right| {
112 left.from
113 .file
114 .cmp(&right.from.file)
115 .then_with(|| left.from.span.start.cmp(&right.from.span.start))
116 .then_with(|| left.to_file.cmp(&right.to_file))
117 .then_with(|| left.to_name.cmp(&right.to_name))
118 });
119 edges
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct ReferenceEdge {
127 pub from: RefSite,
128 pub to_file: PathBuf,
129 pub to_name: String,
130}
131
132pub fn index_references(
139 graph: &ModuleGraph,
140 sources: &HashMap<PathBuf, ParsedModuleSource>,
141 unsaved: &HashSet<PathBuf>,
142) -> ReferenceIndex {
143 let mut files: Vec<PathBuf> = sources.keys().cloned().collect();
144 files.sort();
145 let has_unsaved_buffers = files.iter().any(|file| unsaved.contains(file));
146 let mut index = ReferenceIndex {
147 files,
148 has_unsaved_buffers,
149 by_def: HashMap::new(),
150 definitions: HashMap::new(),
151 site_defs: Vec::new(),
152 };
153
154 for (file, parsed) in sources {
155 let lexical = resolved_identifier_bindings(&[], &parsed.program);
156 let tokens = Lexer::new(&parsed.source)
157 .tokenize()
158 .expect("a retained parsed source must still lex");
159
160 let mut lexical_defs: HashMap<BindingId, DefSite> = HashMap::new();
164 for binding in lexical.values() {
165 lexical_defs.entry(binding.clone()).or_insert_with(|| {
166 graph
167 .definition_of(file, &binding.name)
168 .filter(|def| {
169 def.file == *file
170 && def.span.start == binding.declaration_start
171 && def.span.end == binding.declaration_end
172 })
173 .unwrap_or_else(|| DefSite {
174 name: binding.name.clone(),
175 file: file.clone(),
176 kind: DefKind::Variable,
177 span: Span::with_offsets(
178 binding.declaration_start,
179 binding.declaration_end,
180 1,
181 1,
182 ),
183 })
184 });
185 }
186 for (binding, def) in &lexical_defs {
187 if let Some(span) = identifier_span(
188 &tokens,
189 &binding.name,
190 binding.declaration_start,
191 binding.declaration_end,
192 ) {
193 insert_reference(
194 &mut index,
195 def.clone(),
196 RefSite {
197 file: file.clone(),
198 span,
199 name: binding.name.clone(),
200 },
201 );
202 }
203 }
204
205 walk_program_interpolated(&parsed.source, &parsed.program, &mut |node| {
206 for (name, broad_span) in name_uses(node) {
207 let def = lexical
208 .get(&(broad_span.start, broad_span.end))
209 .and_then(|binding| lexical_defs.get(binding).cloned())
210 .or_else(|| graph.definition_of(file, name));
211 if let (Some(def), Some(span)) = (
212 def,
213 identifier_span(&tokens, name, broad_span.start, broad_span.end),
214 ) {
215 insert_reference(
216 &mut index,
217 def,
218 RefSite {
219 file: file.clone(),
220 span,
221 name: name.to_string(),
222 },
223 );
224 }
225 }
226 });
227 }
228
229 for refs in index.by_def.values_mut() {
230 refs.sort_by(|left, right| {
231 left.file
232 .cmp(&right.file)
233 .then_with(|| left.span.start.cmp(&right.span.start))
234 });
235 refs.dedup_by(|left, right| {
236 left.file == right.file
237 && left.span.start == right.span.start
238 && left.name == right.name
239 });
240 }
241 index.site_defs.sort_by(|(left, _), (right, _)| {
242 left.file
243 .cmp(&right.file)
244 .then_with(|| left.span.start.cmp(&right.span.start))
245 .then_with(|| left.name.cmp(&right.name))
246 });
247 index
248 .site_defs
249 .dedup_by(|(left, left_key), (right, right_key)| {
250 left.file == right.file
251 && left.span == right.span
252 && left.name == right.name
253 && left_key == right_key
254 });
255
256 index
257}
258
259fn insert_reference(index: &mut ReferenceIndex, def: DefSite, site: RefSite) {
260 let key = DefKey::from_def(&def);
261 index.definitions.entry(key.clone()).or_insert(def);
262 index
263 .by_def
264 .entry(key.clone())
265 .or_default()
266 .push(site.clone());
267 index.site_defs.push((site, key));
268}
269
270fn identifier_span(tokens: &[Token], name: &str, start: usize, end: usize) -> Option<Span> {
271 tokens.iter().find_map(|token| match &token.kind {
272 TokenKind::Identifier(found)
273 if found == name && token.span.start >= start && token.span.end <= end =>
274 {
275 Some(token.span)
276 }
277 _ => None,
278 })
279}
280
281fn name_uses(node: &harn_parser::SNode) -> Vec<(&str, Span)> {
282 match &node.node {
283 Node::Identifier(name) => vec![(name.as_str(), node.span)],
284 Node::FunctionCall { name, .. } => vec![(name.as_str(), node.span)],
285 Node::FnDecl { name, .. }
286 | Node::Pipeline { name, .. }
287 | Node::ToolDecl { name, .. }
288 | Node::SkillDecl { name, .. }
289 | Node::StructDecl { name, .. }
290 | Node::EnumDecl { name, .. }
291 | Node::InterfaceDecl { name, .. }
292 | Node::TypeDecl { name, .. }
293 | Node::OverrideDecl { name, .. } => vec![(name.as_str(), node.span)],
294 Node::EvalPackDecl { binding_name, .. } => vec![(binding_name.as_str(), node.span)],
295 _ => Vec::new(),
296 }
297}