1use crate::SourceLocation;
10use crate::ast::{Node, NodeKind};
11use crate::symbol::{ScopeId, ScopeKind, SymbolKind};
12use regex::Regex;
13use std::sync::OnceLock;
14
15use super::SemanticAnalyzer;
16use super::builtins::{
17 get_builtin_documentation, is_builtin_function, is_control_keyword, is_file_test_operator,
18};
19use super::hover::HoverInfo;
20use super::tokens::{SemanticToken, SemanticTokenModifier, SemanticTokenType};
21
22impl SemanticAnalyzer {
23 pub(super) fn analyze_node(&mut self, node: &Node, scope_id: ScopeId) {
25 match &node.kind {
26 NodeKind::Program { statements } => {
27 for stmt in statements {
28 self.analyze_node(stmt, scope_id);
29 }
30 }
31
32 NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
33 if let NodeKind::Variable { sigil, name } = &variable.kind {
35 let token_type = match declarator.as_str() {
36 "my" | "state" => SemanticTokenType::VariableDeclaration,
37 "our" => SemanticTokenType::Variable,
38 "local" => SemanticTokenType::Variable,
39 _ => SemanticTokenType::Variable,
40 };
41
42 let mut modifiers = vec![SemanticTokenModifier::Declaration];
43 if declarator == "state" || attributes.iter().any(|a| a == ":shared") {
44 modifiers.push(SemanticTokenModifier::Static);
45 }
46
47 self.semantic_tokens.push(SemanticToken {
48 location: variable.location,
49 token_type,
50 modifiers,
51 });
52
53 let hover = HoverInfo {
55 signature: format!("{} {}{}", declarator, sigil, name),
56 documentation: self.extract_documentation(node.location.start),
57 details: if attributes.is_empty() {
58 vec![]
59 } else {
60 vec![format!("Attributes: {}", attributes.join(", "))]
61 },
62 };
63
64 self.hover_info.insert(variable.location, hover);
65 }
66
67 if let Some(init) = initializer {
68 self.analyze_node(init, scope_id);
69 }
70 }
71
72 NodeKind::Variable { sigil, name } => {
73 let kind = match sigil.as_str() {
74 "$" => SymbolKind::scalar(),
75 "@" => SymbolKind::array(),
76 "%" => SymbolKind::hash(),
77 _ => return,
78 };
79
80 let symbols = self.symbol_table.find_symbol(name, scope_id, kind);
82
83 let token_type = if let Some(symbol) = symbols.first() {
84 match symbol.declaration.as_deref() {
85 Some("my") | Some("state") => SemanticTokenType::Variable,
86 Some("our") => SemanticTokenType::Variable,
87 _ => SemanticTokenType::Variable,
88 }
89 } else {
90 SemanticTokenType::Variable
92 };
93
94 self.semantic_tokens.push(SemanticToken {
95 location: node.location,
96 token_type,
97 modifiers: vec![],
98 });
99
100 if let Some(symbol) = symbols.first() {
102 let hover = HoverInfo {
103 signature: format!(
104 "{} {}{}",
105 symbol.declaration.as_deref().unwrap_or(""),
106 sigil,
107 name
108 )
109 .trim()
110 .to_string(),
111 documentation: symbol.documentation.clone(),
112 details: vec![format!(
113 "Defined at line {}",
114 self.line_number(symbol.location.start)
115 )],
116 };
117
118 self.hover_info.insert(node.location, hover);
119 }
120 }
121
122 NodeKind::Subroutine { name, prototype, signature, attributes, body, name_span: _ } => {
123 if let Some(sub_name) = name {
124 let token = SemanticToken {
126 location: node.location,
127 token_type: SemanticTokenType::FunctionDeclaration,
128 modifiers: vec![SemanticTokenModifier::Declaration],
129 };
130
131 self.semantic_tokens.push(token);
132
133 let mut signature_str = format!("sub {}", sub_name);
135 if let Some(sig_node) = signature {
136 signature_str.push_str(&format_signature_params(sig_node));
137 }
138
139 let hover = HoverInfo {
140 signature: signature_str,
141 documentation: self.extract_documentation(node.location.start),
142 details: if attributes.is_empty() {
143 vec![]
144 } else {
145 vec![format!("Attributes: {}", attributes.join(", "))]
146 },
147 };
148
149 self.hover_info.insert(node.location, hover);
150 } else {
151 self.semantic_tokens.push(SemanticToken {
154 location: SourceLocation {
155 start: node.location.start,
156 end: node.location.start + 3, },
158 token_type: SemanticTokenType::Keyword,
159 modifiers: vec![],
160 });
161
162 let mut signature_str = "sub".to_string();
164 if let Some(sig_node) = signature {
165 signature_str.push_str(&format_signature_params(sig_node));
166 }
167 signature_str.push_str(" { ... }");
168
169 let mut details = vec!["Anonymous subroutine (closure)".to_string()];
170 if !attributes.is_empty() {
171 details.push(format!("Attributes: {}", attributes.join(", ")));
172 }
173
174 let hover = HoverInfo {
175 signature: signature_str,
176 documentation: self.extract_documentation(node.location.start),
177 details,
178 };
179
180 self.hover_info.insert(node.location, hover);
181 }
182
183 {
184 let sub_scope = self.get_scope_for(node, ScopeKind::Subroutine);
186
187 if let Some(proto) = prototype {
188 self.analyze_node(proto, sub_scope);
189 }
190 if let Some(sig) = signature {
191 self.analyze_node(sig, sub_scope);
192 }
193
194 self.analyze_node(body, sub_scope);
195 }
196 }
197
198 NodeKind::Method { name, signature, attributes, body } => {
199 self.semantic_tokens.push(SemanticToken {
200 location: node.location, token_type: SemanticTokenType::FunctionDeclaration,
202 modifiers: vec![SemanticTokenModifier::Declaration],
203 });
204
205 let hover = HoverInfo {
207 signature: format!("method {}", name),
208 documentation: self.extract_documentation(node.location.start),
209 details: if attributes.is_empty() {
210 vec![]
211 } else {
212 vec![format!("Attributes: {}", attributes.join(", "))]
213 },
214 };
215 self.hover_info.insert(node.location, hover);
216
217 let sub_scope = self.get_scope_for(node, ScopeKind::Subroutine);
219 if let Some(sig) = signature {
220 self.analyze_node(sig, sub_scope);
221 }
222 self.analyze_node(body, sub_scope);
223 }
224
225 NodeKind::FunctionCall { name, args } => {
226 {
228 let token_type = if is_control_keyword(name) {
229 SemanticTokenType::KeywordControl
230 } else if is_builtin_function(name) {
231 SemanticTokenType::Function
232 } else {
233 let symbols =
235 self.symbol_table.find_symbol(name, scope_id, SymbolKind::Subroutine);
236 if symbols.is_empty() {
237 SemanticTokenType::Function
238 } else {
239 SemanticTokenType::Function
240 }
241 };
242
243 self.semantic_tokens.push(SemanticToken {
244 location: node.location,
245 token_type,
246 modifiers: if is_builtin_function(name) && !is_control_keyword(name) {
247 vec![SemanticTokenModifier::DefaultLibrary]
248 } else {
249 vec![]
250 },
251 });
252
253 if let Some(doc) = get_builtin_documentation(name) {
255 let hover = HoverInfo {
256 signature: doc.signature.to_string(),
257 documentation: Some(doc.description.to_string()),
258 details: vec![],
259 };
260
261 self.hover_info.insert(node.location, hover);
262 }
263 }
264
265 for arg in args {
267 self.analyze_node(arg, scope_id);
268 }
269 }
270
271 NodeKind::Package { name, block, name_span: _ } => {
272 self.semantic_tokens.push(SemanticToken {
273 location: node.location,
274 token_type: SemanticTokenType::Namespace,
275 modifiers: vec![SemanticTokenModifier::Declaration],
276 });
277
278 let documentation = self
280 .extract_pod_name_section(name)
281 .or_else(|| self.extract_documentation(node.location.start));
282
283 let hover = HoverInfo {
284 signature: format!("package {}", name),
285 documentation,
286 details: vec![],
287 };
288
289 self.hover_info.insert(node.location, hover);
290
291 if let Some(block_node) = block {
292 let package_scope = self.get_scope_for(node, ScopeKind::Package);
293 self.analyze_node(block_node, package_scope);
294 }
295 }
296
297 NodeKind::String { value: _, interpolated: _ } => {
298 self.semantic_tokens.push(SemanticToken {
299 location: node.location,
300 token_type: SemanticTokenType::String,
301 modifiers: vec![],
302 });
303 }
304
305 NodeKind::Number { value: _ } => {
306 self.semantic_tokens.push(SemanticToken {
307 location: node.location,
308 token_type: SemanticTokenType::Number,
309 modifiers: vec![],
310 });
311 }
312
313 NodeKind::Regex { .. } => {
314 self.semantic_tokens.push(SemanticToken {
315 location: node.location,
316 token_type: SemanticTokenType::Regex,
317 modifiers: vec![],
318 });
319 }
320
321 NodeKind::Match { expr, .. } => {
322 self.semantic_tokens.push(SemanticToken {
323 location: node.location,
324 token_type: SemanticTokenType::Regex,
325 modifiers: vec![],
326 });
327 self.analyze_node(expr, scope_id);
328 }
329 NodeKind::Substitution { expr, .. } => {
330 self.semantic_tokens.push(SemanticToken {
332 location: node.location,
333 token_type: SemanticTokenType::Operator,
334 modifiers: vec![],
335 });
336 self.analyze_node(expr, scope_id);
337 }
338 NodeKind::Transliteration { expr, .. } => {
339 self.semantic_tokens.push(SemanticToken {
341 location: node.location,
342 token_type: SemanticTokenType::Operator,
343 modifiers: vec![],
344 });
345 self.analyze_node(expr, scope_id);
346 }
347
348 NodeKind::LabeledStatement { label: _, statement } => {
349 self.semantic_tokens.push(SemanticToken {
350 location: node.location,
351 token_type: SemanticTokenType::Label,
352 modifiers: vec![],
353 });
354
355 {
356 self.analyze_node(statement, scope_id);
357 }
358 }
359
360 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
362 self.analyze_node(condition, scope_id);
363 self.analyze_node(then_branch, scope_id);
364 for (elsif_cond, elsif_branch) in elsif_branches {
365 self.analyze_node(elsif_cond, scope_id);
366 self.analyze_node(elsif_branch, scope_id);
367 }
368 if let Some(else_node) = else_branch {
369 self.analyze_node(else_node, scope_id);
370 }
371 }
372
373 NodeKind::While { condition, body, continue_block: _ } => {
374 self.analyze_node(condition, scope_id);
375 self.analyze_node(body, scope_id);
376 }
377
378 NodeKind::For { init, condition, update, body, .. } => {
379 if let Some(init_node) = init {
380 self.analyze_node(init_node, scope_id);
381 }
382 if let Some(cond_node) = condition {
383 self.analyze_node(cond_node, scope_id);
384 }
385 if let Some(update_node) = update {
386 self.analyze_node(update_node, scope_id);
387 }
388 self.analyze_node(body, scope_id);
389 }
390
391 NodeKind::Foreach { variable, list, body, continue_block } => {
392 self.analyze_node(variable, scope_id);
393 self.analyze_node(list, scope_id);
394 self.analyze_node(body, scope_id);
395 if let Some(cb) = continue_block {
396 self.analyze_node(cb, scope_id);
397 }
398 }
399
400 NodeKind::Block { statements } => {
402 for stmt in statements {
403 self.analyze_node(stmt, scope_id);
404 }
405 }
406
407 NodeKind::Binary { left, right, .. } => {
408 self.analyze_node(left, scope_id);
409 self.analyze_node(right, scope_id);
410 }
411
412 NodeKind::Assignment { lhs, rhs, .. } => {
413 self.analyze_node(lhs, scope_id);
414 self.analyze_node(rhs, scope_id);
415 }
416
417 NodeKind::VariableListDeclaration {
419 declarator,
420 variables,
421 attributes,
422 initializer,
423 } => {
424 for var in variables {
426 if let NodeKind::Variable { sigil, name } = &var.kind {
427 let token_type = match declarator.as_str() {
428 "my" | "state" => SemanticTokenType::VariableDeclaration,
429 "our" => SemanticTokenType::Variable,
430 "local" => SemanticTokenType::Variable,
431 _ => SemanticTokenType::Variable,
432 };
433
434 let mut modifiers = vec![SemanticTokenModifier::Declaration];
435 if declarator == "state" || attributes.iter().any(|a| a == ":shared") {
436 modifiers.push(SemanticTokenModifier::Static);
437 }
438
439 self.semantic_tokens.push(SemanticToken {
440 location: var.location,
441 token_type,
442 modifiers,
443 });
444
445 let hover = HoverInfo {
447 signature: format!("{} {}{}", declarator, sigil, name),
448 documentation: self.extract_documentation(var.location.start),
449 details: if attributes.is_empty() {
450 vec![]
451 } else {
452 vec![format!("Attributes: {}", attributes.join(", "))]
453 },
454 };
455
456 self.hover_info.insert(var.location, hover);
457 }
458 }
459
460 if let Some(init) = initializer {
461 self.analyze_node(init, scope_id);
462 }
463 }
464
465 NodeKind::Ternary { condition, then_expr, else_expr } => {
466 self.analyze_node(condition, scope_id);
468 self.analyze_node(then_expr, scope_id);
469 self.analyze_node(else_expr, scope_id);
470 }
471
472 NodeKind::ArrayLiteral { elements } => {
473 for elem in elements {
475 self.analyze_node(elem, scope_id);
476 }
477 }
478
479 NodeKind::HashLiteral { pairs } => {
480 for (key, value) in pairs {
482 self.analyze_node(key, scope_id);
483 self.analyze_node(value, scope_id);
484 }
485 }
486
487 NodeKind::Try { body, catch_blocks, finally_block } => {
488 self.analyze_node(body, scope_id);
490
491 for (_var, catch_body) in catch_blocks {
492 self.analyze_node(catch_body, scope_id);
494 }
495
496 if let Some(finally) = finally_block {
497 self.analyze_node(finally, scope_id);
498 }
499 }
500
501 NodeKind::PhaseBlock { phase: _, phase_span: _, block } => {
502 self.semantic_tokens.push(SemanticToken {
504 location: node.location,
505 token_type: SemanticTokenType::Keyword,
506 modifiers: vec![],
507 });
508
509 self.analyze_node(block, scope_id);
510 }
511
512 NodeKind::ExpressionStatement { expression } => {
513 self.analyze_node(expression, scope_id);
516 }
517
518 NodeKind::Do { block } => {
519 self.analyze_node(block, scope_id);
522 }
523
524 NodeKind::Eval { block } => {
525 self.semantic_tokens.push(SemanticToken {
527 location: node.location,
528 token_type: SemanticTokenType::Keyword,
529 modifiers: vec![],
530 });
531
532 self.analyze_node(block, scope_id);
534 }
535
536 NodeKind::VariableWithAttributes { variable, attributes } => {
537 self.analyze_node(variable, scope_id);
540
541 if attributes.iter().any(|a| a == ":shared" || a == ":lvalue") {
543 }
546 }
547
548 NodeKind::Unary { op, operand } => {
549 if matches!(op.as_str(), "++" | "--" | "!" | "-" | "~" | "\\") {
552 self.semantic_tokens.push(SemanticToken {
553 location: node.location,
554 token_type: SemanticTokenType::Operator,
555 modifiers: vec![],
556 });
557 }
558
559 if is_file_test_operator(op) {
561 self.semantic_tokens.push(SemanticToken {
562 location: node.location,
563 token_type: SemanticTokenType::Operator,
564 modifiers: vec![],
565 });
566 }
567
568 self.analyze_node(operand, scope_id);
569 }
570
571 NodeKind::Readline { filehandle } => {
572 self.semantic_tokens.push(SemanticToken {
574 location: node.location,
575 token_type: SemanticTokenType::Operator, modifiers: vec![],
577 });
578
579 if let Some(fh) = filehandle {
581 let hover = HoverInfo {
582 signature: format!("<{}>", fh),
583 documentation: match fh.as_str() {
584 "STDIN" => Some("Standard input filehandle".to_string()),
585 "STDOUT" => Some("Standard output filehandle".to_string()),
586 "STDERR" => Some("Standard error filehandle".to_string()),
587 _ => Some(format!("Read from filehandle {}", fh)),
588 },
589 details: vec![],
590 };
591 self.hover_info.insert(node.location, hover);
592 } else {
593 let hover = HoverInfo {
595 signature: "<>".to_string(),
596 documentation: Some("Read from command-line files or STDIN".to_string()),
597 details: vec![],
598 };
599 self.hover_info.insert(node.location, hover);
600 }
601 }
602
603 NodeKind::MethodCall { object, method, args } => {
605 self.analyze_node(object, scope_id);
606
607 if let Some(offset) =
608 self.find_substring_in_source_after(node, method, object.location.end)
609 {
610 self.semantic_tokens.push(SemanticToken {
611 location: SourceLocation { start: offset, end: offset + method.len() },
612 token_type: SemanticTokenType::Method,
613 modifiers: vec![],
614 });
615 }
616
617 for arg in args {
618 self.analyze_node(arg, scope_id);
619 }
620 }
621
622 NodeKind::IndirectCall { method, object, args } => {
623 if let Some(offset) = self.find_method_name_in_source(node, method) {
624 self.semantic_tokens.push(SemanticToken {
625 location: SourceLocation { start: offset, end: offset + method.len() },
626 token_type: SemanticTokenType::Method,
627 modifiers: vec![],
628 });
629 }
630 self.analyze_node(object, scope_id);
631 for arg in args {
632 self.analyze_node(arg, scope_id);
633 }
634 }
635
636 NodeKind::Use { module, args, .. } => {
637 self.semantic_tokens.push(SemanticToken {
638 location: SourceLocation {
639 start: node.location.start,
640 end: node.location.start + 3,
641 },
642 token_type: SemanticTokenType::Keyword,
643 modifiers: vec![],
644 });
645
646 let mut args_start = node.location.start + 3;
647 if let Some(offset) = self.find_substring_in_source(node, module) {
648 self.semantic_tokens.push(SemanticToken {
649 location: SourceLocation { start: offset, end: offset + module.len() },
650 token_type: SemanticTokenType::Namespace,
651 modifiers: vec![],
652 });
653 args_start = offset + module.len();
654 }
655
656 self.analyze_string_args(node, args, args_start);
657 }
658
659 NodeKind::No { module, args, .. } => {
660 self.semantic_tokens.push(SemanticToken {
661 location: SourceLocation {
662 start: node.location.start,
663 end: node.location.start + 2,
664 },
665 token_type: SemanticTokenType::Keyword,
666 modifiers: vec![],
667 });
668
669 let mut args_start = node.location.start + 2;
670 if let Some(offset) = self.find_substring_in_source(node, module) {
671 self.semantic_tokens.push(SemanticToken {
672 location: SourceLocation { start: offset, end: offset + module.len() },
673 token_type: SemanticTokenType::Namespace,
674 modifiers: vec![],
675 });
676 args_start = offset + module.len();
677 }
678
679 self.analyze_string_args(node, args, args_start);
680 }
681
682 NodeKind::Given { expr, body } => {
683 self.semantic_tokens.push(SemanticToken {
684 location: SourceLocation {
685 start: node.location.start,
686 end: node.location.start + 5,
687 }, token_type: SemanticTokenType::KeywordControl,
689 modifiers: vec![],
690 });
691 self.analyze_node(expr, scope_id);
692 self.analyze_node(body, scope_id);
693 }
694
695 NodeKind::When { condition, body } => {
696 self.semantic_tokens.push(SemanticToken {
697 location: SourceLocation {
698 start: node.location.start,
699 end: node.location.start + 4,
700 }, token_type: SemanticTokenType::KeywordControl,
702 modifiers: vec![],
703 });
704 self.analyze_node(condition, scope_id);
705 self.analyze_node(body, scope_id);
706 }
707
708 NodeKind::Default { body } => {
709 self.semantic_tokens.push(SemanticToken {
710 location: SourceLocation {
711 start: node.location.start,
712 end: node.location.start + 7,
713 }, token_type: SemanticTokenType::KeywordControl,
715 modifiers: vec![],
716 });
717 self.analyze_node(body, scope_id);
718 }
719
720 NodeKind::Return { value } => {
721 self.semantic_tokens.push(SemanticToken {
722 location: SourceLocation {
723 start: node.location.start,
724 end: node.location.start + 6,
725 }, token_type: SemanticTokenType::KeywordControl,
727 modifiers: vec![],
728 });
729 if let Some(v) = value {
730 self.analyze_node(v, scope_id);
731 }
732 }
733
734 NodeKind::Class { name, body } => {
735 self.semantic_tokens.push(SemanticToken {
736 location: SourceLocation {
737 start: node.location.start,
738 end: node.location.start + 5,
739 }, token_type: SemanticTokenType::Keyword,
741 modifiers: vec![],
742 });
743
744 if let Some(offset) = self.find_substring_in_source(node, name) {
745 self.semantic_tokens.push(SemanticToken {
746 location: SourceLocation { start: offset, end: offset + name.len() },
747 token_type: SemanticTokenType::Class,
748 modifiers: vec![SemanticTokenModifier::Declaration],
749 });
750 }
751
752 let class_scope = self.get_scope_for(node, ScopeKind::Package);
753 self.analyze_node(body, class_scope);
754 }
755
756 NodeKind::Signature { parameters } => {
757 for param in parameters {
758 self.analyze_node(param, scope_id);
759 }
760 }
761
762 NodeKind::MandatoryParameter { variable }
763 | NodeKind::OptionalParameter { variable, .. }
764 | NodeKind::SlurpyParameter { variable }
765 | NodeKind::NamedParameter { variable } => {
766 self.analyze_node(variable, scope_id);
767 }
768
769 NodeKind::Diamond | NodeKind::Ellipsis => {
770 self.semantic_tokens.push(SemanticToken {
771 location: node.location,
772 token_type: SemanticTokenType::Operator,
773 modifiers: vec![],
774 });
775 }
776
777 NodeKind::Undef => {
778 self.semantic_tokens.push(SemanticToken {
779 location: node.location,
780 token_type: SemanticTokenType::Keyword,
781 modifiers: vec![],
782 });
783 }
784
785 NodeKind::Identifier { .. } => {
786 }
789
790 NodeKind::Heredoc { .. } => {
791 self.semantic_tokens.push(SemanticToken {
792 location: node.location,
793 token_type: SemanticTokenType::String,
794 modifiers: vec![],
795 });
796 }
797
798 NodeKind::Glob { .. } => {
799 self.semantic_tokens.push(SemanticToken {
800 location: node.location,
801 token_type: SemanticTokenType::Operator,
802 modifiers: vec![],
803 });
804 }
805
806 NodeKind::DataSection { .. } => {
807 self.semantic_tokens.push(SemanticToken {
808 location: node.location,
809 token_type: SemanticTokenType::Comment,
810 modifiers: vec![],
811 });
812 }
813
814 NodeKind::Prototype { .. } => {
815 self.semantic_tokens.push(SemanticToken {
816 location: node.location,
817 token_type: SemanticTokenType::Punctuation,
818 modifiers: vec![],
819 });
820 }
821
822 NodeKind::Typeglob { .. } => {
823 self.semantic_tokens.push(SemanticToken {
824 location: node.location,
825 token_type: SemanticTokenType::Variable,
826 modifiers: vec![],
827 });
828 }
829
830 NodeKind::Untie { variable } => {
831 self.analyze_node(variable, scope_id);
832 }
833
834 NodeKind::LoopControl { .. } => {
835 self.semantic_tokens.push(SemanticToken {
836 location: node.location,
837 token_type: SemanticTokenType::KeywordControl,
838 modifiers: vec![],
839 });
840 }
841
842 NodeKind::Goto { target } => {
843 self.semantic_tokens.push(SemanticToken {
844 location: node.location,
845 token_type: SemanticTokenType::KeywordControl,
846 modifiers: vec![],
847 });
848 self.analyze_node(target, scope_id);
849 }
850
851 NodeKind::MissingExpression
852 | NodeKind::MissingStatement
853 | NodeKind::MissingIdentifier
854 | NodeKind::MissingBlock => {
855 }
857
858 NodeKind::Tie { variable, package, args } => {
859 self.analyze_node(variable, scope_id);
860 self.analyze_node(package, scope_id);
861 for arg in args {
862 self.analyze_node(arg, scope_id);
863 }
864 }
865
866 NodeKind::StatementModifier { statement, condition, modifier } => {
867 if matches!(modifier.as_str(), "for" | "foreach" | "while" | "until") {
870 self.semantic_tokens.push(SemanticToken {
871 location: node.location,
872 token_type: SemanticTokenType::KeywordControl,
873 modifiers: vec![],
874 });
875 }
876 self.analyze_node(statement, scope_id);
877 self.analyze_node(condition, scope_id);
878 }
879
880 NodeKind::Format { name, .. } => {
881 self.semantic_tokens.push(SemanticToken {
882 location: node.location,
883 token_type: SemanticTokenType::FunctionDeclaration,
884 modifiers: vec![SemanticTokenModifier::Declaration],
885 });
886
887 let hover = HoverInfo {
888 signature: format!("format {} =", name),
889 documentation: None,
890 details: vec![],
891 };
892 self.hover_info.insert(node.location, hover);
893 }
894
895 NodeKind::Error { .. } | NodeKind::UnknownRest => {
896 }
898 }
899 }
900
901 pub(super) fn extract_documentation(&self, start: usize) -> Option<String> {
903 static POD_RE: OnceLock<Result<Regex, regex::Error>> = OnceLock::new();
904 static COMMENT_RE: OnceLock<Result<Regex, regex::Error>> = OnceLock::new();
905
906 if self.source.is_empty() {
907 return None;
908 }
909 let before = &self.source[..start];
910
911 let pod_re = POD_RE
913 .get_or_init(|| Regex::new(r"(?ms)(=[a-zA-Z0-9].*?\n=cut\n?)\s*$"))
914 .as_ref()
915 .ok()?;
916 if let Some(caps) = pod_re.captures(before) {
917 if let Some(pod_text) = caps.get(1) {
918 return Some(pod_text.as_str().trim().to_string());
919 }
920 }
921
922 let comment_re =
924 COMMENT_RE.get_or_init(|| Regex::new(r"(?m)(#.*\n)+\s*$")).as_ref().ok()?;
925 if let Some(caps) = comment_re.captures(before) {
926 if let Some(comment_match) = caps.get(0) {
927 let doc = comment_match
929 .as_str()
930 .lines()
931 .map(|line| line.trim_start_matches('#').trim())
932 .filter(|line| !line.is_empty())
933 .collect::<Vec<_>>()
934 .join(" ");
935 return Some(doc);
936 }
937 }
938
939 None
940 }
941
942 pub(super) fn extract_pod_name_section(&self, package_name: &str) -> Option<String> {
947 if self.source.is_empty() {
948 return None;
949 }
950
951 let mut in_name_section = false;
952 let mut name_lines: Vec<&str> = Vec::new();
953
954 for line in self.source.lines() {
955 let trimmed: &str = line.trim();
956 if trimmed.starts_with("=head1") {
957 if in_name_section {
958 break;
959 }
960 let heading = trimmed.strip_prefix("=head1").map(|s: &str| s.trim());
961 if heading == Some("NAME") {
962 in_name_section = true;
963 continue;
964 }
965 } else if trimmed.starts_with("=cut") && in_name_section {
966 break;
967 } else if trimmed.starts_with('=') && in_name_section {
968 break;
969 } else if in_name_section && !trimmed.is_empty() {
970 name_lines.push(trimmed);
971 }
972 }
973
974 if !name_lines.is_empty() {
975 let name_doc = name_lines.join(" ");
976 if name_doc.contains(package_name)
977 || name_doc.contains(&package_name.replace("::", "-"))
978 {
979 return Some(name_doc);
980 }
981 }
982
983 None
984 }
985
986 pub(super) fn get_scope_for(&self, node: &Node, kind: ScopeKind) -> ScopeId {
988 for scope in self.symbol_table.scopes.values() {
989 if scope.kind == kind
990 && scope.location.start == node.location.start
991 && scope.location.end == node.location.end
992 {
993 return scope.id;
994 }
995 }
996 0
997 }
998
999 pub(super) fn line_number(&self, offset: usize) -> usize {
1001 if self.source.is_empty() { 1 } else { self.source[..offset].lines().count() + 1 }
1002 }
1003
1004 pub(super) fn find_substring_in_source(&self, node: &Node, substring: &str) -> Option<usize> {
1006 if self.source.len() < node.location.end {
1007 return None;
1008 }
1009 let node_text = &self.source[node.location.start..node.location.end];
1010 if let Some(pos) = node_text.find(substring) {
1011 return Some(node.location.start + pos);
1012 }
1013 None
1014 }
1015
1016 pub(super) fn find_method_name_in_source(
1018 &self,
1019 node: &Node,
1020 method_name: &str,
1021 ) -> Option<usize> {
1022 self.find_substring_in_source(node, method_name)
1023 }
1024
1025 pub(super) fn find_substring_in_source_after(
1027 &self,
1028 node: &Node,
1029 substring: &str,
1030 after: usize,
1031 ) -> Option<usize> {
1032 if self.source.len() < node.location.end || after >= node.location.end {
1033 return None;
1034 }
1035
1036 let start_rel = after.saturating_sub(node.location.start);
1037
1038 let node_text = &self.source[node.location.start..node.location.end];
1039 if start_rel >= node_text.len() {
1040 return None;
1041 }
1042
1043 let text_to_search = &node_text[start_rel..];
1044 if let Some(pos) = text_to_search.find(substring) {
1045 return Some(node.location.start + start_rel + pos);
1046 }
1047 None
1048 }
1049
1050 pub(super) fn analyze_string_args(
1052 &mut self,
1053 node: &Node,
1054 args: &[String],
1055 start_offset: usize,
1056 ) {
1057 let mut current_offset = start_offset;
1058 for arg in args {
1059 if let Some(offset) = self.find_substring_in_source_after(node, arg, current_offset) {
1060 self.semantic_tokens.push(SemanticToken {
1061 location: SourceLocation { start: offset, end: offset + arg.len() },
1062 token_type: SemanticTokenType::String,
1063 modifiers: vec![],
1064 });
1065 current_offset = offset + arg.len();
1066 }
1067 }
1068 }
1069
1070 pub fn infer_type(&self, node: &Node) -> Option<String> {
1089 match &node.kind {
1090 NodeKind::Number { .. } => Some("number".to_string()),
1091 NodeKind::String { .. } => Some("string".to_string()),
1092 NodeKind::ArrayLiteral { .. } => Some("array".to_string()),
1093 NodeKind::HashLiteral { .. } => Some("hash".to_string()),
1094
1095 NodeKind::Variable { sigil, name } => {
1096 let kind = match sigil.as_str() {
1098 "$" => SymbolKind::scalar(),
1099 "@" => SymbolKind::array(),
1100 "%" => SymbolKind::hash(),
1101 _ => return None,
1102 };
1103
1104 let symbols = self.symbol_table.find_symbol(name, 0, kind);
1105 symbols.first()?;
1106
1107 match sigil.as_str() {
1109 "$" => Some("scalar".to_string()),
1110 "@" => Some("array".to_string()),
1111 "%" => Some("hash".to_string()),
1112 _ => None,
1113 }
1114 }
1115
1116 NodeKind::FunctionCall { name, .. } => {
1117 match name.as_str() {
1119 "scalar" => Some("scalar".to_string()),
1120 "ref" => Some("string".to_string()),
1121 "length" | "index" | "rindex" => Some("number".to_string()),
1122 "split" => Some("array".to_string()),
1123 "keys" | "values" => Some("array".to_string()),
1124 _ => None,
1125 }
1126 }
1127
1128 NodeKind::Binary { op, .. } => {
1129 match op.as_str() {
1131 "+" | "-" | "*" | "/" | "%" | "**" => Some("number".to_string()),
1132 "." | "x" => Some("string".to_string()),
1133 "==" | "!=" | "<" | ">" | "<=" | ">=" | "eq" | "ne" | "lt" | "gt" | "le"
1134 | "ge" => Some("boolean".to_string()),
1135 _ => None,
1136 }
1137 }
1138
1139 _ => None,
1140 }
1141 }
1142}
1143
1144fn format_signature_params(sig_node: &Node) -> String {
1149 let NodeKind::Signature { parameters } = &sig_node.kind else {
1150 return "(...)".to_string();
1151 };
1152
1153 let labels: Vec<String> = parameters
1154 .iter()
1155 .filter_map(|param| {
1156 let var = match ¶m.kind {
1157 NodeKind::MandatoryParameter { variable }
1158 | NodeKind::OptionalParameter { variable, .. }
1159 | NodeKind::SlurpyParameter { variable }
1160 | NodeKind::NamedParameter { variable } => variable.as_ref(),
1161 NodeKind::Variable { .. } => param,
1162 _ => return None,
1163 };
1164 if let NodeKind::Variable { sigil, name } = &var.kind {
1165 Some(format!("{}{}", sigil, name))
1166 } else {
1167 None
1168 }
1169 })
1170 .collect();
1171
1172 format!("({})", labels.join(", "))
1173}