1use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9
10use sipha::line_index::LineIndex;
11use sipha::red::SyntaxNode;
12use sipha::types::IntoSyntaxKind;
13
14use leekscript_analysis::{
15 analyze, analyze_with_include_tree, analyze_with_signatures, build_scope_extents,
16 class_decl_info, function_decl_info, scope_at_offset, var_decl_info, ResolvedSymbol, ScopeId,
17 ScopeStore, VarDeclKind,
18};
19use leekscript_core::doc_comment::{build_doc_map, DocComment};
20use leekscript_core::syntax::Kind;
21use leekscript_core::{
22 build_include_tree, parse, parse_error_to_diagnostics, parse_recovering_multi, IncludeTree,
23 Type,
24};
25use sipha_analysis::collect_definitions;
26
27#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
29pub enum RootSymbolKind {
30 Class,
31 Function,
32 Global,
33}
34
35fn is_top_level(node: &SyntaxNode, root: &SyntaxNode) -> bool {
36 for anc in node.ancestors(root) {
37 if let Some(Kind::NodeBlock | Kind::NodeFunctionDecl | Kind::NodeClassDecl) =
38 anc.kind_as::<Kind>()
39 {
40 return false;
41 }
42 }
43 true
44}
45
46#[must_use]
48pub fn build_definition_map(
49 tree: &IncludeTree,
50 main_path: &Path,
51) -> HashMap<(String, RootSymbolKind), (PathBuf, u32, u32)> {
52 let mut roots: Vec<(PathBuf, SyntaxNode)> = tree
53 .includes
54 .iter()
55 .filter_map(|(path, child)| child.root.as_ref().map(|r| (path.clone(), r.clone())))
56 .collect();
57 if let Some(ref root) = tree.root {
58 roots.push((main_path.to_path_buf(), root.clone()));
59 }
60 collect_definitions(&roots, |node, root| {
61 if !is_top_level(node, root) {
62 return None;
63 }
64 if node.kind_as::<Kind>() == Some(Kind::NodeClassDecl) {
65 return class_decl_info(node)
66 .map(|info| (info.name, RootSymbolKind::Class, info.name_span));
67 }
68 if node.kind_as::<Kind>() == Some(Kind::NodeFunctionDecl) {
69 return function_decl_info(node)
70 .map(|info| (info.name, RootSymbolKind::Function, info.name_span));
71 }
72 if node.kind_as::<Kind>() == Some(Kind::NodeVarDecl) {
73 if let Some(info) = var_decl_info(node) {
74 if info.kind == VarDeclKind::Global {
75 return Some((info.name, RootSymbolKind::Global, info.name_span));
76 }
77 }
78 }
79 None
80 })
81}
82
83#[must_use]
85pub fn decl_span_for_name_span(
86 root: &SyntaxNode,
87 name_start: u32,
88 name_end: u32,
89) -> Option<(u32, u32)> {
90 for node in root.find_all_nodes(Kind::NodeClassDecl.into_syntax_kind()) {
91 if let Some(info) = class_decl_info(&node) {
92 if info.name_span.start == name_start && info.name_span.end == name_end {
93 let r = node.text_range();
94 return Some((r.start, r.end));
95 }
96 }
97 }
98 for node in root.find_all_nodes(Kind::NodeFunctionDecl.into_syntax_kind()) {
99 if let Some(info) = function_decl_info(&node) {
100 if info.name_span.start == name_start && info.name_span.end == name_end {
101 let r = node.text_range();
102 return Some((r.start, r.end));
103 }
104 }
105 }
106 for node in root.find_all_nodes(Kind::NodeVarDecl.into_syntax_kind()) {
107 if !is_top_level(&node, root) {
108 continue;
109 }
110 if let Some(info) = var_decl_info(&node) {
111 if info.kind == VarDeclKind::Global
112 && info.name_span.start == name_start
113 && info.name_span.end == name_end
114 {
115 let r = node.text_range();
116 return Some((r.start, r.end));
117 }
118 }
119 }
120 None
121}
122
123#[must_use]
125pub fn build_class_super(root: Option<&SyntaxNode>) -> HashMap<String, String> {
126 let mut map = HashMap::new();
127 let root = match root {
128 Some(r) => r,
129 None => return map,
130 };
131 for node in root.find_all_nodes(Kind::NodeClassDecl.into_syntax_kind()) {
132 if let Some(info) = class_decl_info(&node) {
133 if let Some(super_name) = info.super_class {
134 map.insert(info.name, super_name);
135 }
136 }
137 }
138 map
139}
140
141#[derive(Default)]
146pub struct DocumentAnalysisOptions<'a> {
147 pub source: &'a str,
149 pub main_path: Option<&'a Path>,
151 pub signature_roots: &'a [SyntaxNode],
153 pub existing_root: Option<SyntaxNode>,
155 pub max_parse_errors: Option<usize>,
157 pub sig_definition_locations: Option<&'a HashMap<String, (PathBuf, u32)>>,
159}
160
161#[derive(Debug)]
163pub struct DocumentAnalysis {
164 pub source: String,
166 pub line_index: LineIndex,
168 pub root: Option<SyntaxNode>,
170 pub include_tree: Option<IncludeTree>,
172 pub main_path: Option<PathBuf>,
174 pub diagnostics: Vec<sipha::error::SemanticDiagnostic>,
176 pub type_map: HashMap<(u32, u32), leekscript_core::Type>,
178 pub scope_store: ScopeStore,
179 pub scope_extents: Vec<(ScopeId, (u32, u32))>,
181 pub definition_map: HashMap<(String, RootSymbolKind), (PathBuf, u32, u32)>,
183 pub doc_map: HashMap<(u32, u32), DocComment>,
185 pub include_doc_maps: Option<HashMap<PathBuf, HashMap<(u32, u32), DocComment>>>,
187 pub class_super: HashMap<String, String>,
189 pub sig_definition_locations: Option<HashMap<String, (PathBuf, u32)>>,
191}
192
193impl DocumentAnalysis {
194 #[must_use]
200 pub fn new_with_options(options: &DocumentAnalysisOptions<'_>) -> Self {
201 let DocumentAnalysisOptions {
202 source,
203 main_path,
204 signature_roots,
205 existing_root,
206 max_parse_errors,
207 sig_definition_locations,
208 } = options;
209 let max_errors = max_parse_errors.unwrap_or(PARSE_RECOVERY_MAX_ERRORS);
210 let mut diagnostics = Vec::new();
211 let mut type_map = HashMap::new();
212 let mut scope_store = ScopeStore::new();
213 let mut scope_extents = vec![];
214 let mut definition_map = HashMap::new();
215 let mut doc_map = HashMap::new();
216 let mut include_doc_maps: Option<HashMap<PathBuf, HashMap<(u32, u32), DocComment>>> = None;
217 let mut include_tree: Option<IncludeTree> = None;
218 let mut main_path_buf: Option<PathBuf> = main_path.map(Path::to_path_buf);
219 let mut root: Option<SyntaxNode> = None;
220 let mut source_owned = (*source).to_string();
221
222 match main_path {
223 Some(path) => match build_include_tree(source, Some(path)) {
224 Ok(tree) => {
225 let result = analyze_with_include_tree(&tree, signature_roots);
226 diagnostics = result.diagnostics;
227 type_map = result.type_map;
228 scope_store = result.scope_store;
229 let len = tree.source.len() as u32;
230 scope_extents = match &tree.root {
231 Some(r) => build_scope_extents(r, &result.scope_id_sequence, len as usize),
232 _ => vec![(ScopeId(0), (0, len))],
233 };
234 if tree.root.is_none() {
235 if let Err(parse_err) = parse(&tree.source) {
236 diagnostics
237 .extend(parse_error_to_diagnostics(&parse_err, &tree.source));
238 }
239 }
240 definition_map = build_definition_map(&tree, path);
241 doc_map = tree.root.as_ref().map(build_doc_map).unwrap_or_default();
242 let mut inc_doc = HashMap::new();
243 for (p, child) in &tree.includes {
244 if let Some(ref inc_root) = child.root {
245 inc_doc.insert(p.clone(), build_doc_map(inc_root));
246 }
247 }
248 include_doc_maps = Some(inc_doc);
249 include_tree = Some(tree.clone());
250 main_path_buf = Some(path.to_path_buf());
251 source_owned = tree.source.clone();
252 root = tree.root.clone();
253 }
254 Err(_) => {
255 single_file_analysis(
256 source,
257 signature_roots,
258 existing_root.clone(),
259 max_errors,
260 &mut diagnostics,
261 &mut type_map,
262 &mut scope_store,
263 &mut scope_extents,
264 &mut root,
265 );
266 }
267 },
268 None => {
269 single_file_analysis(
270 source,
271 signature_roots,
272 existing_root.clone(),
273 max_errors,
274 &mut diagnostics,
275 &mut type_map,
276 &mut scope_store,
277 &mut scope_extents,
278 &mut root,
279 );
280 }
281 }
282
283 if doc_map.is_empty() && root.is_some() && include_tree.is_none() {
284 doc_map = build_doc_map(root.as_ref().unwrap());
285 }
286
287 let class_super = build_class_super(root.as_ref());
288
289 let line_index = LineIndex::new(source_owned.as_bytes());
290
291 Self {
292 source: source_owned,
293 line_index,
294 root,
295 include_tree,
296 main_path: main_path_buf,
297 diagnostics,
298 type_map,
299 scope_store,
300 scope_extents,
301 definition_map,
302 doc_map,
303 include_doc_maps,
304 class_super,
305 sig_definition_locations: sig_definition_locations.cloned(),
306 }
307 }
308
309 #[must_use]
315 pub fn new(
316 source: &str,
317 main_path: Option<&Path>,
318 signature_roots: &[SyntaxNode],
319 existing_root: Option<SyntaxNode>,
320 sig_definition_locations: Option<HashMap<String, (PathBuf, u32)>>,
321 ) -> Self {
322 Self::new_with_options(&DocumentAnalysisOptions {
323 source,
324 main_path,
325 signature_roots,
326 existing_root,
327 max_parse_errors: None,
328 sig_definition_locations: sig_definition_locations.as_ref(),
329 })
330 }
331
332 #[must_use]
335 pub fn symbol_at_offset(&self, byte_offset: u32) -> Option<ResolvedSymbol> {
336 let root = self.root.as_ref()?;
337 let token = root.token_at_offset(byte_offset)?;
338 if token.kind_as::<Kind>() != Some(Kind::TokIdent) {
339 return None;
340 }
341 let name = token.text().to_string();
342 let scope_id = scope_at_offset(&self.scope_extents, byte_offset);
343 self.scope_store.resolve(scope_id, &name)
344 }
345
346 #[must_use]
349 pub fn type_at_offset(&self, byte_offset: u32) -> Option<Type> {
350 let root = self.root.as_ref()?;
351 let node = root.node_at_offset(byte_offset)?;
352 let range = node.text_range();
353 let key = (range.start, range.end);
354 self.type_map.get(&key).cloned().or_else(|| {
355 for anc in node.ancestors(root) {
356 let r = anc.text_range();
357 if let Some(t) = self.type_map.get(&(r.start, r.end)) {
358 return Some(t.clone());
359 }
360 }
361 None
362 })
363 }
364
365 #[must_use]
368 pub fn definition_span_for(
369 &self,
370 name: &str,
371 kind: RootSymbolKind,
372 ) -> Option<(PathBuf, u32, u32)> {
373 self.definition_map.get(&(name.to_string(), kind)).cloned()
374 }
375
376 #[must_use]
380 pub fn minimal(source: String) -> Self {
381 let line_index = LineIndex::new(source.as_bytes());
382 let len = source.len() as u32;
383 Self {
384 source,
385 line_index,
386 root: None,
387 include_tree: None,
388 main_path: None,
389 diagnostics: Vec::new(),
390 type_map: HashMap::new(),
391 scope_store: ScopeStore::new(),
392 scope_extents: vec![(ScopeId(0), (0, len))],
393 definition_map: HashMap::new(),
394 doc_map: HashMap::new(),
395 include_doc_maps: None,
396 class_super: HashMap::new(),
397 sig_definition_locations: None,
398 }
399 }
400
401 #[must_use]
404 pub fn minimal_with_root(source: String, root: SyntaxNode) -> Self {
405 let line_index = LineIndex::new(source.as_bytes());
406 let len = source.len() as u32;
407 Self {
408 source,
409 line_index,
410 root: Some(root),
411 include_tree: None,
412 main_path: None,
413 diagnostics: Vec::new(),
414 type_map: HashMap::new(),
415 scope_store: ScopeStore::new(),
416 scope_extents: vec![(ScopeId(0), (0, len))],
417 definition_map: HashMap::new(),
418 doc_map: HashMap::new(),
419 include_doc_maps: None,
420 class_super: HashMap::new(),
421 sig_definition_locations: None,
422 }
423 }
424
425 #[must_use]
428 pub fn from_parse_only(source: &str) -> Self {
429 let mut diagnostics = Vec::new();
430 let root = match parse_recovering_multi(source, PARSE_RECOVERY_MAX_ERRORS) {
431 Ok(output) => output.syntax_root(source.as_bytes()),
432 Err(recover) => {
433 for parse_err in &recover.errors {
434 diagnostics.extend(parse_error_to_diagnostics(parse_err, source));
435 }
436 recover.partial.syntax_root(source.as_bytes())
437 }
438 };
439 let source_owned = source.to_string();
440 let line_index = LineIndex::new(source_owned.as_bytes());
441 let scope_extents = vec![(ScopeId(0), (0, source.len() as u32))];
442 let doc_map = root.as_ref().map(build_doc_map).unwrap_or_default();
443 let class_super = build_class_super(root.as_ref());
444 Self {
445 source: source_owned,
446 line_index,
447 root,
448 include_tree: None,
449 main_path: None,
450 diagnostics,
451 type_map: HashMap::new(),
452 scope_store: ScopeStore::new(),
453 scope_extents,
454 definition_map: HashMap::new(),
455 doc_map,
456 include_doc_maps: None,
457 class_super,
458 sig_definition_locations: None,
459 }
460 }
461}
462
463const PARSE_RECOVERY_MAX_ERRORS: usize = 64;
465
466fn single_file_analysis(
467 source: &str,
468 signature_roots: &[SyntaxNode],
469 existing_root: Option<SyntaxNode>,
470 max_parse_errors: usize,
471 diagnostics: &mut Vec<sipha::error::SemanticDiagnostic>,
472 type_map: &mut HashMap<(u32, u32), leekscript_core::Type>,
473 scope_store: &mut ScopeStore,
474 scope_extents: &mut Vec<(ScopeId, (u32, u32))>,
475 root: &mut Option<SyntaxNode>,
476) {
477 let parsed = if let Some(r) = existing_root {
478 Some(r)
479 } else {
480 match parse_recovering_multi(source, max_parse_errors) {
481 Ok(output) => output.syntax_root(source.as_bytes()),
482 Err(recover) => {
483 for parse_err in &recover.errors {
484 diagnostics.extend(parse_error_to_diagnostics(parse_err, source));
485 }
486 recover.partial.syntax_root(source.as_bytes())
487 }
488 }
489 };
490
491 if let Some(ref r) = parsed {
492 let result = if signature_roots.is_empty() {
493 analyze(r)
494 } else {
495 analyze_with_signatures(r, signature_roots)
496 };
497 diagnostics.extend(result.diagnostics);
498 *type_map = result.type_map;
499 *scope_store = result.scope_store;
500 *scope_extents = build_scope_extents(r, &result.scope_id_sequence, source.len());
501 *root = Some(r.clone());
502 } else {
503 *scope_extents = vec![(ScopeId(0), (0, source.len() as u32))];
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use std::path::Path;
510
511 use leekscript_core::{build_include_tree, parse};
512
513 use super::{
514 build_class_super, build_definition_map, decl_span_for_name_span, DocumentAnalysis,
515 RootSymbolKind,
516 };
517
518 #[test]
519 fn build_definition_map_main_only() {
520 let source = r#"
521global integer x = 1;
522function f(integer a) -> integer { return a; }
523class C { }
524"#;
525 let main_path = Path::new("main.leek");
526 let tree = build_include_tree(source, Some(main_path)).expect("build_include_tree");
527 let map = build_definition_map(&tree, main_path);
528 assert!(map.contains_key(&("x".to_string(), RootSymbolKind::Global)));
529 assert!(map.contains_key(&("f".to_string(), RootSymbolKind::Function)));
530 assert!(map.contains_key(&("C".to_string(), RootSymbolKind::Class)));
531 for (_, (path, start, end)) in &map {
532 assert_eq!(path, main_path);
533 assert!(end > start);
534 }
535 }
536
537 #[test]
538 fn build_class_super_single_inheritance() {
539 let source = "class Child extends Parent { }";
540 let root = parse(source).unwrap().expect("parse");
541 let map = build_class_super(Some(&root));
542 assert_eq!(map.get("Child"), Some(&"Parent".to_string()));
543 assert_eq!(map.len(), 1);
544 }
545
546 #[test]
547 fn build_class_super_none_for_no_extends() {
548 let source = "class C { }";
549 let root = parse(source).unwrap().expect("parse");
550 let map = build_class_super(Some(&root));
551 assert!(map.is_empty());
552 }
553
554 #[test]
555 fn build_class_super_none_root() {
556 let map = build_class_super(None);
557 assert!(map.is_empty());
558 }
559
560 #[test]
561 fn document_analysis_empty_source() {
562 let analysis = DocumentAnalysis::new("", None, &[], None, None);
563 assert_eq!(analysis.source, "");
564 assert!(!analysis.scope_extents.is_empty());
565 }
566
567 #[test]
568 fn document_analysis_incomplete_syntax_does_not_panic() {
569 let source = "var x = ";
570 let analysis = DocumentAnalysis::new(source, None, &[], None, None);
571 let _ = &analysis.source;
572 let _ = &analysis.scope_extents;
573 let _ = &analysis.scope_store;
574 }
575
576 #[test]
577 fn document_analysis_unclosed_brace_does_not_panic() {
578 let source = "function f() { return 1; ";
579 let analysis = DocumentAnalysis::new(source, None, &[], None, None);
580 let _ = &analysis.diagnostics;
581 let _ = &analysis.scope_extents;
582 }
583
584 #[test]
585 fn document_analysis_symbol_at_offset_no_root_returns_none() {
586 let analysis = DocumentAnalysis::new("", None, &[], None, None);
587 assert!(analysis.symbol_at_offset(0).is_none());
588 }
589
590 #[test]
591 fn document_analysis_type_at_offset_no_root_returns_none() {
592 let analysis = DocumentAnalysis::new("", None, &[], None, None);
593 assert!(analysis.type_at_offset(0).is_none());
594 }
595
596 #[test]
597 fn decl_span_for_name_span_class() {
598 let source = "class Foo { }";
599 let root = parse(source).unwrap().expect("parse");
600 let decl_span = decl_span_for_name_span(&root, 6, 9);
602 assert!(decl_span.is_some());
603 let (start, end) = decl_span.unwrap();
604 assert!(end > start);
605 assert!(start <= 6 && end >= 9);
606 }
607
608 #[test]
609 fn decl_span_for_name_span_function() {
610 let source = "function bar() { }";
611 let root = parse(source).unwrap().expect("parse");
612 let decl_span = decl_span_for_name_span(&root, 9, 12);
614 assert!(decl_span.is_some());
615 let (start, end) = decl_span.unwrap();
616 assert!(end > start);
617 }
618
619 #[test]
620 fn decl_span_for_name_span_unknown_returns_none() {
621 let source = "var x = 1;";
622 let root = parse(source).unwrap().expect("parse");
623 let decl_span = decl_span_for_name_span(&root, 4, 5); assert!(decl_span.is_none());
625 }
626}