1use crate::ast::{Node, NodeKind};
7use crate::symbol::is_universal_method;
8use crate::workspace_index::{SymKind, SymbolKey};
9use rustc_hash::FxHashMap;
10use std::sync::Arc;
11
12pub type ParentMap = FxHashMap<*const Node, *const Node>;
42
43pub struct DeclarationProvider<'a> {
62 pub ast: Arc<Node>,
64 content: String,
65 document_uri: String,
66 parent_map: Option<&'a ParentMap>,
67 doc_version: i32,
68}
69
70#[derive(Debug, Clone)]
72pub struct LocationLink {
73 pub origin_selection_range: (usize, usize),
75 pub target_uri: String,
77 pub target_range: (usize, usize),
79 pub target_selection_range: (usize, usize),
81}
82
83impl<'a> DeclarationProvider<'a> {
84 pub fn new(ast: Arc<Node>, content: String, document_uri: String) -> Self {
109 Self {
110 ast,
111 content,
112 document_uri,
113 parent_map: None,
114 doc_version: 0, }
116 }
117
118 pub fn with_parent_map(mut self, parent_map: &'a ParentMap) -> Self {
151 #[cfg(debug_assertions)]
152 {
153 debug_assert!(
156 !parent_map.is_empty(),
157 "DeclarationProvider: empty ParentMap (did you forget to rebuild after AST refresh?)"
158 );
159
160 let root_ptr = &*self.ast as *const _;
162 debug_assert!(
163 !parent_map.contains_key(&root_ptr),
164 "Root node must have no parent in the parent map"
165 );
166
167 Self::debug_assert_no_cycles(parent_map);
169 }
170 self.parent_map = Some(parent_map);
171 self
172 }
173
174 pub fn with_doc_version(mut self, version: i32) -> Self {
199 self.doc_version = version;
200 self
201 }
202
203 #[inline]
208 #[track_caller]
209 fn is_fresh(&self, current_version: i32) -> bool {
210 if self.doc_version != current_version {
211 tracing::warn!(
212 provider_version = self.doc_version,
213 current_version,
214 "DeclarationProvider used after AST refresh — returning empty result"
215 );
216 return false;
217 }
218 true
219 }
220
221 #[cfg(debug_assertions)]
223 fn debug_assert_no_cycles(parent_map: &ParentMap) {
224 let cap = parent_map.len() + 1; for (&child, _) in parent_map.iter() {
228 let mut current = child;
229 let mut depth = 0;
230
231 while depth < cap {
232 if let Some(&parent) = parent_map.get(¤t) {
233 current = parent;
234 depth += 1;
235 } else {
236 break;
238 }
239 }
240
241 if depth >= cap {
243 tracing::warn!(
244 depth_limit = cap,
245 "Cycle detected in ParentMap - node is its own ancestor"
246 );
247 break;
248 }
249 }
250 }
251
252 pub fn build_parent_map(node: &Node, map: &mut ParentMap, parent: Option<*const Node>) {
282 if let Some(p) = parent {
283 map.insert(node as *const _, p);
306 }
307
308 for child in Self::get_children_static(node) {
309 Self::build_parent_map(child, map, Some(node as *const _));
312 }
313 }
314
315 pub fn find_declaration(
317 &self,
318 offset: usize,
319 current_version: i32,
320 ) -> Option<Vec<LocationLink>> {
321 if !self.is_fresh(current_version) {
323 return None;
324 }
325
326 let node = self.find_node_at_offset(&self.ast, offset)?;
328
329 match &node.kind {
331 NodeKind::Variable { name, .. } => self.find_variable_declaration(node, name),
332 NodeKind::FunctionCall { name, .. } => self.find_subroutine_declaration(node, name),
333 NodeKind::MethodCall { method, object, .. } => {
334 self.find_method_declaration(node, method, object)
335 }
336 NodeKind::IndirectCall { method, object, .. } => {
337 self.find_method_declaration(node, method, object)
339 }
340 NodeKind::Identifier { name } => self.find_identifier_declaration(node, name),
341 NodeKind::Goto { target } => {
342 if let NodeKind::Identifier { name } = &target.kind {
343 self.find_label_declaration(node, name)
344 .or_else(|| self.find_subroutine_declaration(node, name))
345 } else {
346 None
347 }
348 }
349 NodeKind::Method { name, .. } => {
353 let mut declarations = Vec::new();
354 self.collect_subroutine_declarations(&self.ast, name, &mut declarations);
355 declarations.first().map(|decl| {
356 vec![self.create_location_link(
357 node,
358 decl,
359 self.get_subroutine_name_range(decl),
360 )]
361 })
362 }
363 NodeKind::String { value, .. } => self.find_modifier_target_declaration(node, value),
366 _ => None,
367 }
368 }
369
370 fn find_variable_declaration(&self, usage: &Node, var_name: &str) -> Option<Vec<LocationLink>> {
372 let mut current_ptr: *const Node = usage as *const _;
378
379 let temp_parent_map;
381 let parent_map = if let Some(pm) = self.parent_map {
382 pm
383 } else {
384 temp_parent_map = {
385 let mut map = FxHashMap::default();
386 Self::build_parent_map(&self.ast, &mut map, None);
387 map
388 };
389 &temp_parent_map
390 };
391 let node_lookup = self.build_node_lookup_map();
392
393 while let Some(&parent_ptr) = parent_map.get(¤t_ptr) {
394 let Some(parent) = node_lookup.get(&parent_ptr).copied() else {
395 break;
396 };
397
398 if matches!(parent.kind, NodeKind::Subroutine { .. } | NodeKind::Method { .. }) {
399 if let Some(links) =
400 self.find_signature_parameter_declaration(parent, usage, var_name)
401 {
402 return Some(links);
403 }
404 }
405
406 for child in self.get_children(parent) {
408 if child.location.start >= usage.location.start {
410 break;
411 }
412
413 if let NodeKind::VariableDeclaration { variable, .. } = &child.kind {
415 if let NodeKind::Variable { name, .. } = &variable.kind {
416 if name == var_name {
417 return Some(vec![LocationLink {
418 origin_selection_range: (usage.location.start, usage.location.end),
419 target_uri: self.document_uri.clone(),
420 target_range: (child.location.start, child.location.end),
421 target_selection_range: (
422 variable.location.start,
423 variable.location.end,
424 ),
425 }]);
426 }
427 }
428 }
429
430 if let NodeKind::VariableListDeclaration { variables, .. } = &child.kind {
432 for var in variables {
433 if let NodeKind::Variable { name, .. } = &var.kind {
434 if name == var_name {
435 return Some(vec![LocationLink {
436 origin_selection_range: (
437 usage.location.start,
438 usage.location.end,
439 ),
440 target_uri: self.document_uri.clone(),
441 target_range: (child.location.start, child.location.end),
442 target_selection_range: (var.location.start, var.location.end),
443 }]);
444 }
445 }
446 }
447 }
448 }
449
450 current_ptr = parent_ptr;
451 }
452
453 None
454 }
455
456 fn find_signature_parameter_declaration(
457 &self,
458 declaration_site: &Node,
459 usage: &Node,
460 var_name: &str,
461 ) -> Option<Vec<LocationLink>> {
462 let signature = match &declaration_site.kind {
463 NodeKind::Subroutine { signature, .. } | NodeKind::Method { signature, .. } => {
464 signature.as_deref()?
465 }
466 _ => return None,
467 };
468
469 let NodeKind::Signature { parameters } = &signature.kind else {
470 return None;
471 };
472
473 for parameter in parameters {
474 let variable = match ¶meter.kind {
475 NodeKind::MandatoryParameter { variable }
476 | NodeKind::OptionalParameter { variable, .. }
477 | NodeKind::SlurpyParameter { variable }
478 | NodeKind::NamedParameter { variable } => variable.as_ref(),
479 _ => continue,
480 };
481
482 let NodeKind::Variable { name, .. } = &variable.kind else {
483 continue;
484 };
485
486 if name == var_name {
487 return Some(vec![self.create_location_link(
488 usage,
489 parameter,
490 (variable.location.start, variable.location.end),
491 )]);
492 }
493 }
494
495 None
496 }
497
498 fn find_subroutine_declaration(
500 &self,
501 node: &Node,
502 func_name: &str,
503 ) -> Option<Vec<LocationLink>> {
504 let (target_package, target_name) = if let Some(pos) = func_name.rfind("::") {
506 let package = &func_name[..pos];
508 let name = &func_name[pos + 2..];
509 (Some(package), name)
510 } else {
511 (self.find_current_package(node), func_name)
513 };
514
515 let mut declarations = Vec::new();
517 self.collect_subroutine_declarations(&self.ast, target_name, &mut declarations);
518
519 if let Some(pkg_name) = target_package {
521 if let Some(decl) =
522 declarations.iter().find(|d| self.find_current_package(d) == Some(pkg_name))
523 {
524 return Some(vec![self.create_location_link(
525 node,
526 decl,
527 self.get_subroutine_name_range(decl),
528 )]);
529 }
530 }
531
532 if let Some(decl) = declarations.first() {
534 return Some(vec![self.create_location_link(
535 node,
536 decl,
537 self.get_subroutine_name_range(decl),
538 )]);
539 }
540
541 None
542 }
543
544 fn find_method_declaration(
546 &self,
547 node: &Node,
548 method_name: &str,
549 object: &Node,
550 ) -> Option<Vec<LocationLink>> {
551 let package_name = match &object.kind {
553 NodeKind::Identifier { name } if name.chars().next()?.is_uppercase() => {
554 Some(name.as_str())
556 }
557 _ => None,
558 };
559
560 if let Some(pkg) = package_name {
561 let mut declarations = Vec::new();
563 self.collect_subroutine_declarations(&self.ast, method_name, &mut declarations);
564
565 if let Some(decl) =
566 declarations.iter().find(|d| self.find_current_package(d) == Some(pkg))
567 {
568 return Some(vec![self.create_location_link(
569 node,
570 decl,
571 self.get_subroutine_name_range(decl),
572 )]);
573 }
574
575 if is_universal_method(method_name)
576 && let Some(decl) =
577 declarations.iter().find(|d| self.find_current_package(d) == Some("UNIVERSAL"))
578 {
579 return Some(vec![self.create_location_link(
580 node,
581 decl,
582 self.get_subroutine_name_range(decl),
583 )]);
584 }
585 }
586
587 self.find_subroutine_declaration(node, method_name)
589 }
590
591 fn find_identifier_declaration(&self, node: &Node, name: &str) -> Option<Vec<LocationLink>> {
593 if self.identifier_is_goto_target(node)
596 && let Some(links) = self.find_label_declaration(node, name)
597 {
598 return Some(links);
599 }
600
601 if let Some(links) = self.find_subroutine_declaration(node, name) {
603 return Some(links);
604 }
605
606 let packages = self.find_package_declarations(&self.ast, name);
608 if let Some(pkg) = packages.first() {
609 return Some(vec![self.create_location_link(
610 node,
611 pkg,
612 self.get_package_name_range(pkg),
613 )]);
614 }
615
616 let constants = self.find_constant_declarations(&self.ast, name);
618 if let Some(const_decl) = constants.first() {
619 return Some(vec![self.create_location_link(
620 node,
621 const_decl,
622 self.get_constant_name_range_for(const_decl, name),
623 )]);
624 }
625
626 None
627 }
628
629 fn find_label_declaration(&self, origin: &Node, label_name: &str) -> Option<Vec<LocationLink>> {
630 let mut labels = Vec::new();
631 self.collect_label_declarations(&self.ast, label_name, &mut labels);
632 let labeled_stmt = labels.first().copied()?;
633
634 Some(vec![self.create_location_link(
635 origin,
636 labeled_stmt,
637 self.get_labeled_statement_label_range(labeled_stmt),
638 )])
639 }
640
641 fn collect_label_declarations<'b>(
642 &'b self,
643 node: &'b Node,
644 label_name: &str,
645 labels: &mut Vec<&'b Node>,
646 ) {
647 if let NodeKind::LabeledStatement { label, .. } = &node.kind
648 && label == label_name
649 {
650 labels.push(node);
651 }
652
653 for child in self.get_children(node) {
654 self.collect_label_declarations(child, label_name, labels);
655 }
656 }
657
658 fn get_labeled_statement_label_range(&self, node: &Node) -> (usize, usize) {
659 let NodeKind::LabeledStatement { label, .. } = &node.kind else {
660 return (node.location.start, node.location.end);
661 };
662
663 let start = node.location.start;
664 let end = node.location.end.min(self.content.len());
665 if start >= end {
666 return (node.location.start, node.location.end);
667 }
668
669 let text = &self.content[start..end];
670 let label_start = text.find(label).map_or(start, |idx| start + idx);
671 let label_end = label_start.saturating_add(label.len()).min(end);
672 (label_start, label_end)
673 }
674
675 fn identifier_is_goto_target(&self, node: &Node) -> bool {
676 let temp_parent_map;
677 let parent_map = if let Some(pm) = self.parent_map {
678 pm
679 } else {
680 temp_parent_map = {
681 let mut map = FxHashMap::default();
682 Self::build_parent_map(&self.ast, &mut map, None);
683 map
684 };
685 &temp_parent_map
686 };
687 let node_lookup = self.build_node_lookup_map();
688
689 let node_ptr = node as *const _;
690 let Some(parent_ptr) = parent_map.get(&node_ptr).copied() else {
691 return false;
692 };
693 let Some(parent) = node_lookup.get(&parent_ptr).copied() else {
694 return false;
695 };
696
697 match &parent.kind {
698 NodeKind::Goto { target } => std::ptr::eq(target.as_ref(), node),
699 _ => false,
700 }
701 }
702
703 fn find_modifier_target_declaration(
710 &self,
711 string_node: &Node,
712 method_name: &str,
713 ) -> Option<Vec<LocationLink>> {
714 let bare_name = method_name.trim().trim_matches('\'').trim_matches('"').trim();
716 if bare_name.is_empty() {
717 return None;
718 }
719
720 let temp_parent_map;
722 let parent_map = if let Some(pm) = self.parent_map {
723 pm
724 } else {
725 temp_parent_map = {
726 let mut map = FxHashMap::default();
727 Self::build_parent_map(&self.ast, &mut map, None);
728 map
729 };
730 &temp_parent_map
731 };
732 let node_lookup = self.build_node_lookup_map();
733
734 let string_ptr: *const Node = string_node as *const _;
738 let parent_ptr = parent_map.get(&string_ptr).copied()?;
739 let parent = node_lookup.get(&parent_ptr).copied()?;
740
741 if let NodeKind::FunctionCall { name, args } = &parent.kind {
743 if matches!(name.as_str(), "before" | "after" | "around" | "override") {
744 if args.first().map(|a| std::ptr::eq(a, string_node)).unwrap_or(false) {
745 return self.find_subroutine_declaration(string_node, bare_name);
746 }
747 }
748 }
749
750 let grandparent_ptr = parent_map.get(&parent_ptr).copied()?;
753 let grandparent = node_lookup.get(&grandparent_ptr).copied()?;
754
755 if let NodeKind::FunctionCall { name, args } = &grandparent.kind {
756 if matches!(name.as_str(), "before" | "after" | "around" | "override") {
757 if args.first().map(|a| std::ptr::eq(a, string_node)).unwrap_or(false) {
758 return self.find_subroutine_declaration(string_node, bare_name);
759 }
760 }
761 }
762
763 None
764 }
765
766 fn find_current_package<'b>(&'b self, node: &Node) -> Option<&'b str> {
768 let mut current_ptr: *const Node = node as *const _;
774
775 let temp_parent_map;
777 let parent_map = if let Some(pm) = self.parent_map {
778 pm
779 } else {
780 temp_parent_map = {
781 let mut map = FxHashMap::default();
782 Self::build_parent_map(&self.ast, &mut map, None);
783 map
784 };
785 &temp_parent_map
786 };
787 let node_lookup = self.build_node_lookup_map();
788
789 while let Some(&parent_ptr) = parent_map.get(¤t_ptr) {
790 let Some(parent) = node_lookup.get(&parent_ptr).copied() else {
791 break;
792 };
793
794 for child in self.get_children(parent) {
796 if child.location.start >= node.location.start {
797 break;
798 }
799
800 if let NodeKind::Package { name, .. } = &child.kind {
801 return Some(name.as_str());
802 }
803 }
804
805 current_ptr = parent_ptr;
806 }
807
808 None
809 }
810
811 fn create_location_link(
813 &self,
814 origin: &Node,
815 target: &Node,
816 name_range: (usize, usize),
817 ) -> LocationLink {
818 LocationLink {
819 origin_selection_range: (origin.location.start, origin.location.end),
820 target_uri: self.document_uri.clone(),
821 target_range: (target.location.start, target.location.end),
822 target_selection_range: name_range,
823 }
824 }
825
826 fn find_node_at_offset<'b>(&'b self, node: &'b Node, offset: usize) -> Option<&'b Node> {
829 if offset >= node.location.start && offset <= node.location.end {
830 for child in self.get_children(node) {
832 if let Some(found) = self.find_node_at_offset(child, offset) {
833 return Some(found);
834 }
835 }
836 return Some(node);
837 }
838 None
839 }
840
841 fn collect_subroutine_declarations<'b>(
842 &'b self,
843 node: &'b Node,
844 sub_name: &str,
845 subs: &mut Vec<&'b Node>,
846 ) {
847 match &node.kind {
848 NodeKind::Subroutine { name: Some(name_str), .. } if name_str == sub_name => {
849 subs.push(node);
850 }
851 NodeKind::Method { name: method_name, .. } if method_name == sub_name => {
854 subs.push(node);
855 }
856 _ => {}
857 }
858
859 for child in self.get_children(node) {
860 self.collect_subroutine_declarations(child, sub_name, subs);
861 }
862 }
863
864 fn find_package_declarations<'b>(&'b self, node: &'b Node, pkg_name: &str) -> Vec<&'b Node> {
865 let mut packages = Vec::new();
866 self.collect_package_declarations(node, pkg_name, &mut packages);
867 packages
868 }
869
870 fn collect_package_declarations<'b>(
871 &'b self,
872 node: &'b Node,
873 pkg_name: &str,
874 packages: &mut Vec<&'b Node>,
875 ) {
876 match &node.kind {
877 NodeKind::Package { name, .. } | NodeKind::Class { name, .. } if name == pkg_name => {
878 packages.push(node);
879 }
880 _ => {}
881 }
882
883 for child in self.get_children(node) {
884 self.collect_package_declarations(child, pkg_name, packages);
885 }
886 }
887
888 fn find_constant_declarations<'b>(&'b self, node: &'b Node, const_name: &str) -> Vec<&'b Node> {
889 let mut constants = Vec::new();
890 self.collect_constant_declarations(node, const_name, &mut constants);
891 constants
892 }
893
894 fn strip_constant_options<'b>(&self, args: &'b [String]) -> &'b [String] {
896 let mut i = 0;
897 while i < args.len() && args[i].starts_with('-') {
898 i += 1;
899 }
900 if i < args.len() && args[i] == "," {
902 i += 1;
903 }
904 &args[i..]
905 }
906
907 fn collect_constant_declarations<'b>(
908 &'b self,
909 node: &'b Node,
910 const_name: &str,
911 constants: &mut Vec<&'b Node>,
912 ) {
913 if let NodeKind::Use { module, args, .. } = &node.kind {
914 if module == "constant" {
915 let stripped_args = self.strip_constant_options(args);
917
918 if stripped_args.first().map(|s| s.as_str()) == Some(const_name) {
920 constants.push(node);
921 }
923
924 let args_text = stripped_args.join(" ");
926
927 if self.contains_name_in_hash(&args_text, const_name) {
929 constants.push(node);
930 }
931
932 if self.contains_name_in_qw(&args_text, const_name) {
934 constants.push(node);
935 }
936 }
937 }
938
939 for child in self.get_children(node) {
940 self.collect_constant_declarations(child, const_name, constants);
941 }
942 }
943
944 #[inline]
946 fn is_ident_ascii(b: u8) -> bool {
947 matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_')
948 }
949
950 fn for_each_qw_window<F>(&self, s: &str, mut f: F) -> bool
953 where
954 F: FnMut(usize, usize) -> bool,
955 {
956 let b = s.as_bytes();
957 let mut i = 0;
958 while i + 1 < b.len() {
959 if b[i] == b'q' && b[i + 1] == b'w' {
961 let mut j = i + 2;
962
963 while j < b.len() && (b[j] as char).is_ascii_whitespace() {
965 j += 1;
966 }
967 if j >= b.len() {
968 break;
969 }
970
971 let open = b[j] as char;
972
973 if open.is_ascii_alphanumeric() || open == '_' {
976 i += 1;
977 continue;
978 }
979
980 let close = match open {
982 '(' => ')',
983 '[' => ']',
984 '{' => '}',
985 '<' => '>',
986 _ => open, };
988
989 j += 1;
991 let start = j;
992 while j < b.len() && (b[j] as char) != close {
993 j += 1;
994 }
995 if j <= b.len() {
996 if f(start, j) {
998 return true;
999 }
1000 i = j + 1;
1002 continue;
1003 } else {
1004 break;
1006 }
1007 }
1008
1009 i += 1;
1010 }
1011 false
1012 }
1013
1014 fn for_each_brace_window<F>(&self, s: &str, mut f: F) -> bool
1016 where
1017 F: FnMut(usize, usize) -> bool,
1018 {
1019 let b = s.as_bytes();
1020 let mut i = 0;
1021 while i < b.len() {
1022 if b[i] == b'{' {
1023 let start = i + 1;
1024 let mut nesting = 1;
1025 let mut j = i + 1;
1026 while j < b.len() {
1027 match b[j] {
1028 b'{' => nesting += 1,
1029 b'}' => {
1030 nesting -= 1;
1031 if nesting == 0 {
1032 break;
1033 }
1034 }
1035 _ => {}
1036 }
1037 j += 1;
1038 }
1039
1040 if nesting == 0 {
1041 if f(start, j) {
1043 return true;
1044 }
1045 i = j + 1;
1046 continue;
1047 }
1048 }
1049 i += 1;
1050 }
1051 false
1052 }
1053
1054 fn contains_name_in_hash(&self, s: &str, name: &str) -> bool {
1055 self.for_each_brace_window(s, |start, end| {
1057 self.find_word(&s[start..end], name).is_some()
1059 })
1060 }
1061
1062 fn contains_name_in_qw(&self, s: &str, name: &str) -> bool {
1063 self.for_each_qw_window(s, |start, end| {
1065 s[start..end].split_whitespace().any(|tok| tok == name)
1067 })
1068 }
1069
1070 fn find_word(&self, hay: &str, needle: &str) -> Option<(usize, usize)> {
1071 if needle.is_empty() {
1072 return None;
1073 }
1074 let mut find_from = 0;
1075 while let Some(hit) = hay[find_from..].find(needle) {
1076 let start = find_from + hit;
1077 let end = start + needle.len();
1078 let left_ok = start == 0 || !Self::is_ident_ascii(hay.as_bytes()[start - 1]);
1079 let right_ok = end == hay.len()
1080 || !Self::is_ident_ascii(*hay.as_bytes().get(end).unwrap_or(&b' '));
1081 if left_ok && right_ok {
1082 return Some((start, end));
1083 }
1084 find_from = end;
1085 }
1086 None
1087 }
1088
1089 fn first_all_caps_word(&self, s: &str) -> Option<(usize, usize)> {
1090 let bytes = s.as_bytes();
1092 let mut i = 0;
1093 while i < bytes.len() {
1094 while i < bytes.len() && !Self::is_ident_ascii(bytes[i]) {
1095 i += 1;
1096 }
1097 let start = i;
1098 while i < bytes.len() && Self::is_ident_ascii(bytes[i]) {
1099 i += 1;
1100 }
1101 if start < i {
1102 let w = &s[start..i];
1103 if w.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_') {
1104 return Some((start, i));
1105 }
1106 }
1107 }
1108 None
1109 }
1110
1111 fn get_subroutine_name_range(&self, decl: &Node) -> (usize, usize) {
1112 if let NodeKind::Subroutine { name_span: Some(loc), .. } = &decl.kind {
1113 (loc.start, loc.end)
1114 } else {
1115 (decl.location.start, decl.location.end)
1116 }
1117 }
1118
1119 fn get_package_name_range(&self, decl: &Node) -> (usize, usize) {
1120 if let NodeKind::Package { name_span, .. } = &decl.kind {
1121 (name_span.start, name_span.end)
1122 } else {
1123 (decl.location.start, decl.location.end)
1124 }
1125 }
1126
1127 fn get_constant_name_range(&self, decl: &Node) -> (usize, usize) {
1128 let text = self.get_node_text(decl);
1129
1130 if let NodeKind::Use { args, .. } = &decl.kind {
1132 let best_guess = args.first().map(|s| s.as_str()).unwrap_or("");
1133 if let Some((lo, hi)) = self.find_word(&text, best_guess) {
1134 let abs_lo = decl.location.start + lo;
1135 let abs_hi = decl.location.start + hi;
1136 return (abs_lo, abs_hi);
1137 }
1138 }
1139
1140 if let Some((lo, hi)) = self.first_all_caps_word(&text) {
1142 return (decl.location.start + lo, decl.location.start + hi);
1143 }
1144
1145 (decl.location.start, decl.location.end)
1147 }
1148
1149 fn get_constant_name_range_for(&self, decl: &Node, name: &str) -> (usize, usize) {
1150 let text = self.get_node_text(decl);
1151
1152 if let Some((lo, hi)) = self.find_word(&text, name) {
1154 return (decl.location.start + lo, decl.location.start + hi);
1155 }
1156
1157 let mut found_range = None;
1159 self.for_each_qw_window(&text, |start, end| {
1160 if let Some((lo, hi)) = self.find_word(&text[start..end], name) {
1162 found_range =
1163 Some((decl.location.start + start + lo, decl.location.start + start + hi));
1164 true } else {
1166 false }
1168 });
1169 if let Some(range) = found_range {
1170 return range;
1171 }
1172
1173 self.for_each_brace_window(&text, |start, end| {
1175 if let Some((lo, hi)) = self.find_word(&text[start..end], name) {
1176 found_range =
1177 Some((decl.location.start + start + lo, decl.location.start + start + hi));
1178 true } else {
1180 false }
1182 });
1183 if let Some(range) = found_range {
1184 return range;
1185 }
1186
1187 self.get_constant_name_range(decl)
1189 }
1190
1191 fn get_children<'b>(&self, node: &'b Node) -> Vec<&'b Node> {
1192 Self::get_children_static(node)
1193 }
1194
1195 fn build_node_lookup_map(&self) -> FxHashMap<*const Node, &Node> {
1202 let mut map = FxHashMap::default();
1203 Self::build_node_lookup(self.ast.as_ref(), &mut map);
1204 map
1205 }
1206
1207 fn build_node_lookup<'b>(node: &'b Node, map: &mut FxHashMap<*const Node, &'b Node>) {
1208 map.insert(node as *const Node, node);
1214 for child in Self::get_children_static(node) {
1215 Self::build_node_lookup(child, map);
1216 }
1217 }
1218
1219 fn get_children_static(node: &Node) -> Vec<&Node> {
1220 match &node.kind {
1221 NodeKind::Program { statements } => statements.iter().collect(),
1222 NodeKind::Block { statements } => statements.iter().collect(),
1223 NodeKind::If { condition, then_branch, else_branch, .. } => {
1224 let mut children = vec![condition.as_ref(), then_branch.as_ref()];
1225 if let Some(else_b) = else_branch {
1226 children.push(else_b.as_ref());
1227 }
1228 children
1229 }
1230 NodeKind::Binary { left, right, .. } => vec![left.as_ref(), right.as_ref()],
1231 NodeKind::Unary { operand, .. } => vec![operand.as_ref()],
1232 NodeKind::Return { value } => {
1233 if let Some(value) = value {
1234 vec![value.as_ref()]
1235 } else {
1236 vec![]
1237 }
1238 }
1239 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1240 let mut children = vec![variable.as_ref()];
1241 if let Some(init) = initializer {
1242 children.push(init.as_ref());
1243 }
1244 children
1245 }
1246 NodeKind::Method { signature, body, .. } => {
1247 let mut children = vec![body.as_ref()];
1248 if let Some(sig) = signature {
1249 children.push(sig.as_ref());
1250 }
1251 children
1252 }
1253 NodeKind::Subroutine { signature, body, .. } => {
1254 let mut children = vec![body.as_ref()];
1255 if let Some(sig) = signature {
1256 children.push(sig.as_ref());
1257 }
1258 children
1259 }
1260 NodeKind::FunctionCall { args, .. } => args.iter().collect(),
1261 NodeKind::MethodCall { object, args, .. } => {
1262 let mut children = vec![object.as_ref()];
1263 children.extend(args.iter());
1264 children
1265 }
1266 NodeKind::IndirectCall { object, args, .. } => {
1267 let mut children = vec![object.as_ref()];
1268 children.extend(args.iter());
1269 children
1270 }
1271 NodeKind::While { condition, body, .. } => {
1272 vec![condition.as_ref(), body.as_ref()]
1273 }
1274 NodeKind::For { init, condition, update, body, .. } => {
1275 let mut children = Vec::new();
1276 if let Some(i) = init {
1277 children.push(i.as_ref());
1278 }
1279 if let Some(c) = condition {
1280 children.push(c.as_ref());
1281 }
1282 if let Some(u) = update {
1283 children.push(u.as_ref());
1284 }
1285 children.push(body.as_ref());
1286 children
1287 }
1288 NodeKind::Foreach { variable, list, body, .. } => {
1289 vec![variable.as_ref(), list.as_ref(), body.as_ref()]
1290 }
1291 NodeKind::ExpressionStatement { expression } => vec![expression.as_ref()],
1292 NodeKind::Class { body, .. } => vec![body.as_ref()],
1294 NodeKind::Package { block: Some(block), .. } => vec![block.as_ref()],
1296 _ => vec![],
1297 }
1298 }
1299
1300 pub fn get_node_text(&self, node: &Node) -> String {
1328 self.content[node.location.start..node.location.end].to_string()
1329 }
1330}
1331
1332fn symbol_at_cursor_internal(
1371 ast: &Node,
1372 offset: usize,
1373 current_pkg: &str,
1374 source_text: &str,
1375) -> Option<SymbolKey> {
1376 fn collect_node_path_at_offset<'a>(
1377 node: &'a Node,
1378 offset: usize,
1379 path: &mut Vec<&'a Node>,
1380 ) -> bool {
1381 if offset < node.location.start || offset > node.location.end {
1382 return false;
1383 }
1384
1385 path.push(node);
1386
1387 for child in get_node_children(node) {
1388 if collect_node_path_at_offset(child, offset, path) {
1389 return true;
1390 }
1391 }
1392
1393 true
1394 }
1395
1396 fn find_symbol_node_at_offset(ast: &Node, offset: usize) -> Option<(Vec<&Node>, &Node)> {
1397 let mut path = Vec::new();
1398 if !collect_node_path_at_offset(ast, offset, &mut path) {
1399 return None;
1400 }
1401
1402 let node = path
1403 .iter()
1404 .rev()
1405 .copied()
1406 .find(|node| {
1407 matches!(
1408 node.kind,
1409 NodeKind::Variable { .. }
1410 | NodeKind::FunctionCall { .. }
1411 | NodeKind::Subroutine { .. }
1412 | NodeKind::Method { .. }
1413 | NodeKind::MethodCall { .. }
1414 | NodeKind::Use { .. }
1415 )
1416 })
1417 .or_else(|| path.last().copied())?;
1418
1419 Some((path, node))
1420 }
1421
1422 fn node_variable_name(node: &Node) -> Option<&str> {
1423 if let NodeKind::Variable { name, .. } = &node.kind { Some(name.as_str()) } else { None }
1424 }
1425
1426 fn normalize_symbol_name(raw: &str) -> Option<String> {
1427 let trimmed = raw.trim().trim_matches('\'').trim_matches('"').trim();
1428 if trimmed.is_empty() { None } else { Some(trimmed.to_string()) }
1429 }
1430
1431 fn token_at_offset_in_text(text: &str, rel_offset: usize) -> Option<String> {
1432 let bytes = text.as_bytes();
1433 if rel_offset >= bytes.len() {
1434 return None;
1435 }
1436 let is_ident = |b: u8| matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b':');
1437 if !is_ident(bytes[rel_offset]) {
1438 return None;
1439 }
1440
1441 let mut start = rel_offset;
1442 while start > 0 && is_ident(bytes[start - 1]) {
1443 start -= 1;
1444 }
1445 let mut end = rel_offset + 1;
1446 while end < bytes.len() && is_ident(bytes[end]) {
1447 end += 1;
1448 }
1449 Some(text[start..end].to_string())
1450 }
1451
1452 fn export_tag_members(module: &str, tag: &str) -> &'static [&'static str] {
1453 match (module, tag) {
1454 ("POSIX", ":sys_wait_h") => {
1456 &["WEXITSTATUS", "WIFEXITED", "WIFSIGNALED", "WIFSTOPPED", "WTERMSIG"]
1457 }
1458 ("POSIX", ":fcntl_h") => &["F_GETFD", "F_SETFD", "F_GETFL", "F_SETFL", "FD_CLOEXEC"],
1459 ("POSIX", ":termios_h") => {
1460 &["B9600", "B19200", "B38400", "TCSANOW", "TCSADRAIN", "TCSAFLUSH"]
1461 }
1462 ("File::Find", ":find") => &["find", "finddepth"],
1464 ("Fcntl", ":seek") => &["SEEK_SET", "SEEK_CUR", "SEEK_END"],
1466 ("Fcntl", ":lock") => &["LOCK_SH", "LOCK_EX", "LOCK_NB", "LOCK_UN"],
1467 ("Encode", ":fallback") => &[
1469 "FB_DEFAULT",
1470 "FB_CROAK",
1471 "FB_QUIET",
1472 "FB_WARN",
1473 "FB_PERLQQ",
1474 "FB_HTMLCREF",
1475 "FB_XMLCREF",
1476 ],
1477 _ => &[],
1478 }
1479 }
1480
1481 fn tag_imports_symbol(module: &str, import_token: &str, symbol_name: &str) -> bool {
1482 if !import_token.starts_with(':') {
1483 return false;
1484 }
1485 export_tag_members(module, import_token).contains(&symbol_name)
1486 }
1487
1488 const NON_IMPORT_PRAGMAS: &[&str] = &[
1493 "constant", "parent", "base", "vars", "Exporter", "mro", "if", "lib", "feature", "utf8", ];
1504
1505 fn use_args_import_symbol(module: &str, args: &[String], symbol_name: &str) -> bool {
1506 args.iter().any(|arg| {
1507 if arg == symbol_name || tag_imports_symbol(module, arg, symbol_name) {
1508 return true;
1509 }
1510
1511 if arg.starts_with("qw") {
1512 let content = arg
1513 .trim_start_matches("qw")
1514 .trim_start_matches(|c: char| "([{/<|!".contains(c))
1515 .trim_end_matches(|c: char| ")]}/|!>".contains(c));
1516 return content
1517 .split_whitespace()
1518 .any(|tok| tok == symbol_name || tag_imports_symbol(module, tok, symbol_name));
1519 }
1520
1521 let bare = arg.trim().trim_matches('\'').trim_matches('"').trim();
1522 bare == symbol_name || tag_imports_symbol(module, bare, symbol_name)
1523 })
1524 }
1525
1526 fn find_import_source(ast: &Node, symbol_name: &str) -> Option<String> {
1527 fn require_module_name(node: &Node) -> Option<String> {
1533 let args = match &node.kind {
1534 NodeKind::FunctionCall { name, args } if name == "require" => args,
1535 _ => return None,
1536 };
1537 let arg = args.first()?;
1538 match &arg.kind {
1539 NodeKind::Identifier { name } => Some(name.clone()),
1540 NodeKind::String { value, .. } => {
1541 let cleaned = value.trim_matches('\'').trim_matches('"').trim();
1543 let module = cleaned.trim_end_matches(".pm").replace('/', "::");
1544 Some(module)
1545 }
1546 _ => None,
1547 }
1548 }
1549
1550 fn import_call_exports(
1558 method_node: &Node,
1559 expected_module: &str,
1560 symbol: &str,
1561 aliases: &std::collections::HashMap<String, String>,
1562 ) -> bool {
1563 let (object, method, args) = match &method_node.kind {
1564 NodeKind::MethodCall { object, method, args } => (object, method, args),
1565 _ => return false,
1566 };
1567 if method != "import" {
1568 return false;
1569 }
1570 let obj_name = match &object.kind {
1572 NodeKind::Identifier { name } => Some(name.as_str()),
1573 NodeKind::Variable { name, .. } => aliases.get(name).map(String::as_str),
1574 _ => return false,
1575 };
1576 let Some(obj_name) = obj_name else {
1577 return false;
1578 };
1579 if obj_name != expected_module {
1580 return false;
1581 }
1582 if args.is_empty() {
1583 return false;
1588 }
1589 for arg in args {
1591 if arg_node_matches_symbol(arg, expected_module, symbol) {
1592 return true;
1593 }
1594 }
1595 false
1596 }
1597
1598 fn arg_node_matches_symbol(arg: &Node, module: &str, symbol: &str) -> bool {
1603 match &arg.kind {
1604 NodeKind::String { value, .. } => {
1605 let bare = value.trim_matches('\'').trim_matches('"');
1608 bare == symbol || tag_imports_symbol(module, bare, symbol)
1609 }
1610 NodeKind::Identifier { name } => {
1611 if name == symbol {
1612 return true;
1613 }
1614 if name.starts_with("qw") {
1617 let content = name
1618 .trim_start_matches("qw")
1619 .trim_start_matches(|c: char| "([{/<|!".contains(c))
1620 .trim_end_matches(|c: char| ")]}/|!>".contains(c));
1621 return content
1622 .split_whitespace()
1623 .any(|tok| tok == symbol || tag_imports_symbol(module, tok, symbol));
1624 }
1625 false
1626 }
1627 NodeKind::ArrayLiteral { elements } => {
1628 elements.iter().any(|el| arg_node_matches_symbol(el, module, symbol))
1630 }
1631 _ => false,
1632 }
1633 }
1634
1635 fn module_runtime_alias(expr: &Node) -> Option<(String, String)> {
1636 let (alias_name, call_node) = match &expr.kind {
1637 NodeKind::Assignment { lhs, rhs, op } if op == "=" => {
1638 let NodeKind::Variable { name, .. } = &lhs.kind else {
1639 return None;
1640 };
1641 (name.as_str(), rhs.as_ref())
1642 }
1643 NodeKind::VariableDeclaration { variable, initializer: Some(rhs), .. } => {
1644 let NodeKind::Variable { name, .. } = &variable.kind else {
1645 return None;
1646 };
1647 (name.as_str(), rhs.as_ref())
1648 }
1649 _ => return None,
1650 };
1651
1652 let NodeKind::FunctionCall { name, args } = &call_node.kind else {
1653 return None;
1654 };
1655 if !matches!(
1656 name.as_str(),
1657 "use_module"
1658 | "require_module"
1659 | "Module::Runtime::use_module"
1660 | "Module::Runtime::require_module"
1661 ) {
1662 return None;
1663 }
1664 let first = args.first()?;
1665 let NodeKind::String { value, .. } = &first.kind else {
1666 return None;
1667 };
1668 let module = value.trim_matches('\'').trim_matches('"').trim();
1669 if module.is_empty() {
1670 return None;
1671 }
1672 Some((alias_name.to_string(), module.to_string()))
1673 }
1674
1675 fn inner_expr(node: &Node) -> &Node {
1679 if let NodeKind::ExpressionStatement { expression } = &node.kind {
1680 expression.as_ref()
1681 } else {
1682 node
1683 }
1684 }
1685
1686 fn scan_statements_for_require_import(stmts: &[Node], symbol: &str) -> Option<String> {
1691 let mut required_modules: Vec<String> =
1693 stmts.iter().filter_map(|s| require_module_name(inner_expr(s))).collect();
1694 let mut aliases: std::collections::HashMap<String, String> =
1695 std::collections::HashMap::new();
1696 for stmt in stmts {
1697 if let Some((alias, module)) = module_runtime_alias(inner_expr(stmt)) {
1698 aliases.insert(alias, module.clone());
1699 if !required_modules.contains(&module) {
1700 required_modules.push(module);
1701 }
1702 }
1703 }
1704
1705 if required_modules.is_empty() {
1706 return None;
1707 }
1708
1709 for stmt in stmts {
1712 let expr = inner_expr(stmt);
1713 for module in &required_modules {
1714 if import_call_exports(expr, module, symbol, &aliases) {
1715 return Some(module.clone());
1716 }
1717 }
1718 }
1719 None
1720 }
1721
1722 fn find(node: &Node, name: &str) -> Option<String> {
1723 if let NodeKind::Use { module, args, .. } = &node.kind {
1724 if NON_IMPORT_PRAGMAS.contains(&module.as_str()) {
1726 } else {
1728 for arg in args {
1729 if arg == name {
1730 return Some(module.clone());
1731 }
1732 if tag_imports_symbol(module, arg, name) {
1733 return Some(module.clone());
1734 }
1735 if arg.starts_with("qw") {
1736 let content = arg
1737 .trim_start_matches("qw")
1738 .trim_start_matches(|c: char| "([{/<|!".contains(c))
1739 .trim_end_matches(|c: char| ")]}/|!>".contains(c));
1740 for import_token in content.split_whitespace() {
1741 if import_token == name
1742 || tag_imports_symbol(module, import_token, name)
1743 {
1744 return Some(module.clone());
1745 }
1746 }
1747 } else {
1748 let bare = arg.trim().trim_matches('\'').trim_matches('"').trim();
1752 if bare == name {
1753 return Some(module.clone());
1754 }
1755 if tag_imports_symbol(module, bare, name) {
1756 return Some(module.clone());
1757 }
1758 }
1759 }
1760 }
1761 }
1762
1763 let stmts = match &node.kind {
1765 NodeKind::Program { statements } => Some(statements.as_slice()),
1766 NodeKind::Block { statements } => Some(statements.as_slice()),
1767 _ => None,
1768 };
1769 if let Some(statements) = stmts {
1770 if let Some(module) = scan_statements_for_require_import(statements, name) {
1771 return Some(module);
1772 }
1773 }
1774
1775 for child in get_node_children(node) {
1776 if let Some(module) = find(child, name) {
1777 return Some(module);
1778 }
1779 }
1780
1781 None
1782 }
1783
1784 find(ast, symbol_name)
1785 }
1786
1787 fn plack_builder_middleware_symbol(path: &[&Node], offset: usize) -> Option<SymbolKey> {
1788 let has_builder = path.iter().any(|ancestor| {
1789 matches!(ancestor.kind, NodeKind::FunctionCall { ref name, .. } if name == "builder")
1790 });
1791 if !has_builder {
1792 return None;
1793 }
1794
1795 let block = path.iter().rev().find_map(|ancestor| {
1796 if let NodeKind::Block { statements } = &ancestor.kind {
1797 Some(statements)
1798 } else {
1799 None
1800 }
1801 })?;
1802
1803 for statement in block {
1804 let NodeKind::ExpressionStatement { expression } = &statement.kind else {
1805 continue;
1806 };
1807 let NodeKind::FunctionCall { name, args } = &expression.kind else {
1808 continue;
1809 };
1810 if name != "enable" {
1811 continue;
1812 }
1813
1814 let Some(first) = args.first() else {
1815 continue;
1816 };
1817 if offset < first.location.start || offset > first.location.end {
1818 continue;
1819 }
1820
1821 let raw_name = match &first.kind {
1822 NodeKind::String { value, .. } => normalize_symbol_name(value)?,
1823 NodeKind::Identifier { name } => name.clone(),
1824 _ => continue,
1825 };
1826
1827 let middleware_name = if raw_name.contains("::") {
1828 raw_name
1829 } else {
1830 format!("Plack::Middleware::{raw_name}")
1831 };
1832
1833 return Some(SymbolKey {
1834 pkg: middleware_name.clone().into(),
1835 name: middleware_name.into(),
1836 sigil: None,
1837 kind: SymKind::Pack,
1838 });
1839 }
1840
1841 None
1842 }
1843
1844 fn looks_like_package_name(name: &str) -> bool {
1845 name.contains("::") || name.chars().next().is_some_and(|ch| ch.is_ascii_uppercase())
1846 }
1847
1848 fn infer_receiver_package(
1849 object: &Node,
1850 current_pkg: &str,
1851 receiver_packages: &std::collections::HashMap<String, String>,
1852 ) -> Option<String> {
1853 if let NodeKind::Identifier { name } = &object.kind {
1854 return Some(name.clone());
1855 }
1856
1857 if let Some(name) = node_variable_name(object) {
1858 if let Some(package_name) = receiver_packages.get(name) {
1859 return Some(package_name.clone());
1860 }
1861
1862 if matches!(name, "self" | "this" | "class") {
1863 return Some(current_pkg.to_string());
1864 }
1865
1866 if looks_like_package_name(name) {
1867 return Some(name.to_string());
1868 }
1869 }
1870
1871 None
1872 }
1873
1874 fn infer_constructor_package(
1875 rhs: &Node,
1876 current_pkg: &str,
1877 receiver_packages: &std::collections::HashMap<String, String>,
1878 ) -> Option<String> {
1879 match &rhs.kind {
1880 NodeKind::MethodCall { method, object, .. } if method == "new" => {
1881 infer_receiver_package(object, current_pkg, receiver_packages)
1882 }
1883 NodeKind::FunctionCall { name, .. } => {
1884 name.rsplit_once("::").map(|(package_name, _)| package_name.to_string())
1885 }
1886 _ => None,
1887 }
1888 }
1889
1890 fn record_receiver_assignment(
1891 node: &Node,
1892 offset: usize,
1893 current_pkg: &str,
1894 receiver_packages: &mut std::collections::HashMap<String, String>,
1895 ) {
1896 if node.location.start > offset {
1897 return;
1898 }
1899
1900 if node.location.end <= offset {
1901 match &node.kind {
1902 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1903 if let (Some(variable_name), Some(initializer)) =
1904 (node_variable_name(variable), initializer.as_ref())
1905 {
1906 if let Some(package_name) =
1907 infer_constructor_package(initializer, current_pkg, receiver_packages)
1908 {
1909 receiver_packages.insert(variable_name.to_string(), package_name);
1910 }
1911 }
1912 }
1913 NodeKind::Assignment { lhs, rhs, .. } => {
1914 if let Some(variable_name) = node_variable_name(lhs) {
1915 if let Some(package_name) =
1916 infer_constructor_package(rhs, current_pkg, receiver_packages)
1917 {
1918 receiver_packages.insert(variable_name.to_string(), package_name);
1919 }
1920 }
1921 }
1922 _ => {}
1923 }
1924 }
1925
1926 for child in get_node_children(node) {
1927 if child.location.start <= offset {
1928 record_receiver_assignment(child, offset, current_pkg, receiver_packages);
1929 }
1930 }
1931 }
1932
1933 let (path, node) = find_symbol_node_at_offset(ast, offset)?;
1934
1935 if let Some(symbol_key) = plack_builder_middleware_symbol(&path, offset) {
1936 return Some(symbol_key);
1937 }
1938
1939 match &node.kind {
1940 NodeKind::Variable { sigil, name } => {
1941 let sigil_char = sigil.chars().next();
1943 Some(SymbolKey {
1944 pkg: current_pkg.into(),
1945 name: name.clone().into(),
1946 sigil: sigil_char,
1947 kind: SymKind::Var,
1948 })
1949 }
1950 NodeKind::FunctionCall { name, .. } => {
1951 let (pkg, bare) = if let Some(idx) = name.rfind("::") {
1952 (name[..idx].to_string(), name[idx + 2..].to_string())
1953 } else {
1954 (
1955 find_import_source(ast, name).unwrap_or_else(|| current_pkg.to_string()),
1956 name.clone(),
1957 )
1958 };
1959 Some(SymbolKey { pkg: pkg.into(), name: bare.into(), sigil: None, kind: SymKind::Sub })
1960 }
1961 NodeKind::Subroutine { name: Some(name), .. } => {
1962 let (pkg, bare) = if let Some(idx) = name.rfind("::") {
1963 (&name[..idx], &name[idx + 2..])
1964 } else {
1965 (current_pkg, name.as_str())
1966 };
1967 Some(SymbolKey { pkg: pkg.into(), name: bare.into(), sigil: None, kind: SymKind::Sub })
1968 }
1969 NodeKind::Method { name, .. } => {
1972 let (pkg, bare) = if let Some(idx) = name.rfind("::") {
1973 (&name[..idx], &name[idx + 2..])
1974 } else {
1975 (current_pkg, name.as_str())
1976 };
1977 Some(SymbolKey { pkg: pkg.into(), name: bare.into(), sigil: None, kind: SymKind::Sub })
1978 }
1979 NodeKind::MethodCall { object, method, .. } => {
1980 let mut receiver_packages = std::collections::HashMap::new();
1981 record_receiver_assignment(ast, offset, current_pkg, &mut receiver_packages);
1982 let pkg = infer_receiver_package(object, current_pkg, &receiver_packages)
1983 .unwrap_or_else(|| current_pkg.to_string());
1984 Some(SymbolKey {
1985 pkg: pkg.into(),
1986 name: method.clone().into(),
1987 sigil: None,
1988 kind: SymKind::Sub,
1989 })
1990 }
1991 NodeKind::Use { module, args, .. } => {
1992 if !NON_IMPORT_PRAGMAS.contains(&module.as_str())
1993 && !source_text.is_empty()
1994 && offset >= node.location.start
1995 && offset <= node.location.end
1996 {
1997 let rel_offset = offset.saturating_sub(node.location.start);
1998 if let Some(stmt_text) = source_text.get(node.location.start..node.location.end)
1999 && let Some(token) = token_at_offset_in_text(stmt_text, rel_offset)
2000 && token != *module
2001 && token != "use"
2002 && use_args_import_symbol(module, args, &token)
2003 {
2004 return Some(SymbolKey {
2005 pkg: module.clone().into(),
2006 name: token.into(),
2007 sigil: None,
2008 kind: SymKind::Sub,
2009 });
2010 }
2011 }
2012
2013 Some(SymbolKey {
2015 pkg: module.clone().into(),
2016 name: module.clone().into(),
2017 sigil: None,
2018 kind: SymKind::Pack,
2019 })
2020 }
2021 _ => None,
2022 }
2023}
2024
2025pub fn symbol_at_cursor_with_source(
2030 ast: &Node,
2031 offset: usize,
2032 current_pkg: &str,
2033 source_text: &str,
2034) -> Option<SymbolKey> {
2035 symbol_at_cursor_internal(ast, offset, current_pkg, source_text)
2036}
2037
2038pub fn symbol_at_cursor(ast: &Node, offset: usize, current_pkg: &str) -> Option<SymbolKey> {
2043 symbol_at_cursor_internal(ast, offset, current_pkg, "")
2044}
2045
2046pub fn current_package_at(ast: &Node, offset: usize) -> &str {
2082 fn package_in_statement_list<'a>(
2083 statements: &'a [Node],
2084 offset: usize,
2085 mut current_pkg: &'a str,
2086 ) -> &'a str {
2087 for child in statements {
2088 if child.location.start > offset {
2089 break;
2090 }
2091
2092 if child.location.start <= offset && offset <= child.location.end {
2093 return package_in_node(child, offset, current_pkg);
2094 }
2095
2096 if let NodeKind::Package { name, block: None, .. } = &child.kind {
2097 current_pkg = name.as_str();
2098 }
2099 }
2100
2101 current_pkg
2102 }
2103
2104 fn package_in_node<'a>(node: &'a Node, offset: usize, current_pkg: &'a str) -> &'a str {
2105 match &node.kind {
2106 NodeKind::Program { statements } | NodeKind::Block { statements } => {
2107 package_in_statement_list(statements, offset, current_pkg)
2108 }
2109 NodeKind::Package { name, block, .. } if node.location.start <= offset => {
2110 let package_name = name.as_str();
2111 if let Some(block) = block
2112 && block.location.start <= offset
2113 && offset <= block.location.end
2114 {
2115 return package_in_node(block, offset, package_name);
2116 }
2117 package_name
2118 }
2119 _ => {
2120 for child in get_node_children(node) {
2121 if child.location.start <= offset && offset <= child.location.end {
2122 return package_in_node(child, offset, current_pkg);
2123 }
2124 }
2125 current_pkg
2126 }
2127 }
2128 }
2129
2130 package_in_node(ast, offset, "main")
2131}
2132
2133pub fn find_node_at_offset(node: &Node, offset: usize) -> Option<&Node> {
2170 if offset < node.location.start || offset > node.location.end {
2171 return None;
2172 }
2173
2174 let children = get_node_children(node);
2176 for child in children {
2177 if let Some(found) = find_node_at_offset(child, offset) {
2178 return Some(found);
2179 }
2180 }
2181
2182 Some(node)
2184}
2185
2186pub fn get_node_children(node: &Node) -> Vec<&Node> {
2212 node.children()
2215}
2216
2217#[cfg(test)]
2218mod tests {
2219 use super::*;
2220 use crate::Parser;
2221 use std::sync::Arc;
2222
2223 fn make_provider(source: &str) -> DeclarationProvider<'static> {
2225 let mut parser = Parser::new(source);
2226 let ast = parser.parse().expect("parse must succeed");
2227 DeclarationProvider::new(Arc::new(ast), source.to_string(), "file:///test.pl".to_string())
2228 }
2229
2230 #[test]
2246 fn method_decl_find_declaration_self_locates() {
2247 let source = "class Foo { method greet { return 1; } }";
2248 let provider = make_provider(source);
2249 let offset = source.find("greet").expect("greet must be in source");
2253 let result = provider.find_declaration(offset, 0);
2254 assert!(
2255 result.is_some(),
2256 "find_declaration on a Method node must return Some; source={source:?} offset={offset}"
2257 );
2258 }
2259
2260 #[test]
2264 fn method_decl_collect_subroutine_declarations_finds_method() {
2265 let source = "class Foo { method greet { return 1; } }";
2266 let provider = make_provider(source);
2267 let mut subs = Vec::new();
2268 provider.collect_subroutine_declarations(&provider.ast, "greet", &mut subs);
2269 assert!(
2270 !subs.is_empty(),
2271 "collect_subroutine_declarations must find the method 'greet'; got empty vec"
2272 );
2273 assert!(
2274 matches!(subs[0].kind, NodeKind::Method { ref name, .. } if name == "greet"),
2275 "collected declaration must be a Method node named 'greet'"
2276 );
2277 }
2278
2279 #[test]
2283 fn class_decl_collect_package_declarations_finds_class() {
2284 let source = "class Foo { method greet { return 1; } }";
2285 let provider = make_provider(source);
2286 let mut packages = Vec::new();
2287 provider.collect_package_declarations(&provider.ast, "Foo", &mut packages);
2288 assert!(
2289 !packages.is_empty(),
2290 "collect_package_declarations must find class 'Foo'; got empty vec"
2291 );
2292 assert!(
2293 matches!(packages[0].kind, NodeKind::Class { ref name, .. } if name == "Foo"),
2294 "collected declaration must be a Class node named 'Foo'"
2295 );
2296 }
2297
2298 #[test]
2302 fn get_children_static_class_returns_body() {
2303 let source = "class Foo { method greet { return 1; } }";
2304 let provider = make_provider(source);
2305 fn find_class(node: &Node) -> Option<&Node> {
2307 if matches!(node.kind, NodeKind::Class { .. }) {
2308 return Some(node);
2309 }
2310 for child in node.children() {
2311 if let Some(found) = find_class(child) {
2312 return Some(found);
2313 }
2314 }
2315 None
2316 }
2317 let class_node = find_class(&provider.ast).expect("Class node must exist in parsed AST");
2318 let children = DeclarationProvider::get_children_static(class_node);
2319 assert!(
2320 !children.is_empty(),
2321 "get_children_static on Class must return the body; got empty vec"
2322 );
2323 assert!(
2325 matches!(children[0].kind, NodeKind::Block { .. }),
2326 "Class child returned by get_children_static must be a Block"
2327 );
2328 }
2329
2330 #[test]
2334 fn get_children_static_package_block_returns_block() {
2335 let source = "package Foo { sub hello { return 1; } }";
2336 let provider = make_provider(source);
2337 fn find_package(node: &Node) -> Option<&Node> {
2338 if matches!(node.kind, NodeKind::Package { .. }) {
2339 return Some(node);
2340 }
2341 for child in node.children() {
2342 if let Some(found) = find_package(child) {
2343 return Some(found);
2344 }
2345 }
2346 None
2347 }
2348 let package_node =
2349 find_package(&provider.ast).expect("Package node must exist in parsed AST");
2350 let children = DeclarationProvider::get_children_static(package_node);
2351 assert!(
2353 !children.is_empty(),
2354 "get_children_static on Package-with-block must return the block; got empty vec"
2355 );
2356 }
2357
2358 #[test]
2363 fn symbol_at_cursor_method_decl_returns_symbol_key() {
2364 let source = "class Foo { method greet { return 1; } }";
2365 let mut parser = Parser::new(source);
2366 let ast = parser.parse().expect("parse must succeed");
2367 let offset = source.find("greet").expect("greet must be in source");
2368 let result = symbol_at_cursor(&ast, offset, "Foo");
2369 assert!(
2370 result.is_some(),
2371 "symbol_at_cursor on a Method declaration must return Some; source={source:?}"
2372 );
2373 let key = result.unwrap();
2374 assert_eq!(key.name.as_ref(), "greet", "symbol name must be the method name");
2375 assert_eq!(key.kind, crate::workspace_index::SymKind::Sub, "method kind must be Sub");
2376 }
2377
2378 #[test]
2390 fn subroutine_decl_boundary_discriminator_rejects_different_sub_name() {
2391 let source = "sub hello { return 1; }";
2392 let provider = make_provider(source);
2393 let mut subs = Vec::new();
2394 provider.collect_subroutine_declarations(&provider.ast, "goodbye", &mut subs);
2396 assert!(
2397 subs.is_empty(),
2398 "collect_subroutine_declarations must NOT collect hello when searching for goodbye; got {count} node(s)",
2399 count = subs.len()
2400 );
2401 }
2402
2403 #[test]
2408 fn method_decl_boundary_discriminator_rejects_different_method_name() {
2409 let source = "class Foo { method greet { return 1; } }";
2410 let provider = make_provider(source);
2411 let mut subs = Vec::new();
2412 provider.collect_subroutine_declarations(&provider.ast, "farewell", &mut subs);
2414 assert!(
2415 subs.is_empty(),
2416 "collect_subroutine_declarations must NOT collect greet when searching for farewell; got {count} node(s)",
2417 count = subs.len()
2418 );
2419 }
2420
2421 #[test]
2426 fn class_decl_boundary_discriminator_rejects_different_class_name() {
2427 let source = "class Foo { method greet { return 1; } }";
2428 let provider = make_provider(source);
2429 let mut packages = Vec::new();
2430 provider.collect_package_declarations(&provider.ast, "Bar", &mut packages);
2432 assert!(
2433 packages.is_empty(),
2434 "collect_package_declarations must NOT collect Foo when searching for Bar; got {count} node(s)",
2435 count = packages.len()
2436 );
2437 }
2438
2439 #[test]
2449 fn subroutine_decl_collect_subroutine_declarations_finds_subroutine() {
2450 let source = "sub hello { return 1; }";
2451 let provider = make_provider(source);
2452 let mut subs = Vec::new();
2453 provider.collect_subroutine_declarations(&provider.ast, "hello", &mut subs);
2454 assert!(
2455 !subs.is_empty(),
2456 "collect_subroutine_declarations must find the sub hello; got empty vec"
2457 );
2458 assert!(
2459 matches!(subs[0].kind, NodeKind::Subroutine { ref name, .. } if name.as_deref() == Some("hello")),
2460 "collected declaration must be a Subroutine node named hello"
2461 );
2462 }
2463
2464 #[test]
2469 fn package_decl_collect_package_declarations_finds_package() {
2470 let source = "package Bar; sub hello { return 1; }";
2471 let provider = make_provider(source);
2472 let mut packages = Vec::new();
2473 provider.collect_package_declarations(&provider.ast, "Bar", &mut packages);
2474 assert!(
2475 !packages.is_empty(),
2476 "collect_package_declarations must find package Bar; got empty vec"
2477 );
2478 assert!(
2479 matches!(packages[0].kind, NodeKind::Package { ref name, .. } if name == "Bar"),
2480 "collected declaration must be a Package node named Bar"
2481 );
2482 }
2483
2484 #[test]
2491 fn symbol_at_cursor_with_source_method_decl_returns_symbol_key() {
2492 let source = "class Foo { method greet { return 1; } }";
2493 let mut parser = Parser::new(source);
2494 let ast = parser.parse().expect("parse must succeed");
2495 let offset = source.find("greet").expect("greet must be in source");
2496 let result = symbol_at_cursor_with_source(&ast, offset, "Foo", source);
2497 assert!(result.is_some(), "symbol_at_cursor_with_source on a Method must return Some");
2498 let key = result.unwrap();
2499 assert_eq!(key.name.as_ref(), "greet", "symbol name must be the bare method name");
2500 assert_eq!(key.pkg.as_ref(), "Foo", "pkg must be the current_pkg for bare method names");
2501 }
2502}