use super::model::{Element, ElementId, ElementKind, Model, PropertyValue, Visibility};
use crate::base::FileId;
use crate::hir::{
HirRelationship, HirSymbol, RelationshipKind as HirRelKind, RootDatabase, SymbolKind,
};
use crate::parser::{Direction, Multiplicity};
use std::sync::Arc;
pub fn model_from_database(_db: &RootDatabase) -> Model {
Model::new()
}
pub fn symbols_from_model(model: &Model) -> Vec<HirSymbol> {
let mut symbols = Vec::new();
for element in model.elements.values() {
if element.kind.is_relationship() {
continue;
}
let kind: SymbolKind = element.kind.into();
let qualified_name: Arc<str> = element
.qualified_name
.clone()
.or_else(|| element.name.clone())
.map(|n| n.to_string().into())
.unwrap_or_else(|| element.id.as_str().into());
let name: Arc<str> = element
.name
.clone()
.map(|n| n.to_string().into())
.unwrap_or_else(|| {
qualified_name
.rsplit("::")
.next()
.unwrap_or(qualified_name.as_ref())
.into()
});
let relationships: Vec<HirRelationship> = model
.rel_elements_from(&element.id)
.filter_map(|re| {
let hir_kind = element_kind_to_hir(re.kind)?;
let target_name: Arc<str> = re
.target()
.and_then(|tid| model.elements.get(tid))
.and_then(|target_elem| {
target_elem
.qualified_name
.clone()
.or_else(|| target_elem.name.clone())
})
.map(|n| n.to_string().into())
.unwrap_or_else(|| {
re.target()
.map(|tid| tid.as_str().into())
.unwrap_or_else(|| "".into())
});
Some(HirRelationship {
kind: hir_kind,
target: target_name.clone(),
resolved_target: Some(target_name), start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
})
})
.collect();
let supertypes: Vec<Arc<str>> = relationships
.iter()
.filter(|r| r.kind == HirRelKind::Specializes)
.map(|r| r.target.clone())
.collect();
let symbol = HirSymbol {
name,
short_name: None, qualified_name,
element_id: element.id.as_str().into(), kind,
file: FileId::new(0), start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: element.documentation.as_ref().map(|d| d.to_string().into()),
supertypes,
relationships,
type_refs: Vec::new(),
is_public: true, view_data: None,
metadata_annotations: Vec::new(),
is_abstract: element.is_abstract,
is_variation: element.is_variation,
is_readonly: element.is_readonly,
is_derived: element.is_derived,
is_parallel: element.is_parallel,
is_individual: element.is_individual,
is_end: element.is_end,
is_default: element.is_default,
is_ordered: element.is_ordered,
is_nonunique: element.is_nonunique,
is_portion: element.is_portion,
direction: element.properties.get("direction").and_then(|v| match v {
PropertyValue::String(s) => match s.as_ref() {
"in" => Some(Direction::In),
"out" => Some(Direction::Out),
"inout" => Some(Direction::InOut),
_ => None,
},
_ => None,
}),
multiplicity: extract_multiplicity(element, model),
value: None,
};
symbols.push(symbol);
}
symbols
}
fn extract_multiplicity(element: &Element, model: &Model) -> Option<Multiplicity> {
for child_id in &element.owned_elements {
if let Some(child) = model.elements.get(child_id) {
if child.kind == ElementKind::MultiplicityRange {
return multiplicity_from_range(child, model);
}
for grandchild_id in &child.owned_elements {
if let Some(grandchild) = model.elements.get(grandchild_id) {
if grandchild.kind == ElementKind::MultiplicityRange {
return multiplicity_from_range(grandchild, model);
}
}
}
}
}
None
}
fn multiplicity_from_range(range: &Element, model: &Model) -> Option<Multiplicity> {
let mut lower: Option<u64> = None;
let mut upper: Option<u64> = None;
for (i, child_id) in range.owned_elements.iter().enumerate() {
if let Some(child) = model.elements.get(child_id) {
match child.kind {
ElementKind::LiteralInteger => {
if let Some(PropertyValue::Integer(v)) = child.properties.get("value") {
let val = (*v).max(0) as u64;
if i == 0 {
lower = Some(val);
} else {
upper = Some(val);
}
}
}
ElementKind::LiteralInfinity => {
if i == 0 {
lower = None;
}
}
_ => {}
}
}
}
if lower.is_some() || upper.is_some() {
Some(Multiplicity { lower, upper })
} else {
None
}
}
fn element_kind_to_hir(kind: ElementKind) -> Option<HirRelKind> {
match kind {
ElementKind::Specialization => Some(HirRelKind::Specializes),
ElementKind::FeatureTyping => Some(HirRelKind::TypedBy),
ElementKind::Redefinition => Some(HirRelKind::Redefines),
ElementKind::Subsetting => Some(HirRelKind::Subsets),
ElementKind::Satisfaction => Some(HirRelKind::Satisfies),
ElementKind::Verification => Some(HirRelKind::Verifies),
ElementKind::ActionUsage => Some(HirRelKind::Performs), _ => None,
}
}
impl From<ElementKind> for SymbolKind {
fn from(kind: ElementKind) -> Self {
match kind {
ElementKind::Package | ElementKind::LibraryPackage => SymbolKind::Package,
ElementKind::PartDefinition => SymbolKind::PartDefinition,
ElementKind::ItemDefinition => SymbolKind::ItemDefinition,
ElementKind::ActionDefinition => SymbolKind::ActionDefinition,
ElementKind::PortDefinition => SymbolKind::PortDefinition,
ElementKind::AttributeDefinition => SymbolKind::AttributeDefinition,
ElementKind::ConnectionDefinition => SymbolKind::ConnectionDefinition,
ElementKind::InterfaceDefinition => SymbolKind::InterfaceDefinition,
ElementKind::AllocationDefinition => SymbolKind::AllocationDefinition,
ElementKind::RequirementDefinition => SymbolKind::RequirementDefinition,
ElementKind::ConstraintDefinition => SymbolKind::ConstraintDefinition,
ElementKind::StateDefinition => SymbolKind::StateDefinition,
ElementKind::CalculationDefinition => SymbolKind::CalculationDefinition,
ElementKind::UseCaseDefinition => SymbolKind::UseCaseDefinition,
ElementKind::AnalysisCaseDefinition => SymbolKind::AnalysisCaseDefinition,
ElementKind::ConcernDefinition => SymbolKind::ConcernDefinition,
ElementKind::ViewDefinition => SymbolKind::ViewDefinition,
ElementKind::ViewpointDefinition => SymbolKind::ViewpointDefinition,
ElementKind::RenderingDefinition => SymbolKind::RenderingDefinition,
ElementKind::EnumerationDefinition => SymbolKind::EnumerationDefinition,
ElementKind::MetadataDefinition => SymbolKind::MetadataDefinition,
ElementKind::DataType => SymbolKind::DataType,
ElementKind::Class => SymbolKind::Class,
ElementKind::Structure => SymbolKind::Structure,
ElementKind::Behavior => SymbolKind::Behavior,
ElementKind::Function => SymbolKind::Function,
ElementKind::Association | ElementKind::AssociationStructure => SymbolKind::Association,
ElementKind::Interaction => SymbolKind::Interaction,
ElementKind::PartUsage => SymbolKind::PartUsage,
ElementKind::ItemUsage => SymbolKind::ItemUsage,
ElementKind::ActionUsage => SymbolKind::ActionUsage,
ElementKind::PortUsage => SymbolKind::PortUsage,
ElementKind::AttributeUsage => SymbolKind::AttributeUsage,
ElementKind::ConnectionUsage => SymbolKind::ConnectionUsage,
ElementKind::InterfaceUsage => SymbolKind::InterfaceUsage,
ElementKind::AllocationUsage => SymbolKind::AllocationUsage,
ElementKind::RequirementUsage => SymbolKind::RequirementUsage,
ElementKind::ConstraintUsage => SymbolKind::ConstraintUsage,
ElementKind::StateUsage => SymbolKind::StateUsage,
ElementKind::TransitionUsage => SymbolKind::TransitionUsage,
ElementKind::CalculationUsage => SymbolKind::CalculationUsage,
ElementKind::ReferenceUsage => SymbolKind::ReferenceUsage,
ElementKind::OccurrenceUsage => SymbolKind::OccurrenceUsage,
ElementKind::FlowConnectionUsage => SymbolKind::FlowConnectionUsage,
ElementKind::Import | ElementKind::NamespaceImport | ElementKind::MembershipImport => {
SymbolKind::Import
}
ElementKind::Comment | ElementKind::Documentation => SymbolKind::Comment,
_ => SymbolKind::Other,
}
}
}
pub fn model_from_symbols(symbols: &[HirSymbol]) -> Model {
let mut model = Model::new();
let mut rel_counter = 0u64;
let name_to_id: std::collections::HashMap<&str, &str> = symbols
.iter()
.map(|s| (s.qualified_name.as_ref(), s.element_id.as_ref()))
.collect();
for symbol in symbols {
let id = ElementId::new(symbol.element_id.as_ref());
let kind: ElementKind = symbol.kind.into();
if symbol.kind == SymbolKind::Import {
let import_name = symbol.name.as_ref();
let is_wildcard = import_name.ends_with("::*");
let namespace = if is_wildcard {
import_name.trim_end_matches("::*")
} else {
import_name
};
let owner_id = symbol.qualified_name.find("::import:").and_then(|idx| {
let parent_qn = &symbol.qualified_name[..idx];
name_to_id.get(parent_qn).map(|&eid| ElementId::new(eid))
});
let import_kind = if is_wildcard {
ElementKind::NamespaceImport
} else {
ElementKind::MembershipImport
};
let target_id = ElementId::new(namespace);
let source_id = owner_id.clone().unwrap_or_else(|| id.clone());
let rel_id = model.add_rel(
id.clone(),
import_kind,
source_id,
target_id,
owner_id.clone(),
);
let attr_name = if is_wildcard {
"importedNamespace"
} else {
"importedMembership"
};
if let Some(rel_element) = model.get_mut(&rel_id) {
rel_element.properties.insert(
Arc::from(attr_name),
PropertyValue::String(Arc::from(namespace)),
);
rel_element.visibility = if symbol.is_public {
Visibility::Public
} else {
Visibility::Private
};
}
if let Some(ref oid) = owner_id {
if let Some(parent) = model.get_mut(oid) {
if !parent.owned_elements.contains(&id) {
parent.owned_elements.push(id.clone());
}
}
}
continue;
}
let owner = if symbol.qualified_name.contains("::") {
let parent = symbol.qualified_name.rsplit_once("::").map(|(p, _)| p);
parent.and_then(|p| name_to_id.get(p).map(|&id| ElementId::new(id)))
} else {
None
};
let mut element = Element::new(id.clone(), kind)
.with_name(symbol.name.as_ref())
.with_qualified_name(symbol.qualified_name.as_ref());
if let Some(ref owner_id) = owner {
element = element.with_owner(owner_id.clone());
}
element.is_abstract = symbol.is_abstract;
element.is_variation = symbol.is_variation;
element.is_readonly = symbol.is_readonly;
element.is_derived = symbol.is_derived;
element.is_parallel = symbol.is_parallel;
element.is_individual = symbol.is_individual;
element.is_end = symbol.is_end;
element.is_default = symbol.is_default;
element.is_ordered = symbol.is_ordered;
element.is_nonunique = symbol.is_nonunique;
element.is_portion = symbol.is_portion;
element.short_name = symbol.short_name.clone();
element.documentation = symbol.doc.clone();
if let Some(ref dir) = symbol.direction {
let dir_str = match dir {
Direction::In => "in",
Direction::Out => "out",
Direction::InOut => "inout",
};
element.properties.insert(
Arc::from("direction"),
PropertyValue::String(Arc::from(dir_str)),
);
}
if let Some(ref mult) = symbol.multiplicity {
if let Some(lower) = mult.lower {
element.properties.insert(
Arc::from("multiplicityLower"),
PropertyValue::String(Arc::from(lower.to_string())),
);
}
if let Some(upper) = mult.upper {
element.properties.insert(
Arc::from("multiplicityUpper"),
PropertyValue::String(Arc::from(upper.to_string())),
);
}
}
if symbol.kind == SymbolKind::Alias {
if let Some(target) = symbol.supertypes.first() {
element.properties.insert(
Arc::from("aliasTarget"),
PropertyValue::String(Arc::from(target.as_ref())),
);
}
}
model.add_element(element);
let mut child_owner_pairs: Vec<(ElementId, ElementId)> = Vec::new();
for (id, element) in model.elements.iter() {
if let Some(owner_id) = &element.owner {
child_owner_pairs.push((id.clone(), owner_id.clone()));
}
}
for (_id, element) in model.elements.iter_mut() {
element.owned_elements.clear();
}
for (child_id, owner_id) in child_owner_pairs {
if let Some(parent) = model.get_mut(&owner_id) {
parent.owned_elements.push(child_id);
}
}
for hir_rel in &symbol.relationships {
let element_kind = hir_relationship_kind_to_element_kind(&hir_rel.kind);
if let Some(ek) = element_kind {
rel_counter += 1;
let rel_id = ElementId::new(format!("rel_{}", rel_counter));
let target_name = hir_rel
.resolved_target
.as_deref()
.unwrap_or(hir_rel.target.as_ref());
let target_id = name_to_id
.get(target_name)
.or_else(|| {
name_to_id.get(hir_rel.target.as_ref())
})
.or_else(|| {
let mut ns = symbol.qualified_name.as_ref();
while let Some((parent, _)) = ns.rsplit_once("::") {
let candidate = format!("{}::{}", parent, hir_rel.target);
if let Some(found) = name_to_id.get(candidate.as_str()) {
return Some(found);
}
ns = parent;
}
None
})
.or_else(|| {
let suffix = format!("::{}", hir_rel.target);
let mut matches: Vec<&&str> = name_to_id
.keys()
.filter(|qn| qn.ends_with(suffix.as_str()))
.collect();
if matches.len() == 1 {
name_to_id.get(*matches[0])
} else if matches.len() > 1 {
matches.sort_by_key(|qn| {
let common = symbol
.qualified_name
.as_ref()
.chars()
.zip(qn.chars())
.take_while(|(a, b)| a == b)
.count();
std::cmp::Reverse(common)
});
name_to_id.get(*matches[0])
} else {
None
}
})
.map(|&id| ElementId::new(id));
let target_id = target_id.unwrap_or_else(|| {
let ext_id = ElementId::new(format!("_ext_{}", hir_rel.target));
if !model.elements.contains_key(&ext_id) {
let mut stub = Element::new(ext_id.clone(), ElementKind::Other)
.with_name(hir_rel.target.as_ref());
stub.properties
.insert(Arc::from("_external"), PropertyValue::Boolean(true));
model.add_element(stub);
}
ext_id
});
model.add_rel(
rel_id.clone(),
ek,
id.clone(),
target_id.clone(),
Some(id.clone()),
);
let target_attr = match ek {
ElementKind::FeatureTyping => "type",
ElementKind::Specialization => "general",
ElementKind::Redefinition => "redefinedFeature",
ElementKind::Subsetting => "subsettedFeature",
_ => "target",
};
if let Some(rel_element) = model.get_mut(&rel_id) {
rel_element.properties.insert(
Arc::from(target_attr),
PropertyValue::String(Arc::from(target_id.as_str())),
);
}
if let Some(parent) = model.get_mut(&id) {
parent.owned_elements.push(rel_id);
}
}
}
if let Some(ref value) = symbol.value {
use crate::parser::ValueExpression;
let fv_id = ElementId::new(format!("{}-fv", symbol.element_id));
let lit_id = ElementId::new(format!("{}-fv-lit", symbol.element_id));
let (lit_kind, lit_prop_value) = match value {
ValueExpression::LiteralInteger(v) => {
(ElementKind::LiteralInteger, PropertyValue::Integer(*v))
}
ValueExpression::LiteralReal(v) => {
(ElementKind::LiteralReal, PropertyValue::Real(*v))
}
ValueExpression::LiteralString(s) => (
ElementKind::LiteralString,
PropertyValue::String(Arc::from(s.as_str())),
),
ValueExpression::LiteralBoolean(b) => {
(ElementKind::LiteralBoolean, PropertyValue::Boolean(*b))
}
ValueExpression::Null => (
ElementKind::NullExpression,
PropertyValue::String(Arc::from("null")),
),
ValueExpression::Expression(text) => (
ElementKind::FeatureReferenceExpression,
PropertyValue::String(Arc::from(text.as_str())),
),
};
let mut lit_element = Element::new(lit_id.clone(), lit_kind).with_owner(fv_id.clone());
lit_element
.properties
.insert(Arc::from("value"), lit_prop_value);
model.add_element(lit_element);
let mut fv_element =
Element::new(fv_id.clone(), ElementKind::FeatureValue).with_owner(id.clone());
fv_element.owned_elements.push(lit_id);
model.add_element(fv_element);
if let Some(parent) = model.get_mut(&id) {
parent.owned_elements.push(fv_id);
}
}
}
model.wrap_children_in_memberships();
model
}
fn hir_relationship_kind_to_element_kind(
kind: &crate::hir::RelationshipKind,
) -> Option<ElementKind> {
use crate::hir::RelationshipKind as HirRelKind;
match kind {
HirRelKind::Specializes => Some(ElementKind::Specialization),
HirRelKind::TypedBy => Some(ElementKind::FeatureTyping),
HirRelKind::Redefines => Some(ElementKind::Redefinition),
HirRelKind::Subsets => Some(ElementKind::Subsetting),
HirRelKind::References => None, HirRelKind::Satisfies => Some(ElementKind::Satisfaction),
HirRelKind::Performs => Some(ElementKind::ActionUsage),
HirRelKind::Exhibits => None,
HirRelKind::Includes => None,
HirRelKind::Asserts => None,
HirRelKind::Verifies => Some(ElementKind::Verification),
}
}
impl From<SymbolKind> for ElementKind {
fn from(kind: SymbolKind) -> Self {
match kind {
SymbolKind::Package => ElementKind::Package,
SymbolKind::PartDefinition => ElementKind::PartDefinition,
SymbolKind::ItemDefinition => ElementKind::ItemDefinition,
SymbolKind::ActionDefinition => ElementKind::ActionDefinition,
SymbolKind::PortDefinition => ElementKind::PortDefinition,
SymbolKind::AttributeDefinition => ElementKind::AttributeDefinition,
SymbolKind::ConnectionDefinition => ElementKind::ConnectionDefinition,
SymbolKind::InterfaceDefinition => ElementKind::InterfaceDefinition,
SymbolKind::AllocationDefinition => ElementKind::AllocationDefinition,
SymbolKind::RequirementDefinition => ElementKind::RequirementDefinition,
SymbolKind::ConstraintDefinition => ElementKind::ConstraintDefinition,
SymbolKind::StateDefinition => ElementKind::StateDefinition,
SymbolKind::CalculationDefinition => ElementKind::CalculationDefinition,
SymbolKind::UseCaseDefinition => ElementKind::UseCaseDefinition,
SymbolKind::AnalysisCaseDefinition => ElementKind::AnalysisCaseDefinition,
SymbolKind::ConcernDefinition => ElementKind::ConcernDefinition,
SymbolKind::ViewDefinition => ElementKind::ViewDefinition,
SymbolKind::ViewpointDefinition => ElementKind::ViewpointDefinition,
SymbolKind::RenderingDefinition => ElementKind::RenderingDefinition,
SymbolKind::EnumerationDefinition => ElementKind::EnumerationDefinition,
SymbolKind::DataType => ElementKind::DataType,
SymbolKind::Class => ElementKind::Class,
SymbolKind::Structure => ElementKind::Structure,
SymbolKind::Behavior => ElementKind::Behavior,
SymbolKind::Function => ElementKind::Function,
SymbolKind::Association => ElementKind::Association,
SymbolKind::MetadataDefinition => ElementKind::MetadataDefinition,
SymbolKind::Interaction => ElementKind::Interaction,
SymbolKind::PartUsage => ElementKind::PartUsage,
SymbolKind::ItemUsage => ElementKind::ItemUsage,
SymbolKind::ActionUsage => ElementKind::ActionUsage,
SymbolKind::PortUsage => ElementKind::PortUsage,
SymbolKind::AttributeUsage => ElementKind::AttributeUsage,
SymbolKind::ConnectionUsage => ElementKind::ConnectionUsage,
SymbolKind::InterfaceUsage => ElementKind::InterfaceUsage,
SymbolKind::AllocationUsage => ElementKind::AllocationUsage,
SymbolKind::RequirementUsage => ElementKind::RequirementUsage,
SymbolKind::ConstraintUsage => ElementKind::ConstraintUsage,
SymbolKind::StateUsage => ElementKind::StateUsage,
SymbolKind::TransitionUsage => ElementKind::TransitionUsage,
SymbolKind::CalculationUsage => ElementKind::CalculationUsage,
SymbolKind::ReferenceUsage => ElementKind::ReferenceUsage,
SymbolKind::OccurrenceUsage => ElementKind::OccurrenceUsage,
SymbolKind::FlowConnectionUsage => ElementKind::FlowConnectionUsage,
SymbolKind::Import => ElementKind::Import,
SymbolKind::Comment => ElementKind::Comment,
SymbolKind::Alias => ElementKind::Alias,
_ => ElementKind::Other,
}
}
}
pub fn apply_metadata_to_host(
host: &mut crate::ide::AnalysisHost,
metadata: &super::metadata::ImportMetadata,
) {
use std::sync::Arc;
let _ = host.analysis();
host.update_symbols(|symbol| {
if let Some(meta) = metadata.get_element(&symbol.qualified_name) {
if let Some(id) = &meta.original_id {
symbol.element_id = Arc::from(id.as_str());
}
}
});
}
#[derive(Debug)]
pub struct ApplyEditsResult {
pub rendered_text: String,
pub parse_errors: Vec<crate::syntax::parser::ParseError>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::FileId;
use crate::hir::{FileText, file_symbols_from_text};
#[test]
fn test_model_from_database_empty() {
let db = RootDatabase::new();
let model = model_from_database(&db);
assert!(
model.elements.is_empty(),
"Empty database should produce empty model"
);
assert!(
model.roots.is_empty(),
"Empty database should have no root elements"
);
assert_eq!(
model.relationship_count(),
0,
"Empty database should have no relationships"
);
}
#[test]
fn test_model_from_database_single_package() {
let db = RootDatabase::new();
let sysml = "package TestPackage;";
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols = file_symbols_from_text(&db, file_text);
assert!(!symbols.is_empty(), "Should have parsed the package");
let model = model_from_symbols(&symbols);
assert_eq!(model.elements.len(), 1, "Should have one element");
assert_eq!(model.roots.len(), 1, "Should have one root element");
let root_id = &model.roots[0];
let element = model
.elements
.get(root_id)
.expect("Root element should exist");
assert_eq!(element.kind, super::super::model::ElementKind::Package);
assert_eq!(element.name.as_deref(), Some("TestPackage"));
}
#[test]
fn test_model_from_database_with_parts() {
let db = RootDatabase::new();
let sysml = r#"
package Vehicle {
part def Car;
part def Engine;
}
"#;
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols = file_symbols_from_text(&db, file_text);
let model = model_from_symbols(&symbols);
assert_eq!(model.roots.len(), 1, "Should have one root (Vehicle)");
let root_id = &model.roots[0];
let members = model.owned_members(root_id);
assert_eq!(members.len(), 2, "Vehicle should own 2 content members");
let car = model
.elements
.values()
.find(|e| e.name.as_deref() == Some("Car"))
.expect("Car should exist");
assert_eq!(car.kind, super::super::model::ElementKind::PartDefinition);
assert!(car.owner.is_some(), "Car should have an owner");
let direct_owner = model
.elements
.get(car.owner.as_ref().unwrap())
.expect("Direct owner should exist");
assert!(
direct_owner.kind.is_membership(),
"Car's direct owner should be a membership"
);
let logical_owner = model
.elements
.get(direct_owner.owner.as_ref().unwrap())
.expect("Logical owner should exist");
assert_eq!(
logical_owner.name.as_deref(),
Some("Vehicle"),
"Logical owner should be Vehicle"
);
}
#[test]
fn test_model_from_database_relationships() {
let db = RootDatabase::new();
let sysml = r#"
package Types {
part def Vehicle;
part def Car :> Vehicle;
}
"#;
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols = file_symbols_from_text(&db, file_text);
let model = model_from_symbols(&symbols);
assert!(model.relationship_count() > 0, "Should have relationships");
let specialization = model
.iter_relationship_elements()
.find(|e| e.kind == super::super::model::ElementKind::Specialization)
.expect("Should have a specialization");
let rel_data = specialization.relationship.as_ref().unwrap();
let source_elem = model
.elements
.get(&rel_data.source[0])
.expect("Source element should exist");
let target_elem = model
.elements
.get(&rel_data.target[0])
.expect("Target element should exist");
assert_eq!(
source_elem.name.as_deref(),
Some("Car"),
"Source should be Car"
);
assert_eq!(
target_elem.name.as_deref(),
Some("Vehicle"),
"Target should be Vehicle"
);
}
#[test]
fn test_roundtrip_through_xmi() {
use super::super::{ModelFormat, Xmi};
let db = RootDatabase::new();
let sysml = "package Vehicles;";
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let symbols = file_symbols_from_text(&db, file_text);
let model = model_from_symbols(&symbols);
assert_eq!(model.elements.len(), 1, "Should have one package");
let original_id = model.elements.keys().next().unwrap().clone();
let xmi_bytes = Xmi.write(&model).expect("Should write XMI");
let roundtrip_model = Xmi.read(&xmi_bytes).expect("Should read XMI");
assert!(
!roundtrip_model.elements.is_empty(),
"Should have at least one element after roundtrip"
);
assert!(
roundtrip_model.elements.contains_key(&original_id),
"Should find the original element by ID after roundtrip"
);
}
#[test]
fn test_symbols_from_empty_model() {
let model = Model::new();
let symbols = symbols_from_model(&model);
assert!(symbols.is_empty(), "Empty model should produce no symbols");
}
#[test]
fn test_symbols_from_model_single_package() {
let mut model = Model::new();
let pkg = Element::new(ElementId::new("TestPackage"), ElementKind::Package)
.with_name("TestPackage");
model.add_element(pkg);
let symbols = symbols_from_model(&model);
assert_eq!(symbols.len(), 1, "Should have one symbol");
assert_eq!(symbols[0].name.as_ref(), "TestPackage");
assert_eq!(symbols[0].kind, SymbolKind::Package);
assert_eq!(symbols[0].qualified_name.as_ref(), "TestPackage");
}
#[test]
fn test_symbols_from_model_with_part_definitions() {
let mut model = Model::new();
let pkg =
Element::new(ElementId::new("Vehicle"), ElementKind::Package).with_name("Vehicle");
model.add_element(pkg);
let car = Element::new(ElementId::new("Vehicle::Car"), ElementKind::PartDefinition)
.with_name("Car")
.with_owner(ElementId::new("Vehicle"));
model.add_element(car);
let engine = Element::new(
ElementId::new("Vehicle::Engine"),
ElementKind::PartDefinition,
)
.with_name("Engine")
.with_owner(ElementId::new("Vehicle"));
model.add_element(engine);
let symbols = symbols_from_model(&model);
assert_eq!(symbols.len(), 3, "Should have 3 symbols");
let car_sym = symbols
.iter()
.find(|s| s.name.as_ref() == "Car")
.expect("Should have Car");
assert_eq!(car_sym.kind, SymbolKind::PartDefinition);
assert_eq!(car_sym.qualified_name.as_ref(), "Car");
let engine_sym = symbols
.iter()
.find(|s| s.name.as_ref() == "Engine")
.expect("Should have Engine");
assert_eq!(engine_sym.kind, SymbolKind::PartDefinition);
}
#[test]
fn test_symbols_from_model_with_relationships() {
let mut model = Model::new();
let vehicle = Element::new(ElementId::new("Vehicle"), ElementKind::PartDefinition)
.with_name("Vehicle");
model.add_element(vehicle);
let car = Element::new(ElementId::new("Car"), ElementKind::PartDefinition).with_name("Car");
model.add_element(car);
model.add_rel(
ElementId::new("rel_1"),
ElementKind::Specialization,
ElementId::new("Car"),
ElementId::new("Vehicle"),
None,
);
let symbols = symbols_from_model(&model);
let car_sym = symbols
.iter()
.find(|s| s.name.as_ref() == "Car")
.expect("Should have Car");
assert!(
!car_sym.relationships.is_empty(),
"Car should have relationships"
);
let spec_rel = car_sym
.relationships
.iter()
.find(|r| r.kind == HirRelKind::Specializes)
.expect("Should have specialization");
assert_eq!(spec_rel.target.as_ref(), "Vehicle");
assert!(car_sym.supertypes.iter().any(|s| s.as_ref() == "Vehicle"));
}
#[test]
fn test_symbols_from_model_with_documentation() {
let mut model = Model::new();
let mut pkg =
Element::new(ElementId::new("MyPackage"), ElementKind::Package).with_name("MyPackage");
pkg.documentation = Some("This is a documented package".into());
model.add_element(pkg);
let symbols = symbols_from_model(&model);
assert_eq!(symbols.len(), 1);
assert_eq!(
symbols[0].doc.as_deref(),
Some("This is a documented package")
);
}
#[test]
fn test_symbols_from_model_roundtrip() {
let db = RootDatabase::new();
let sysml = r#"
package Types {
part def Vehicle;
part def Car :> Vehicle;
}
"#;
let file_text = FileText::new(&db, FileId::new(0), sysml.to_string());
let original_symbols = file_symbols_from_text(&db, file_text);
let model = model_from_symbols(&original_symbols);
let roundtrip_symbols = symbols_from_model(&model);
let original_count = original_symbols.len();
let roundtrip_count = roundtrip_symbols.len();
assert_eq!(
roundtrip_count, original_count,
"Roundtrip should preserve symbol count: {} → {}",
original_count, roundtrip_count
);
for orig in &original_symbols {
let found = roundtrip_symbols
.iter()
.find(|s| s.qualified_name == orig.qualified_name);
assert!(
found.is_some(),
"Symbol {} should exist after roundtrip",
orig.qualified_name
);
}
}
#[test]
fn test_apply_metadata_to_host() {
use crate::ide::AnalysisHost;
use crate::interchange::integrate::apply_metadata_to_host;
use crate::interchange::metadata::{ElementMeta, ImportMetadata};
let mut host = AnalysisHost::new();
let sysml = r#"
package TestPkg {
part def Car;
}
"#;
host.set_file_content("/test.sysml", sysml);
let mut metadata = ImportMetadata::new();
metadata.add_element("TestPkg", ElementMeta::with_id("uuid-pkg-1"));
metadata.add_element("TestPkg::Car", ElementMeta::with_id("uuid-car-1"));
apply_metadata_to_host(&mut host, &metadata);
let analysis = host.analysis();
let pkg = analysis
.symbol_index()
.lookup_qualified("TestPkg")
.expect("Should find TestPkg");
assert_eq!(
pkg.element_id.as_ref(),
"uuid-pkg-1",
"Package should have metadata element_id"
);
let car = analysis
.symbol_index()
.lookup_qualified("TestPkg::Car")
.expect("Should find TestPkg::Car");
assert_eq!(
car.element_id.as_ref(),
"uuid-car-1",
"Car should have metadata element_id"
);
}
}