use crate::core::{Position, Span};
use crate::semantic::adapters::selection::find_kerml_selection_spans;
use crate::syntax::kerml::ast::{
Annotation, Classifier, ClassifierKind, ClassifierMember, Element, Feature, FeatureMember,
Import, ImportKind, KerMLFile, Package,
};
use crate::syntax::kerml::model::types::Comment;
fn make_span(start_line: usize, start_col: usize, end_line: usize, end_col: usize) -> Span {
Span {
start: Position {
line: start_line,
column: start_col,
},
end: Position {
line: end_line,
column: end_col,
},
}
}
fn make_package(name: &str, span: Option<Span>, elements: Vec<Element>) -> Package {
Package {
name: Some(name.to_string()),
elements,
span,
}
}
fn make_classifier(
kind: ClassifierKind,
name: &str,
span: Option<Span>,
body: Vec<ClassifierMember>,
) -> Classifier {
Classifier {
kind,
is_abstract: false,
name: Some(name.to_string()),
body,
span,
}
}
fn make_feature(name: &str, span: Option<Span>, body: Vec<FeatureMember>) -> Feature {
Feature {
name: Some(name.to_string()),
direction: None,
is_const: false,
is_derived: false,
body,
span,
}
}
fn make_comment(content: &str, span: Option<Span>) -> Comment {
Comment {
content: content.to_string(),
about: vec![],
locale: None,
span,
}
}
fn make_import(path: &str, span: Option<Span>) -> Import {
Import {
path: path.to_string(),
path_span: None,
is_recursive: false,
is_public: false,
kind: ImportKind::Normal,
span,
}
}
fn make_annotation(reference: &str, span: Option<Span>) -> Annotation {
Annotation {
reference: reference.to_string(),
span,
}
}
#[test]
fn test_find_selection_spans_empty_file() {
let file = KerMLFile {
namespace: None,
elements: vec![],
};
let pos = Position::new(1, 5);
let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_find_selection_spans_position_outside_all_elements() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(1, 0, 5, 1)),
vec![],
))],
};
let pos = Position::new(10, 5); let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_find_selection_spans_single_element() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(1, 0, 5, 1)),
vec![],
))],
};
let pos = Position::new(3, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
assert_eq!(spans[0].end.line, 5);
}
#[test]
fn test_find_selection_spans_stops_at_first_containing_element() {
let file = KerMLFile {
namespace: None,
elements: vec![
Element::Package(make_package("First", Some(make_span(1, 0, 3, 1)), vec![])),
Element::Package(make_package("Second", Some(make_span(5, 0, 8, 1)), vec![])),
],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
assert_eq!(spans[0].end.line, 3);
}
#[test]
fn test_range_size_sorting_single_line_spans() {
let inner = Element::Package(make_package("Inner", Some(make_span(2, 5, 2, 15)), vec![]));
let outer = Element::Package(make_package(
"Outer",
Some(make_span(2, 0, 2, 20)),
vec![inner],
));
let file = KerMLFile {
namespace: None,
elements: vec![outer],
};
let pos = Position::new(2, 10); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.column, 5); assert_eq!(spans[1].start.column, 0); }
#[test]
fn test_range_size_sorting_multi_line_spans() {
let inner = Element::Feature(make_feature("Inner", Some(make_span(2, 0, 3, 10)), vec![]));
let outer = Element::Package(make_package(
"Outer",
Some(make_span(1, 0, 5, 10)),
vec![inner],
));
let file = KerMLFile {
namespace: None,
elements: vec![outer],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_range_size_sorting_three_nested_spans() {
let innermost = Comment {
content: "comment".to_string(),
about: vec![],
locale: None,
span: Some(make_span(3, 5, 3, 15)),
};
let middle = Element::Feature(make_feature(
"Middle",
Some(make_span(2, 0, 4, 10)),
vec![FeatureMember::Comment(innermost)],
));
let outer = Element::Package(make_package(
"Outer",
Some(make_span(1, 0, 6, 10)),
vec![middle],
));
let file = KerMLFile {
namespace: None,
elements: vec![outer],
};
let pos = Position::new(3, 10); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 3);
assert_eq!(spans[0].start.line, 3); assert_eq!(spans[1].start.line, 2); assert_eq!(spans[2].start.line, 1); }
#[test]
fn test_try_push_span_with_none_span() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package("Test", None, vec![]))],
};
let pos = Position::new(2, 5);
let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_try_push_span_with_comment_element() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Comment(make_comment(
"Test comment",
Some(make_span(1, 0, 1, 20)),
))],
};
let pos = Position::new(1, 10);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_try_push_span_with_import_element() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Import(make_import(
"Test::Package",
Some(make_span(1, 0, 1, 20)),
))],
};
let pos = Position::new(1, 10);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_try_push_span_with_annotation_element() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Annotation(make_annotation(
"TestAnnotation",
Some(make_span(1, 0, 1, 20)),
))],
};
let pos = Position::new(1, 10);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_try_push_span_position_at_boundary_start() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Comment(make_comment(
"Test",
Some(make_span(5, 10, 5, 20)),
))],
};
let pos = Position::new(5, 10); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_try_push_span_position_at_boundary_end() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Comment(make_comment(
"Test",
Some(make_span(5, 10, 5, 20)),
))],
};
let pos = Position::new(5, 20); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_try_push_span_position_before_span() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Comment(make_comment(
"Test",
Some(make_span(5, 10, 5, 20)),
))],
};
let pos = Position::new(5, 9); let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_try_push_span_position_after_span() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Comment(make_comment(
"Test",
Some(make_span(5, 10, 5, 20)),
))],
};
let pos = Position::new(5, 21); let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_collect_containing_spans_dispatches_to_package() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(1, 0, 5, 1)),
vec![],
))],
};
let pos = Position::new(3, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_collect_containing_spans_dispatches_to_classifier() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Classifier(make_classifier(
ClassifierKind::Class,
"TestClass",
Some(make_span(1, 0, 5, 1)),
vec![],
))],
};
let pos = Position::new(3, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_collect_containing_spans_dispatches_to_feature() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Feature(make_feature(
"testFeature",
Some(make_span(1, 0, 3, 1)),
vec![],
))],
};
let pos = Position::new(2, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_collect_containing_spans_with_mixed_elements() {
let file = KerMLFile {
namespace: None,
elements: vec![
Element::Comment(make_comment("Comment", Some(make_span(1, 0, 1, 10)))),
Element::Import(make_import("Import", Some(make_span(2, 0, 2, 10)))),
Element::Package(make_package(
"Package",
Some(make_span(3, 0, 5, 10)),
vec![],
)),
],
};
let pos = Position::new(4, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 3);
}
#[test]
fn test_collect_package_spans_empty_package() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Empty",
Some(make_span(1, 0, 3, 1)),
vec![],
))],
};
let pos = Position::new(2, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_collect_package_spans_with_nested_package() {
let inner = Element::Package(make_package("Inner", Some(make_span(2, 2, 3, 3)), vec![]));
let outer = Element::Package(make_package(
"Outer",
Some(make_span(1, 0, 5, 1)),
vec![inner],
));
let file = KerMLFile {
namespace: None,
elements: vec![outer],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_package_spans_with_classifier_child() {
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Class,
"Child",
Some(make_span(2, 2, 4, 3)),
vec![],
));
let package = Element::Package(make_package(
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![classifier],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(3, 2); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_package_spans_with_feature_child() {
let feature = Element::Feature(make_feature("Child", Some(make_span(2, 2, 3, 3)), vec![]));
let package = Element::Package(make_package(
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![feature],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_package_spans_with_comment_child() {
let comment = Element::Comment(make_comment("Comment", Some(make_span(2, 2, 2, 10))));
let package = Element::Package(make_package(
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![comment],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_package_spans_stops_at_first_matching_child() {
let child1 = Element::Feature(make_feature("First", Some(make_span(2, 0, 3, 1)), vec![]));
let child2 = Element::Feature(make_feature("Second", Some(make_span(4, 0, 5, 1)), vec![]));
let package = Element::Package(make_package(
"Parent",
Some(make_span(1, 0, 6, 1)),
vec![child1, child2],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_package_spans_returns_false_when_span_not_containing() {
let package = Element::Package(make_package("Test", Some(make_span(1, 0, 3, 1)), vec![]));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(5, 0); let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_collect_package_spans_with_multiple_children_none_matching() {
let child1 = Element::Feature(make_feature("First", Some(make_span(2, 0, 2, 10)), vec![]));
let child2 = Element::Feature(make_feature("Second", Some(make_span(3, 0, 3, 10)), vec![]));
let package = Element::Package(make_package(
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![child1, child2],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(4, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_collect_classifier_spans_empty_body() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Classifier(make_classifier(
ClassifierKind::Class,
"Empty",
Some(make_span(1, 0, 3, 1)),
vec![],
))],
};
let pos = Position::new(2, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_collect_classifier_spans_with_feature_member() {
let feature = make_feature("member", Some(make_span(2, 2, 3, 3)), vec![]);
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Class,
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![ClassifierMember::Feature(feature)],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_classifier_spans_with_comment_member() {
let comment = make_comment("Comment", Some(make_span(2, 2, 2, 10)));
let classifier = Element::Classifier(make_classifier(
ClassifierKind::DataType,
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![ClassifierMember::Comment(comment)],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_classifier_spans_with_non_feature_non_comment_members() {
use crate::syntax::kerml::ast::Specialization;
let spec = Specialization {
general: "Base".to_string(),
span: Some(make_span(2, 2, 2, 10)),
};
let import = make_import("Test::Import", Some(make_span(3, 2, 3, 10)));
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Behavior,
"Parent",
Some(make_span(1, 0, 5, 1)),
vec![
ClassifierMember::Specialization(spec),
ClassifierMember::Import(import),
],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_collect_classifier_spans_stops_at_first_matching_member() {
let feature1 = make_feature("First", Some(make_span(2, 0, 3, 1)), vec![]);
let feature2 = make_feature("Second", Some(make_span(4, 0, 5, 1)), vec![]);
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Structure,
"Parent",
Some(make_span(1, 0, 6, 1)),
vec![
ClassifierMember::Feature(feature1),
ClassifierMember::Feature(feature2),
],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].start.line, 2); assert_eq!(spans[1].start.line, 1); }
#[test]
fn test_collect_classifier_spans_returns_false_when_not_containing() {
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Function,
"Test",
Some(make_span(1, 0, 3, 1)),
vec![],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(5, 0); let spans = find_kerml_selection_spans(&file, pos);
assert!(spans.is_empty());
}
#[test]
fn test_collect_classifier_spans_with_different_classifier_kinds() {
let kinds = vec![
ClassifierKind::Type,
ClassifierKind::Classifier,
ClassifierKind::DataType,
ClassifierKind::Class,
ClassifierKind::Structure,
ClassifierKind::Behavior,
ClassifierKind::Function,
ClassifierKind::Association,
ClassifierKind::AssociationStructure,
ClassifierKind::Metaclass,
];
for kind in kinds {
let classifier = Element::Classifier(make_classifier(
kind.clone(),
"Test",
Some(make_span(1, 0, 3, 1)),
vec![],
));
let file = KerMLFile {
namespace: None,
elements: vec![classifier],
};
let pos = Position::new(2, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1, "Failed for kind {kind:?}");
}
}
#[test]
fn test_collect_classifier_spans_with_abstract_classifier() {
let classifier = Classifier {
kind: ClassifierKind::Class,
is_abstract: true,
name: Some("AbstractClass".to_string()),
body: vec![],
span: Some(make_span(1, 0, 3, 1)),
};
let file = KerMLFile {
namespace: None,
elements: vec![Element::Classifier(classifier)],
};
let pos = Position::new(2, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_deeply_nested_structure() {
let comment = make_comment("Deep", Some(make_span(4, 6, 4, 15)));
let feature = make_feature(
"feat",
Some(make_span(3, 4, 5, 5)),
vec![FeatureMember::Comment(comment)],
);
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Class,
"cls",
Some(make_span(2, 2, 6, 3)),
vec![ClassifierMember::Feature(feature)],
));
let package = Element::Package(make_package(
"pkg",
Some(make_span(1, 0, 7, 1)),
vec![classifier],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(4, 10);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 4);
assert_eq!(spans[0].start.line, 4); assert_eq!(spans[1].start.line, 3); assert_eq!(spans[2].start.line, 2); assert_eq!(spans[3].start.line, 1); }
#[test]
fn test_position_at_zero_zero() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(0, 0, 5, 1)),
vec![],
))],
};
let pos = Position::new(0, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_multiple_top_level_elements_first_contains() {
let file = KerMLFile {
namespace: None,
elements: vec![
Element::Package(make_package("First", Some(make_span(1, 0, 3, 1)), vec![])),
Element::Package(make_package("Second", Some(make_span(5, 0, 7, 1)), vec![])),
],
};
let pos = Position::new(2, 0); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 1);
}
#[test]
fn test_multiple_top_level_elements_second_contains() {
let file = KerMLFile {
namespace: None,
elements: vec![
Element::Package(make_package("First", Some(make_span(1, 0, 3, 1)), vec![])),
Element::Package(make_package("Second", Some(make_span(5, 0, 7, 1)), vec![])),
],
};
let pos = Position::new(6, 0); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].start.line, 5);
}
#[test]
fn test_spans_with_same_start_different_end() {
let inner = Element::Feature(make_feature("Inner", Some(make_span(2, 0, 3, 5)), vec![]));
let outer = Element::Package(make_package(
"Outer",
Some(make_span(2, 0, 5, 10)),
vec![inner],
));
let file = KerMLFile {
namespace: None,
elements: vec![outer],
};
let pos = Position::new(2, 5); let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].end.line, 3); assert_eq!(spans[1].end.line, 5); }
#[test]
fn test_comment_in_feature_in_classifier_in_package() {
let comment = make_comment("Test", Some(make_span(4, 6, 4, 15)));
let feature = make_feature(
"feat",
Some(make_span(3, 4, 5, 5)),
vec![FeatureMember::Comment(comment)],
);
let classifier = Element::Classifier(make_classifier(
ClassifierKind::Class,
"cls",
Some(make_span(2, 2, 6, 3)),
vec![ClassifierMember::Feature(feature)],
));
let package = Element::Package(make_package(
"pkg",
Some(make_span(1, 0, 7, 1)),
vec![classifier],
));
let file = KerMLFile {
namespace: None,
elements: vec![package],
};
let pos = Position::new(4, 10);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 4);
let pos = Position::new(3, 4);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 3);
let pos = Position::new(2, 2);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 2);
let pos = Position::new(1, 1);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1); }
#[test]
fn test_large_line_numbers() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(1000, 0, 2000, 1)),
vec![],
))],
};
let pos = Position::new(1500, 0);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}
#[test]
fn test_large_column_numbers() {
let file = KerMLFile {
namespace: None,
elements: vec![Element::Package(make_package(
"Test",
Some(make_span(1, 0, 1, 1000)),
vec![],
))],
};
let pos = Position::new(1, 500);
let spans = find_kerml_selection_spans(&file, pos);
assert_eq!(spans.len(), 1);
}