#![allow(clippy::unwrap_used)]
use crate::semantic::resolver::Resolver;
use crate::parser::{SysMLParser, sysml::Rule};
use crate::semantic::adapters::SysmlAdapter;
use crate::semantic::graphs::ReferenceIndex;
use crate::semantic::symbol_table::{Symbol, SymbolTable};
use crate::syntax::sysml::ast::parse_file;
use pest::Parser;
#[test]
fn test_visitor_creates_package_symbol() {
let source = "package MyPackage;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("MyPackage").is_some());
}
#[test]
fn test_visitor_creates_definition_symbol() {
let source = "part def Vehicle;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let symbol = resolver.resolve("Vehicle").unwrap();
match symbol {
Symbol::Definition { kind, .. } => assert_eq!(kind, "Part"),
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_qualified_redefinition_does_not_create_duplicate_symbols() {
let source = r#"
package TestPkg {
item def Shell {
item edges {
item vertices;
}
}
item def Disc :> Shell {
item :>> edges {
ref item :>> Shell::edges::vertices;
}
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let shell_count = all_symbols
.iter()
.filter(|sym| sym.name() == "Shell")
.count();
assert_eq!(
shell_count, 1,
"Shell should be defined exactly once, got {shell_count} definitions"
);
}
#[test]
fn test_same_name_in_different_namespaces_creates_two_symbols() {
let source = r#"
package Namespace1 {
item def Shell;
}
package Namespace2 {
item def Shell;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let shell_symbols: Vec<_> = all_symbols
.iter()
.filter(|sym| sym.name() == "Shell")
.collect();
assert_eq!(
shell_symbols.len(),
2,
"Should have exactly 2 Shell definitions in different namespaces, got {}",
shell_symbols.len()
);
let qualified_names: Vec<String> = shell_symbols
.iter()
.filter_map(|symbol| match symbol {
Symbol::Definition { qualified_name, .. } => Some(qualified_name.clone()),
_ => None,
})
.collect();
assert!(qualified_names.contains(&"Namespace1::Shell".to_string()));
assert!(qualified_names.contains(&"Namespace2::Shell".to_string()));
}
#[test]
fn test_comma_separated_redefinitions_do_not_create_duplicate_symbols() {
let source = r#"
package TestPkg {
item def Disc {
attribute semiMajorAxis;
attribute semiMinorAxis;
item shape {
attribute semiMajorAxis;
attribute semiMinorAxis;
}
}
item def Circle {
attribute semiMajorAxis;
attribute semiMinorAxis;
}
item def CircularDisc :> Disc {
item :>> shape : Circle {
attribute :>> Disc::shape::semiMajorAxis, Circle::semiMajorAxis;
attribute :>> Disc::shape::semiMinorAxis, Circle::semiMinorAxis;
}
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have errors from comma-separated redefinitions, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let disc_count = all_symbols
.iter()
.filter(|sym| sym.name() == "Disc")
.count();
let circle_count = all_symbols
.iter()
.filter(|sym| sym.name() == "Circle")
.count();
assert_eq!(
disc_count, 1,
"Disc should be defined exactly once, got {disc_count} definitions"
);
assert_eq!(
circle_count, 1,
"Circle should be defined exactly once, got {circle_count} definitions"
);
}
#[test]
fn test_attribute_reference_in_expression_not_treated_as_definition() {
let source = r#"
package TestPkg {
attribute radius : Real;
item def Circle {
attribute :>> radius [1];
attribute :>> semiMajorAxis [1] = radius;
attribute :>> semiMinorAxis [1] = radius;
}
item def Sphere {
attribute :>> radius [1];
attribute :>> semiAxis1 [1] = radius;
attribute :>> semiAxis2 [1] = radius;
attribute :>> semiAxis3 [1] = radius;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have duplicate symbol errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let radius_count = all_symbols
.iter()
.filter(|sym| sym.name() == "radius")
.count();
assert_eq!(
radius_count, 3,
"radius should appear 3 times (package level + Circle + Sphere), got {radius_count}"
);
}
#[test]
fn test_inline_attribute_definitions_with_same_name_create_duplicates() {
let source = r#"
package TestPkg {
item def Circle {
attribute radius;
}
item def CircularDisc {
item :>> shape : Circle {
attribute :>> radius;
}
item :>> edges : Circle {
attribute :>> radius;
}
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have duplicate symbol errors, got: {:?}",
result.err()
);
}
#[test]
fn test_radius_redefinition_in_multiple_items_no_duplicates() {
let source = r#"
package ShapeItems {
item def CircularDisc {
attribute :>> radius [1];
}
item def Sphere {
attribute :>> radius [1];
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have duplicate symbol errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let radius_count = all_symbols
.iter()
.filter(|sym| sym.name() == "radius")
.count();
assert_eq!(
radius_count, 2,
"Should have 2 radius symbols (one in each item def), got {radius_count}"
);
}
#[test]
fn test_simple_redefinition_creates_child_symbol() {
let source = r#"
package TestPkg {
item def Parent {
attribute radius;
}
item def Child :> Parent {
attribute :>> radius [1];
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let radius_symbols: Vec<_> = all_symbols
.iter()
.filter(|sym| sym.name() == "radius")
.collect();
assert_eq!(
radius_symbols.len(),
2,
"Should have 2 radius symbols (Parent::radius and Child::radius), got {}",
radius_symbols.len()
);
}
#[test]
fn test_visitor_creates_usage_symbol() {
let source = "part myCar : Vehicle;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let symbol = resolver.resolve("myCar").unwrap();
match symbol {
Symbol::Usage { usage_type, .. } => {
assert_eq!(usage_type.as_deref(), Some("Vehicle"));
}
_ => panic!("Expected Usage symbol"),
}
}
#[test]
fn test_visitor_records_specialization_relationship() {
let source = "part def Car :> Vehicle;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
}
#[test]
fn test_visitor_records_typing_relationship() {
let source = "part myCar : Vehicle;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
}
#[test]
fn test_visitor_handles_nested_usage() {
let source = r#"part def Car { attribute mass : Real; }"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("Car").is_some());
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let mass_symbol = all_symbols
.iter()
.find(|sym| sym.name() == "mass")
.expect("Should have 'mass' symbol");
match mass_symbol {
Symbol::Usage { qualified_name, .. } => {
assert_eq!(qualified_name, "Car::mass");
}
_ => panic!("Expected Usage symbol for mass"),
}
}
#[test]
fn test_debug_symbol_table_contents() {
let source = r#"part def Car { attribute mass : Real; }"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
for _symbol in symbol_table.iter_symbols().collect::<Vec<_>>() {}
}
#[test]
fn test_multiple_specializations() {
let source = "part def ElectricCar :> Car, Electric, Vehicle;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
}
#[test]
fn test_multiple_symbols_in_same_scope() {
let source = r#"
part def Car;
part def Truck;
part def Motorcycle;
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("Car").is_some());
assert!(Resolver::new(&symbol_table).resolve("Truck").is_some());
assert!(Resolver::new(&symbol_table).resolve("Motorcycle").is_some());
}
#[test]
fn test_deeply_nested_symbols() {
let source = r#"
part def Vehicle {
part engine {
attribute cylinders : Integer;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("Vehicle").is_some());
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let engine = all_symbols
.iter()
.find(|sym| sym.name() == "engine")
.expect("Should have 'engine' symbol");
match engine {
Symbol::Usage { qualified_name, .. } => {
assert_eq!(qualified_name, "Vehicle::engine");
}
_ => panic!("Expected Usage symbol for engine"),
}
let cylinders = all_symbols
.iter()
.find(|sym| sym.name() == "cylinders")
.expect("Should have 'cylinders' symbol");
match cylinders {
Symbol::Usage { qualified_name, .. } => {
assert_eq!(qualified_name, "Vehicle::engine::cylinders");
}
_ => panic!("Expected Usage symbol for cylinders"),
}
}
#[test]
fn test_different_definition_kinds() {
let source = r#"
part def PartDef;
action def ActionDef;
requirement def ReqDef;
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let part_def = resolver.resolve("PartDef").unwrap();
match part_def {
Symbol::Definition { kind, .. } => assert_eq!(kind, "Part"),
_ => panic!("Expected Definition symbol"),
}
let resolver = Resolver::new(&symbol_table);
let action_def = resolver.resolve("ActionDef").unwrap();
match action_def {
Symbol::Definition { kind, .. } => assert_eq!(kind, "Action"),
_ => panic!("Expected Definition symbol"),
}
let resolver = Resolver::new(&symbol_table);
let req_def = resolver.resolve("ReqDef").unwrap();
match req_def {
Symbol::Definition { kind, .. } => assert_eq!(kind, "Requirement"),
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_scoped_symbols_with_same_name() {
let source = r#"
part def Car {
attribute speed : Real;
}
part def Plane {
attribute speed : Real;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let speed_symbols: Vec<_> = all_symbols
.iter()
.filter(|sym| sym.name() == "speed")
.collect();
assert_eq!(
speed_symbols.len(),
2,
"Should have 2 'speed' symbols in different scopes"
);
let qualified_names: Vec<String> = speed_symbols
.iter()
.map(|symbol| match symbol {
Symbol::Usage { qualified_name, .. } => qualified_name.clone(),
_ => panic!("Expected Usage symbol"),
})
.collect();
assert!(qualified_names.contains(&"Car::speed".to_string()));
assert!(qualified_names.contains(&"Plane::speed".to_string()));
}
#[test]
fn test_nested_packages() {
let source = r#"
package OuterPackage {
package InnerPackage {
part def Component;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(
Resolver::new(&symbol_table)
.resolve("OuterPackage")
.is_some()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let inner = all_symbols
.iter()
.find(|sym| sym.name() == "InnerPackage")
.expect("Should have 'InnerPackage' symbol");
match inner {
Symbol::Package {
documentation: None,
qualified_name,
..
} => {
assert_eq!(qualified_name, "OuterPackage::InnerPackage");
}
_ => panic!("Expected Package symbol for InnerPackage"),
}
}
#[test]
fn test_empty_definition() {
let source = "part def EmptyPart { }";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let symbol = resolver.resolve("EmptyPart").unwrap();
match symbol {
Symbol::Definition { name, .. } => assert_eq!(name, "EmptyPart"),
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_usage_without_type() {
let source = "part untyped;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let symbol = resolver.resolve("untyped").unwrap();
match symbol {
Symbol::Usage { usage_type, .. } => {
assert_eq!(
usage_type, &None,
"Usage without type should have None as usage_type"
);
}
_ => panic!("Expected Usage symbol"),
}
}
#[test]
fn test_qualified_names_are_correct() {
let source = r#"
package Vehicles {
part def Car {
attribute mass : Real;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let vehicles = resolver.resolve("Vehicles").unwrap();
match vehicles {
Symbol::Package {
documentation: None,
qualified_name,
..
} => {
assert_eq!(qualified_name, "Vehicles");
}
_ => panic!("Expected Package symbol"),
}
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let car = all_symbols
.iter()
.find(|sym| sym.name() == "Car")
.expect("Should have 'Car' symbol");
match car {
Symbol::Definition { qualified_name, .. } => {
assert_eq!(qualified_name, "Vehicles::Car");
}
_ => panic!("Expected Definition symbol"),
}
let mass = all_symbols
.iter()
.find(|sym| sym.name() == "mass")
.expect("Should have 'mass' symbol");
match mass {
Symbol::Usage { qualified_name, .. } => {
assert_eq!(qualified_name, "Vehicles::Car::mass");
}
_ => panic!("Expected Usage symbol"),
}
}
#[test]
fn test_multiple_usages_of_same_type() {
let source = r#"
part car1 : Vehicle;
part car2 : Vehicle;
part car3 : Vehicle;
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
for name in ["car1", "car2", "car3"] {
let symbol = resolver
.resolve(name)
.unwrap_or_else(|| panic!("Should have '{name}' symbol"));
match symbol {
Symbol::Usage { usage_type, .. } => {
assert_eq!(usage_type.as_deref(), Some("Vehicle"));
}
_ => panic!("Expected Usage symbol for {name}"),
}
}
}
#[test]
fn test_redefinition_relationship() {
let source = "part def SportsCar :>> Car;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("SportsCar").is_some());
}
#[test]
fn test_alias_definition() {
let source = "alias MyAlias for SomeType;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let symbol = symbol_table
.iter_symbols()
.collect::<Vec<_>>()
.into_iter()
.find(|sym| sym.name() == "MyAlias");
assert!(symbol.is_some(), "Alias should be in symbol table");
match symbol.unwrap() {
Symbol::Alias { target, .. } => {
assert_eq!(target, "SomeType");
}
_ => panic!("Expected Alias symbol"),
}
}
#[test]
fn test_import_statement() {
let source = "import Vehicles::*;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
}
#[test]
fn test_import_indexed_for_hover() {
let source = "package Camera { import PictureTaking::*; }";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
symbol_table.set_current_file(Some("/test.sysml".to_string()));
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let targets = graph.targets();
println!("Indexed targets: {:?}", targets);
assert!(
targets.contains(&"PictureTaking"),
"Import target 'PictureTaking' should be indexed for hover support. Found: {:?}",
targets
);
let refs = graph.get_references("PictureTaking");
assert!(
!refs.is_empty(),
"Should have at least one reference to PictureTaking"
);
let ref_info = &refs[0];
println!(
"Reference info: source={}, span={:?}",
ref_info.source_qname, ref_info.span
);
}
#[test]
fn test_import_quoted_name_indexed_for_hover() {
let source = r#"package 'Robotic Vacuum Cleaner' { part def Robot; }
package Test { import 'Robotic Vacuum Cleaner'::*; }"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
symbol_table.set_current_file(Some("/test.sysml".to_string()));
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let targets = graph.targets();
println!("Indexed targets: {:?}", targets);
assert!(
targets.contains(&"Robotic Vacuum Cleaner"),
"Import target 'Robotic Vacuum Cleaner' should be indexed WITHOUT quotes. Found: {:?}",
targets
);
let resolver = Resolver::new(&symbol_table);
let pkg = resolver.resolve("Robotic Vacuum Cleaner");
assert!(
pkg.is_some(),
"Package 'Robotic Vacuum Cleaner' should be resolvable"
);
}
#[test]
fn test_port_definition_and_usage() {
let source = r#"
port def DataPort;
part def Component {
port input : DataPort;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let port_def = resolver.resolve("DataPort").unwrap();
match port_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Port");
}
_ => panic!("Expected Definition symbol"),
}
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let input_port = all_symbols
.iter()
.find(|sym| sym.name() == "input")
.expect("Should have 'input' port");
match input_port {
Symbol::Usage {
kind,
qualified_name,
..
} => {
assert_eq!(kind, "Port");
assert_eq!(qualified_name, "Component::input");
}
_ => panic!("Expected Usage symbol for port"),
}
}
#[test]
fn test_action_with_parameters() {
let source = r#"
action def ProcessData {
in item data : String;
out item result : Integer;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let action_def = resolver.resolve("ProcessData").unwrap();
match action_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Action");
}
_ => panic!("Expected Definition symbol"),
}
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let has_data = all_symbols.iter().any(|sym| sym.name() == "data");
let has_result = all_symbols.iter().any(|sym| sym.name() == "result");
assert!(has_data, "Should have 'data' parameter");
assert!(has_result, "Should have 'result' parameter");
}
#[test]
fn test_constraint_definition() {
let source = r#"
constraint def SpeedLimit {
attribute maxSpeed : Real;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let constraint_def = resolver.resolve("SpeedLimit").unwrap();
match constraint_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Constraint");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_enumeration_definition() {
let source = r#"
enum def Color {
Red;
Green;
Blue;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let enum_def = resolver.resolve("Color").unwrap();
match enum_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Enumeration");
}
_ => panic!("Expected Definition symbol"),
}
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let has_red = all_symbols.iter().any(|sym| sym.name() == "Red");
let has_green = all_symbols.iter().any(|sym| sym.name() == "Green");
let has_blue = all_symbols.iter().any(|sym| sym.name() == "Blue");
assert!(has_red, "Should have enum value 'Red'");
assert!(has_green, "Should have enum value 'Green'");
assert!(has_blue, "Should have enum value 'Blue'");
}
#[test]
fn test_state_definition() {
let source = r#"
state def VehicleState {
entry; then idle;
state idle;
state moving;
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let state_def = resolver.resolve("VehicleState").unwrap();
match state_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "State");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_connection_definition() {
let source = "connection def DataFlow;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let conn_def = resolver.resolve("DataFlow").unwrap();
match conn_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Connection");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_interface_definition() {
let source = "interface def NetworkInterface;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let intf_def = resolver.resolve("NetworkInterface").unwrap();
match intf_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Interface");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_allocation_definition() {
let source = "allocation def ResourceAllocation;";
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let alloc_def = resolver.resolve("ResourceAllocation").unwrap();
match alloc_def {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Allocation");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_mixed_definitions_and_usages() {
let source = r#"
part def Engine;
part def Wheel;
part def Car {
part engine : Engine;
part wheel1 : Wheel;
part wheel2 : Wheel;
}
part myCar : Car;
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
assert!(Resolver::new(&symbol_table).resolve("Engine").is_some());
assert!(Resolver::new(&symbol_table).resolve("Wheel").is_some());
assert!(Resolver::new(&symbol_table).resolve("Car").is_some());
assert!(Resolver::new(&symbol_table).resolve("myCar").is_some());
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
assert!(all_symbols.iter().any(|sym| sym.name() == "engine"));
assert!(all_symbols.iter().any(|sym| sym.name() == "wheel1"));
assert!(all_symbols.iter().any(|sym| sym.name() == "wheel2"));
}
#[test]
fn test_concern_and_requirement() {
let source = r#"
concern def Safety;
requirement def SafetyRequirement;
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let concern = resolver.resolve("Safety").unwrap();
match concern {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "UseCase"); }
_ => panic!("Expected Definition symbol"),
}
let resolver = Resolver::new(&symbol_table);
let requirement = resolver.resolve("SafetyRequirement").unwrap();
match requirement {
Symbol::Definition { kind, .. } => {
assert_eq!(kind, "Requirement");
}
_ => panic!("Expected Definition symbol"),
}
}
#[test]
fn test_identifier_in_default_value_not_treated_as_definition() {
let source = r#"
package TestPkg {
action def Performance {
attribute redefines dispatchScope default thisPerformance;
attribute thisPerformance: Performance [1] default self;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
let result = adapter.populate(&file);
assert!(
result.is_ok(),
"Should not have duplicate symbol errors, got: {:?}",
result.err()
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let this_perf_count = all_symbols
.iter()
.filter(|sym| sym.name() == "thisPerformance")
.count();
assert_eq!(
this_perf_count, 1,
"Should have exactly one 'thisPerformance' definition, got {this_perf_count}"
);
}
#[test]
fn test_constraint_def_with_in_parameters_extracts_typing() {
let source = r#"
constraint def MassConstraint {
in totalMass : MassValue;
in partMasses : MassValue[0..*];
totalMass == sum(partMasses)
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
let constraint_def = resolver.resolve("MassConstraint");
assert!(
constraint_def.is_some(),
"Should have MassConstraint definition"
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let has_total_mass = all_symbols.iter().any(|sym| sym.name() == "totalMass");
let has_part_masses = all_symbols.iter().any(|sym| sym.name() == "partMasses");
assert!(has_total_mass, "Should have 'totalMass' parameter");
assert!(has_part_masses, "Should have 'partMasses' parameter");
}
#[test]
fn test_assert_constraint_usage_with_in_parameters() {
let source = r#"
part def Vehicle5 {
assert constraint ml : MassLimit {
in mass = m;
in maxMass = 2500;
}
}
"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
assert!(
resolver.resolve("Vehicle5").is_some(),
"Should have Vehicle5 definition"
);
assert!(
resolver.resolve("Vehicle5::ml").is_some(),
"Should have ml constraint usage"
);
let all_symbols = symbol_table.iter_symbols().collect::<Vec<_>>();
let has_mass = all_symbols.iter().any(|sym| sym.name() == "mass");
let has_max_mass = all_symbols.iter().any(|sym| sym.name() == "maxMass");
assert!(has_mass, "Should have 'mass' parameter");
assert!(has_max_mass, "Should have 'maxMass' parameter");
}
#[test]
fn test_feature_chain_source_parses_correctly() {
let source = r#"package Camera {
action def TakePicture {
in item focus;
out item photo;
}
part def FocusingSubsystem {
perform action takePicture : TakePicture {
in item :>> focus;
}
perform action :> takePicture.focus;
}
}"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
println!("=== All Symbols ===");
for sym in symbol_table.iter_symbols() {
println!(" {} (span: {:?})", sym.qualified_name(), sym.span());
}
assert!(
resolver.resolve("Camera").is_some(),
"Should have Camera package"
);
assert!(
resolver.resolve("Camera::TakePicture").is_some(),
"Should have TakePicture action def"
);
assert!(
resolver.resolve("Camera::FocusingSubsystem").is_some(),
"Should have FocusingSubsystem part def"
);
assert!(
resolver.resolve("Camera::TakePicture::focus").is_some(),
"Should have focus item"
);
assert!(
resolver.resolve("Camera::TakePicture::photo").is_some(),
"Should have photo item"
);
assert!(
resolver
.resolve("Camera::FocusingSubsystem::takePicture")
.is_some(),
"Should have takePicture perform"
);
}
#[test]
fn test_resolve_member_through_subsets() {
let source = r#"package PictureTaking {
action def TakePicture {
action focus;
action shoot;
}
action takePicture : TakePicture;
}
part def Camera {
perform action takePicture :> PictureTaking::takePicture;
}"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
let mut symbol_table = SymbolTable::new();
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
println!("=== All Symbols ===");
for sym in symbol_table.iter_symbols() {
if let Symbol::Usage {
usage_type,
subsets,
..
} = sym
{
println!(
" {} (USAGE - type: {:?}, subsets: {:?})",
sym.qualified_name(),
usage_type,
subsets
);
} else {
println!(" {} (subsets: {:?})", sym.qualified_name(), sym.subsets());
}
}
let camera_takepicture = resolver
.resolve("Camera::takePicture")
.expect("Should have Camera::takePicture");
println!(
"\nCamera::takePicture subsets: {:?}",
camera_takepicture.subsets()
);
let pt_takepicture = resolver
.resolve("PictureTaking::takePicture")
.expect("Should have PictureTaking::takePicture");
println!(
"PictureTaking::takePicture: {:?}",
pt_takepicture.qualified_name()
);
let focus_symbol = resolver
.resolve("PictureTaking::TakePicture::focus")
.expect("Should have focus in TakePicture action def");
println!(
"PictureTaking::TakePicture::focus: {}",
focus_symbol.qualified_name()
);
let camera_scope_id = resolver
.resolve("Camera")
.expect("Should have Camera")
.scope_id();
println!("\n=== Testing resolve_member ===");
println!(
"Looking for 'focus' as member of {}",
camera_takepicture.qualified_name()
);
let result = resolver.resolve_member("focus", camera_takepicture, camera_scope_id);
assert!(
result.is_some(),
"resolve_member should find 'focus' through subsets relationship"
);
let resolved = result.unwrap();
println!("Found: {}", resolved.qualified_name());
assert!(
resolved.qualified_name().contains("focus"),
"Resolved symbol should be 'focus'"
);
}
#[test]
fn test_feature_chain_redefine_indexes_correctly() {
let source = r#"package TimeTest {
part def Clock {
attribute currentTime;
}
part def Transport {
part localClock : Clock;
attribute :>> localClock.currentTime = 0;
}
}"#;
let mut pairs = SysMLParser::parse(Rule::file, source).unwrap();
let file = parse_file(&mut pairs).unwrap();
println!("=== Parsed AST ===");
for elem in &file.elements {
match elem {
crate::syntax::sysml::ast::Element::Package(pkg) => {
println!(" Package: {:?}", pkg.name);
for inner_elem in &pkg.elements {
if let crate::syntax::sysml::ast::Element::Definition(def) = inner_elem {
println!(" Definition: {:?} (kind: {:?})", def.name, def.kind);
for body_member in &def.body {
match body_member {
crate::syntax::sysml::ast::enums::DefinitionMember::Usage(u) => {
println!(
" Usage: {} (kind: {:?})",
u.name.as_deref().unwrap_or("<anonymous>"),
u.kind
);
println!(" redefines: {:?}", u.relationships.redefines);
println!(" subsets: {:?}", u.relationships.subsets);
}
crate::syntax::sysml::ast::enums::DefinitionMember::Comment(_c) => {
println!(" Comment");
}
crate::syntax::sysml::ast::enums::DefinitionMember::Import(i) => {
println!(" Import: {}", i.path);
}
}
}
}
}
}
crate::syntax::sysml::ast::Element::Definition(def) => {
println!(" Definition: {:?} (kind: {:?})", def.name, def.kind);
}
_ => {
println!(" Other element");
}
}
}
let mut symbol_table = SymbolTable::new();
symbol_table.set_current_file(Some("/test.sysml".to_string()));
let mut graph = ReferenceIndex::new();
let mut adapter = SysmlAdapter::with_index(&mut symbol_table, &mut graph);
adapter.populate(&file).unwrap();
let resolver = Resolver::new(&symbol_table);
println!("=== All Symbols ===");
for sym in symbol_table.iter_symbols() {
println!(" {}", sym.qualified_name());
}
println!("\n=== Reference Targets ===");
for target in graph.targets() {
println!(" {}", target);
}
assert!(
resolver.resolve("TimeTest::Clock").is_some(),
"Should have Clock"
);
assert!(
resolver.resolve("TimeTest::Clock::currentTime").is_some(),
"Should have Clock::currentTime"
);
assert!(
resolver.resolve("TimeTest::Transport").is_some(),
"Should have Transport"
);
assert!(
resolver
.resolve("TimeTest::Transport::localClock")
.is_some(),
"Should have localClock"
);
let localclock_refs = graph.get_references("localClock");
println!("\n=== References to 'localClock' ===");
for r in &localclock_refs {
println!(
" source={}, chain_context={:?}",
r.source_qname, r.chain_context
);
}
let currenttime_refs = graph.get_references("currentTime");
println!("\n=== References to 'currentTime' ===");
for r in ¤ttime_refs {
println!(
" source={}, chain_context={:?}",
r.source_qname, r.chain_context
);
}
let has_chain_ref = currenttime_refs.iter().any(|r| r.chain_context.is_some());
assert!(
has_chain_ref,
"currentTime reference should have chain_context for feature chain resolution"
);
}