1use perl_parser_core::PositionMapper;
31use perl_parser_core::ast::{Node, NodeKind};
32use perl_position_tracking::{WirePosition, WireRange};
33use serde::{Deserialize, Serialize};
34use std::collections::{BTreeMap, BTreeSet};
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct TypeHierarchyItem {
39 pub name: String,
41 pub kind: TypeHierarchySymbolKind,
43 pub uri: String,
45 pub range: WireRange,
47 pub selection_range: WireRange,
49 pub detail: Option<String>,
51 pub data: Option<serde_json::Value>,
53}
54
55#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
60pub enum TypeHierarchySymbolKind {
61 Class = 5,
63 Method = 6,
65 Function = 12,
67}
68
69#[derive(Default, Debug)]
71struct HierarchyIndex {
72 parents: BTreeMap<String, BTreeSet<String>>,
74 children: BTreeMap<String, BTreeSet<String>>,
76 roles: BTreeMap<String, BTreeSet<String>>,
78}
79
80impl HierarchyIndex {
81 fn add_inheritance(&mut self, child: &str, parent: &str) {
82 self.parents.entry(child.to_string()).or_default().insert(parent.to_string());
83 self.children.entry(parent.to_string()).or_default().insert(child.to_string());
84 }
85
86 fn add_role(&mut self, package: &str, role: &str) {
87 self.roles.entry(package.to_string()).or_default().insert(role.to_string());
88 }
89
90 fn get_parents(&self, package: &str) -> Vec<String> {
91 self.parents.get(package).map(|set| set.iter().cloned().collect()).unwrap_or_default()
92 }
93
94 fn get_roles(&self, package: &str) -> Vec<String> {
95 self.roles.get(package).map(|set| set.iter().cloned().collect()).unwrap_or_default()
96 }
97
98 fn get_children(&self, package: &str) -> Vec<String> {
99 self.children.get(package).map(|set| set.iter().cloned().collect()).unwrap_or_default()
100 }
101}
102
103pub struct TypeHierarchyProvider;
105
106impl Default for TypeHierarchyProvider {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl TypeHierarchyProvider {
113 pub fn new() -> Self {
115 Self
116 }
117
118 fn build_hierarchy_index(&self, ast: &Node) -> HierarchyIndex {
120 let mut index = HierarchyIndex::default();
121 let mut current_package = "main".to_string();
122
123 self.index_hierarchy_recursive(ast, &mut index, &mut current_package);
125
126 index
127 }
128
129 fn index_hierarchy_recursive(
130 &self,
131 node: &Node,
132 index: &mut HierarchyIndex,
133 current_package: &mut String,
134 ) {
135 match &node.kind {
136 NodeKind::Package { name, block, name_span: _ } => {
137 if block.is_some() {
138 let saved_package = current_package.clone();
141 *current_package = name.clone();
142 if let Some(blk) = block {
143 self.index_hierarchy_recursive(blk, index, current_package);
144 }
145 *current_package = saved_package;
146 } else {
147 *current_package = name.clone();
150 }
151 }
152 NodeKind::Use { module, args, .. } => {
153 if module == "parent" || module == "base" {
154 for arg in args {
155 for parent in self.normalize_parent_arg(arg) {
156 index.add_inheritance(current_package, &parent);
157 }
158 }
159 }
160 }
161 NodeKind::VariableDeclaration { declarator, variable, initializer, .. } => {
162 if declarator == "our"
163 && let NodeKind::Variable { sigil, name: var_name } = &variable.kind
164 && sigil == "@"
165 && var_name == "ISA"
166 && let Some(init) = initializer
167 {
168 for parent in self.extract_isa_parents(init) {
169 index.add_inheritance(current_package, &parent);
170 }
171 }
172 }
173 NodeKind::VariableListDeclaration { declarator, variables, initializer, .. } => {
174 if declarator == "our" {
175 for var in variables {
177 if let NodeKind::Variable { sigil, name: var_name } = &var.kind
178 && sigil == "@"
179 && var_name == "ISA"
180 && let Some(init) = initializer
181 {
182 for parent in self.extract_isa_parents(init) {
183 index.add_inheritance(current_package, &parent);
184 }
185 }
186 }
187 }
188 }
189 NodeKind::ExpressionStatement { expression } => {
191 if let NodeKind::FunctionCall { name, args } = &expression.kind {
192 match name.as_str() {
193 "extends" => {
194 for parent in Self::extract_names_from_args(args) {
195 index.add_inheritance(current_package, &parent);
196 }
197 }
198 "with" => {
199 for role in Self::extract_names_from_args(args) {
200 index.add_role(current_package, &role);
201 }
202 }
203 _ => {}
204 }
205 }
206 }
207 NodeKind::Program { statements } | NodeKind::Block { statements } => {
208 for stmt in statements {
209 self.index_hierarchy_recursive(stmt, index, current_package);
210 }
211 }
212 _ => {
213 if let Some(children) = self.get_children(node) {
215 for child in children {
216 self.index_hierarchy_recursive(child, index, current_package);
217 }
218 }
219 }
220 }
221 }
222
223 fn normalize_parent_arg(&self, arg: &str) -> Vec<String> {
225 let arg = arg.trim();
226
227 if arg.starts_with("qw(") && arg.ends_with(')') {
229 let content = &arg[3..arg.len() - 1];
230 return content.split_whitespace().map(|s| s.to_string()).collect();
231 }
232
233 if arg.starts_with("qw") && arg.len() > 2 {
235 let delim_start = arg.chars().nth(2).unwrap_or(' ');
236 let delim_end = match delim_start {
237 '(' => ')',
238 '{' => '}',
239 '[' => ']',
240 '<' => '>',
241 _ => delim_start,
242 };
243 if let Some(start) = arg.find(delim_start)
244 && let Some(end) = arg.rfind(delim_end)
245 {
246 let content = &arg[start + 1..end];
247 return content.split_whitespace().map(|s| s.to_string()).collect();
248 }
249 }
250
251 let clean = arg.trim_matches('"').trim_matches('\'').trim_matches('`');
253 vec![clean.to_string()]
254 }
255
256 fn extract_names_from_args(args: &[Node]) -> Vec<String> {
261 args.iter().flat_map(Self::collect_symbol_names).collect()
262 }
263
264 fn collect_symbol_names(node: &Node) -> Vec<String> {
266 match &node.kind {
267 NodeKind::String { value, .. } => {
268 let trimmed = value.trim().trim_matches('\'').trim_matches('"').trim();
269 if trimmed.is_empty() { Vec::new() } else { vec![trimmed.to_string()] }
270 }
271 NodeKind::Identifier { name } => {
272 let trimmed = name.trim();
273 if trimmed.is_empty() { Vec::new() } else { vec![trimmed.to_string()] }
274 }
275 NodeKind::ArrayLiteral { elements } => {
276 elements.iter().flat_map(Self::collect_symbol_names).collect()
277 }
278 _ => Vec::new(),
279 }
280 }
281
282 fn extract_isa_parents(&self, node: &Node) -> Vec<String> {
284 let mut parents = Vec::new();
285
286 match &node.kind {
287 NodeKind::ArrayLiteral { elements } => {
288 for elem in elements {
289 match &elem.kind {
290 NodeKind::String { value, .. } => {
291 for parent in self.normalize_parent_arg(value) {
292 parents.push(parent);
293 }
294 }
295 NodeKind::Identifier { name } => {
296 parents.push(name.clone());
298 }
299 _ => {}
300 }
301 }
302 }
303 NodeKind::String { value, .. } => {
304 for parent in self.normalize_parent_arg(value) {
305 parents.push(parent);
306 }
307 }
308 NodeKind::Identifier { name } => {
309 parents.push(name.clone());
311 }
312 _ => {}
313 }
314
315 parents
316 }
317
318 pub fn prepare(&self, ast: &Node, code: &str, offset: usize) -> Option<Vec<TypeHierarchyItem>> {
320 let position_mapper = PositionMapper::new(code);
321 let target_node = self.find_node_at_offset(ast, offset)?;
323
324 match &target_node.kind {
326 NodeKind::Package { name, .. } => {
327 let item = self.create_type_item(
328 name,
329 target_node,
330 &position_mapper,
331 TypeHierarchySymbolKind::Class,
332 );
333 Some(vec![item])
334 }
335 NodeKind::Class { name, .. } => {
336 let item = self.create_type_item(
337 name,
338 target_node,
339 &position_mapper,
340 TypeHierarchySymbolKind::Class,
341 );
342 Some(vec![item])
343 }
344 NodeKind::Identifier { name } => {
345 if self.is_package_identifier(ast, offset, name) {
347 let item = TypeHierarchyItem {
348 name: name.clone(),
349 kind: TypeHierarchySymbolKind::Class,
350 uri: "file:///current".to_string(),
351 range: self.node_to_range(target_node, &position_mapper),
352 selection_range: self.node_to_range(target_node, &position_mapper),
353 detail: Some("Perl Package".to_string()),
354 data: None,
355 };
356 Some(vec![item])
357 } else {
358 None
359 }
360 }
361 _ => None,
362 }
363 }
364
365 pub fn find_supertypes(&self, ast: &Node, item: &TypeHierarchyItem) -> Vec<TypeHierarchyItem> {
367 let index = self.build_hierarchy_index(ast);
368 let parents = index.get_parents(&item.name);
369 let roles = index.get_roles(&item.name);
370
371 let parent_items = parents.into_iter().map(|name| TypeHierarchyItem {
372 name,
373 kind: TypeHierarchySymbolKind::Class,
374 uri: "file:///current".to_string(),
375 range: WireRange::default(),
376 selection_range: WireRange::default(),
377 detail: Some("Parent Class".to_string()),
378 data: None,
379 });
380
381 let role_items = roles.into_iter().map(|name| TypeHierarchyItem {
382 name,
383 kind: TypeHierarchySymbolKind::Class,
384 uri: "file:///current".to_string(),
385 range: WireRange::default(),
386 selection_range: WireRange::default(),
387 detail: Some("Role".to_string()),
388 data: None,
389 });
390
391 parent_items.chain(role_items).collect()
392 }
393
394 pub fn c3_mro(&self, ast: &Node, package: &str) -> Vec<String> {
401 let index = self.build_hierarchy_index(ast);
402 let mut result = Vec::new();
403 let mut visited = BTreeSet::new();
404 self.c3_linearize(package, &index, &mut result, &mut visited);
405 result
406 }
407
408 fn c3_linearize(
410 &self,
411 package: &str,
412 index: &HierarchyIndex,
413 result: &mut Vec<String>,
414 visited: &mut BTreeSet<String>,
415 ) {
416 if visited.contains(package) {
417 return;
418 }
419 visited.insert(package.to_string());
420
421 let parents = index.get_parents(package);
422 if parents.is_empty() {
423 result.push(package.to_string());
424 return;
425 }
426
427 let mut parent_mros: Vec<Vec<String>> = parents
429 .iter()
430 .map(|p| {
431 let mut sub_result = Vec::new();
432 let mut sub_visited = BTreeSet::new();
433 self.c3_linearize(p, index, &mut sub_result, &mut sub_visited);
434 sub_result
435 })
436 .collect();
437 parent_mros.push(parents.clone());
439
440 result.push(package.to_string());
442
443 loop {
445 parent_mros.retain(|list| !list.is_empty());
447 if parent_mros.is_empty() {
448 break;
449 }
450
451 let chosen = parent_mros.iter().find_map(|list| {
453 let candidate = list.first()?;
454 let in_tail =
455 parent_mros.iter().any(|other| other.iter().skip(1).any(|n| n == candidate));
456 if in_tail { None } else { Some(candidate.clone()) }
457 });
458
459 match chosen {
460 Some(cls) => {
461 if !result.contains(&cls) {
462 result.push(cls.clone());
463 }
464 for list in &mut parent_mros {
466 if list.first().is_some_and(|h| h == &cls) {
467 list.remove(0);
468 }
469 }
470 }
471 None => {
472 for list in &parent_mros.clone() {
474 if let Some(head) = list.first() {
475 if !result.contains(head) {
476 result.push(head.clone());
477 }
478 }
479 }
480 break;
481 }
482 }
483 }
484 }
485
486 pub fn find_subtypes(&self, ast: &Node, item: &TypeHierarchyItem) -> Vec<TypeHierarchyItem> {
488 let index = self.build_hierarchy_index(ast);
489 let children = index.get_children(&item.name);
490
491 children
492 .into_iter()
493 .map(|name| TypeHierarchyItem {
494 name,
495 kind: TypeHierarchySymbolKind::Class,
496 uri: "file:///current".to_string(),
497 range: WireRange::default(),
498 selection_range: WireRange::default(),
499 detail: Some("Subclass".to_string()),
500 data: None,
501 })
502 .collect()
503 }
504
505 fn find_node_at_offset<'a>(&self, node: &'a Node, offset: usize) -> Option<&'a Node> {
508 if offset >= node.location.start && offset < node.location.end {
509 if let Some(children) = self.get_children(node) {
511 for child in children {
512 if let Some(found) = self.find_node_at_offset(child, offset) {
513 return Some(found);
514 }
515 }
516 }
517 Some(node)
519 } else {
520 None
521 }
522 }
523
524 fn get_children<'a>(&self, node: &'a Node) -> Option<Vec<&'a Node>> {
525 match &node.kind {
526 NodeKind::Program { statements } => Some(statements.iter().collect()),
527 NodeKind::Block { statements } => Some(statements.iter().collect()),
528 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
529 let mut children = vec![condition.as_ref(), then_branch.as_ref()];
530 for branch in elsif_branches {
531 children.push(&branch.0);
532 children.push(&branch.1);
533 }
534 if let Some(else_b) = else_branch {
535 children.push(else_b.as_ref());
536 }
537 Some(children)
538 }
539 NodeKind::Package { block, .. } => block.as_ref().map(|b| vec![b.as_ref()]),
540 NodeKind::Class { body, .. } => Some(vec![body.as_ref()]),
541 NodeKind::Subroutine { body, .. } => Some(vec![body.as_ref()]),
542 NodeKind::Assignment { lhs, rhs, .. } => Some(vec![lhs.as_ref(), rhs.as_ref()]),
543 NodeKind::ExpressionStatement { expression } => Some(vec![expression.as_ref()]),
544 _ => None,
545 }
546 }
547
548 fn is_package_identifier(&self, _ast: &Node, _offset: usize, _name: &str) -> bool {
549 false
552 }
553
554 fn create_type_item(
555 &self,
556 name: &str,
557 node: &Node,
558 position_mapper: &PositionMapper,
559 kind: TypeHierarchySymbolKind,
560 ) -> TypeHierarchyItem {
561 TypeHierarchyItem {
562 name: name.to_string(),
563 kind,
564 uri: "file:///current".to_string(),
565 range: self.node_to_range(node, position_mapper),
566 selection_range: self.node_to_range(node, position_mapper),
567 detail: Some(format!(
568 "Perl {}",
569 match kind {
570 TypeHierarchySymbolKind::Class => "Package",
571 TypeHierarchySymbolKind::Method => "Method",
572 TypeHierarchySymbolKind::Function => "Function",
573 }
574 )),
575 data: None,
576 }
577 }
578
579 fn node_to_range(&self, node: &Node, position_mapper: &PositionMapper) -> WireRange {
581 let start_pos = self.offset_to_position(node.location.start, position_mapper);
582 let end_pos = self.offset_to_position(node.location.end, position_mapper);
583 WireRange {
584 start: WirePosition { line: start_pos.0, character: start_pos.1 },
585 end: WirePosition { line: end_pos.0, character: end_pos.1 },
586 }
587 }
588
589 fn offset_to_position(&self, offset: usize, position_mapper: &PositionMapper) -> (u32, u32) {
591 let pos = position_mapper.byte_to_lsp_pos(offset);
592 (pos.line, pos.character)
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599 use perl_parser_core::parser::Parser;
600 use perl_tdd_support::{must, must_some};
601
602 #[test]
603 fn test_type_hierarchy_for_package() {
604 let code = r#"package MyClass;
605use parent 'BaseClass';
606
607sub new {
608 my $class = shift;
609 return bless {}, $class;
610}
611"#;
612 let mut parser = Parser::new(code);
613 let ast = must(parser.parse());
614 let provider = TypeHierarchyProvider::new();
615
616 let items = provider.prepare(&ast, code, 8);
618 assert!(items.is_some());
619 let items = must_some(items);
620 assert_eq!(items.len(), 1);
621 assert_eq!(items[0].name, "MyClass");
622
623 let supertypes = provider.find_supertypes(&ast, &items[0]);
625 assert_eq!(supertypes.len(), 1);
626 assert_eq!(supertypes[0].name, "BaseClass");
627 }
628
629 #[test]
630 fn test_type_hierarchy_with_isa() {
631 let code = r#"package Child;
632our @ISA = qw(Parent1 Parent2);
633"#;
634 let mut parser = Parser::new(code);
635 let ast = must(parser.parse());
636 let provider = TypeHierarchyProvider::new();
637
638 let items = provider.prepare(&ast, code, 8);
640 assert!(items.is_some());
641 let items = must_some(items);
642 assert_eq!(items[0].name, "Child");
643
644 let supertypes = provider.find_supertypes(&ast, &items[0]);
646 let _ = supertypes.len();
648 }
649
650 #[test]
651 fn test_find_subtypes() {
652 let code = r#"package Base;
653
654package Derived1;
655use parent 'Base';
656
657package Derived2;
658our @ISA = ('Base');
659
660package Unrelated;
661use parent 'Other';
662"#;
663 let mut parser = Parser::new(code);
664 let ast = must(parser.parse());
665 let provider = TypeHierarchyProvider::new();
666
667 let base_item = TypeHierarchyItem {
669 name: "Base".to_string(),
670 kind: TypeHierarchySymbolKind::Class,
671 uri: "file:///test".to_string(),
672 range: WireRange::default(),
673 selection_range: WireRange::default(),
674 detail: None,
675 data: None,
676 };
677
678 let subtypes = provider.find_subtypes(&ast, &base_item);
680 assert_eq!(subtypes.len(), 2, "Should find exactly 2 subtypes");
681
682 let subtype_names: Vec<String> = subtypes.iter().map(|t| t.name.clone()).collect();
683 assert!(subtype_names.contains(&"Derived1".to_string()), "Should find Derived1");
684 assert!(subtype_names.contains(&"Derived2".to_string()), "Should find Derived2");
685 assert!(!subtype_names.contains(&"Unrelated".to_string()), "Should not find Unrelated");
686 }
687
688 #[test]
689 fn test_qw_parsing() {
690 let code = r#"package Multi;
691our @ISA = qw(Parent1 Parent2 Parent3);
692"#;
693 let mut parser = Parser::new(code);
694 let ast = must(parser.parse());
695 let provider = TypeHierarchyProvider::new();
696
697 let items = provider.prepare(&ast, code, 8);
698 assert!(items.is_some());
699 let items = must_some(items);
700 assert_eq!(items[0].name, "Multi");
701
702 let supertypes = provider.find_supertypes(&ast, &items[0]);
704 let _ = supertypes.len();
706 }
707
708 #[test]
709 fn test_moose_extends_single_parent() {
710 let code = r#"package Animal;
711
712package Dog;
713use Moose;
714extends 'Animal';
715"#;
716 let mut parser = Parser::new(code);
717 let ast = must(parser.parse());
718 let provider = TypeHierarchyProvider::new();
719
720 let dog_item = TypeHierarchyItem {
721 name: "Dog".to_string(),
722 kind: TypeHierarchySymbolKind::Class,
723 uri: "file:///test".to_string(),
724 range: WireRange::default(),
725 selection_range: WireRange::default(),
726 detail: None,
727 data: None,
728 };
729
730 let supertypes = provider.find_supertypes(&ast, &dog_item);
731 assert_eq!(supertypes.len(), 1, "Should find 1 parent via extends");
732 assert_eq!(supertypes[0].name, "Animal");
733 assert_eq!(
734 supertypes[0].detail.as_deref(),
735 Some("Parent Class"),
736 "extends parent should have 'Parent Class' detail"
737 );
738 }
739
740 #[test]
741 fn test_moose_extends_multiple_parents() {
742 let code = r#"package Readable;
743package Writable;
744
745package ReadWriteFile;
746use Moose;
747extends 'Readable', 'Writable';
748"#;
749 let mut parser = Parser::new(code);
750 let ast = must(parser.parse());
751 let provider = TypeHierarchyProvider::new();
752
753 let item = TypeHierarchyItem {
754 name: "ReadWriteFile".to_string(),
755 kind: TypeHierarchySymbolKind::Class,
756 uri: "file:///test".to_string(),
757 range: WireRange::default(),
758 selection_range: WireRange::default(),
759 detail: None,
760 data: None,
761 };
762
763 let supertypes = provider.find_supertypes(&ast, &item);
764 let names: Vec<&str> = supertypes.iter().map(|s| s.name.as_str()).collect();
765 assert!(names.contains(&"Readable"), "Should find Readable parent");
766 assert!(names.contains(&"Writable"), "Should find Writable parent");
767 }
768
769 #[test]
770 fn test_moose_with_role() {
771 let code = r#"package Printable;
772
773package Document;
774use Moose;
775with 'Printable';
776"#;
777 let mut parser = Parser::new(code);
778 let ast = must(parser.parse());
779 let provider = TypeHierarchyProvider::new();
780
781 let item = TypeHierarchyItem {
782 name: "Document".to_string(),
783 kind: TypeHierarchySymbolKind::Class,
784 uri: "file:///test".to_string(),
785 range: WireRange::default(),
786 selection_range: WireRange::default(),
787 detail: None,
788 data: None,
789 };
790
791 let supertypes = provider.find_supertypes(&ast, &item);
792 let role_types: Vec<&TypeHierarchyItem> =
793 supertypes.iter().filter(|s| s.detail.as_deref() == Some("Role")).collect();
794 assert_eq!(role_types.len(), 1, "Should find 1 role via with");
795 assert_eq!(role_types[0].name, "Printable");
796 }
797
798 #[test]
799 fn test_moose_with_multiple_roles() {
800 let code = r#"package Serializable;
801package Printable;
802
803package Report;
804use Moose;
805with 'Serializable', 'Printable';
806"#;
807 let mut parser = Parser::new(code);
808 let ast = must(parser.parse());
809 let provider = TypeHierarchyProvider::new();
810
811 let item = TypeHierarchyItem {
812 name: "Report".to_string(),
813 kind: TypeHierarchySymbolKind::Class,
814 uri: "file:///test".to_string(),
815 range: WireRange::default(),
816 selection_range: WireRange::default(),
817 detail: None,
818 data: None,
819 };
820
821 let supertypes = provider.find_supertypes(&ast, &item);
822 let role_names: Vec<&str> = supertypes
823 .iter()
824 .filter(|s| s.detail.as_deref() == Some("Role"))
825 .map(|s| s.name.as_str())
826 .collect();
827 assert!(role_names.contains(&"Serializable"), "Should find Serializable role");
828 assert!(role_names.contains(&"Printable"), "Should find Printable role");
829 }
830
831 #[test]
832 fn test_moose_extends_and_with_combined() {
833 let code = r#"package Base;
834package MyRole;
835
836package Child;
837use Moose;
838extends 'Base';
839with 'MyRole';
840"#;
841 let mut parser = Parser::new(code);
842 let ast = must(parser.parse());
843 let provider = TypeHierarchyProvider::new();
844
845 let item = TypeHierarchyItem {
846 name: "Child".to_string(),
847 kind: TypeHierarchySymbolKind::Class,
848 uri: "file:///test".to_string(),
849 range: WireRange::default(),
850 selection_range: WireRange::default(),
851 detail: None,
852 data: None,
853 };
854
855 let supertypes = provider.find_supertypes(&ast, &item);
856
857 let parent_names: Vec<&str> = supertypes
858 .iter()
859 .filter(|s| s.detail.as_deref() == Some("Parent Class"))
860 .map(|s| s.name.as_str())
861 .collect();
862 let role_names: Vec<&str> = supertypes
863 .iter()
864 .filter(|s| s.detail.as_deref() == Some("Role"))
865 .map(|s| s.name.as_str())
866 .collect();
867
868 assert_eq!(parent_names, vec!["Base"], "Should find Base as parent");
869 assert_eq!(role_names, vec!["MyRole"], "Should find MyRole as role");
870 }
871
872 #[test]
873 fn test_moo_extends_with() {
874 let code = r#"package MooParent;
875package MooRole;
876
877package MooChild;
878use Moo;
879extends 'MooParent';
880with 'MooRole';
881"#;
882 let mut parser = Parser::new(code);
883 let ast = must(parser.parse());
884 let provider = TypeHierarchyProvider::new();
885
886 let item = TypeHierarchyItem {
887 name: "MooChild".to_string(),
888 kind: TypeHierarchySymbolKind::Class,
889 uri: "file:///test".to_string(),
890 range: WireRange::default(),
891 selection_range: WireRange::default(),
892 detail: None,
893 data: None,
894 };
895
896 let supertypes = provider.find_supertypes(&ast, &item);
897 let names: Vec<&str> = supertypes.iter().map(|s| s.name.as_str()).collect();
898 assert!(names.contains(&"MooParent"), "Moo extends should work");
899 assert!(names.contains(&"MooRole"), "Moo with should work");
900 }
901
902 #[test]
903 fn test_mixed_use_parent_and_extends() {
904 let code = r#"package OldBase;
905package MooseBase;
906
907package Mixed;
908use parent 'OldBase';
909use Moose;
910extends 'MooseBase';
911"#;
912 let mut parser = Parser::new(code);
913 let ast = must(parser.parse());
914 let provider = TypeHierarchyProvider::new();
915
916 let item = TypeHierarchyItem {
917 name: "Mixed".to_string(),
918 kind: TypeHierarchySymbolKind::Class,
919 uri: "file:///test".to_string(),
920 range: WireRange::default(),
921 selection_range: WireRange::default(),
922 detail: None,
923 data: None,
924 };
925
926 let supertypes = provider.find_supertypes(&ast, &item);
927 let parent_names: Vec<&str> = supertypes
928 .iter()
929 .filter(|s| s.detail.as_deref() == Some("Parent Class"))
930 .map(|s| s.name.as_str())
931 .collect();
932 assert!(parent_names.contains(&"OldBase"), "use parent should still work");
933 assert!(parent_names.contains(&"MooseBase"), "extends should also work");
934 }
935
936 #[test]
937 fn test_extends_subtypes_reverse() {
938 let code = r#"package Animal;
940
941package Dog;
942use Moose;
943extends 'Animal';
944
945package Cat;
946use Moo;
947extends 'Animal';
948"#;
949 let mut parser = Parser::new(code);
950 let ast = must(parser.parse());
951 let provider = TypeHierarchyProvider::new();
952
953 let animal_item = TypeHierarchyItem {
954 name: "Animal".to_string(),
955 kind: TypeHierarchySymbolKind::Class,
956 uri: "file:///test".to_string(),
957 range: WireRange::default(),
958 selection_range: WireRange::default(),
959 detail: None,
960 data: None,
961 };
962
963 let subtypes = provider.find_subtypes(&ast, &animal_item);
964 let subtype_names: Vec<&str> = subtypes.iter().map(|s| s.name.as_str()).collect();
965 assert!(subtype_names.contains(&"Dog"), "Dog should be a subtype of Animal");
966 assert!(subtype_names.contains(&"Cat"), "Cat should be a subtype of Animal");
967 }
968
969 #[test]
970 fn test_block_form_packages() {
971 let code = r#"package Outer {
972 package Inner;
973 use parent 'Outer';
974}
975package Other;
976use parent 'Outer';
977"#;
978 let mut parser = Parser::new(code);
979 let ast = must(parser.parse());
980 let provider = TypeHierarchyProvider::new();
981
982 let outer_item = TypeHierarchyItem {
983 name: "Outer".to_string(),
984 kind: TypeHierarchySymbolKind::Class,
985 uri: "file:///test".to_string(),
986 range: WireRange::default(),
987 selection_range: WireRange::default(),
988 detail: None,
989 data: None,
990 };
991
992 let subtypes = provider.find_subtypes(&ast, &outer_item);
994 assert_eq!(subtypes.len(), 2, "Should find both Inner and Other as subtypes");
996 }
997}