use std::sync::Arc;
use uuid::Uuid;
use crate::base::FileId;
use crate::parser::Direction;
use crate::syntax::normalized::{
Multiplicity, NormalizedAlias, NormalizedComment, NormalizedDefKind, NormalizedDefinition,
NormalizedDependency, NormalizedElement, NormalizedImport, NormalizedPackage,
NormalizedRelKind, NormalizedRelationship, NormalizedUsage, NormalizedUsageKind,
};
pub fn new_element_id() -> Arc<str> {
Uuid::new_v4().to_string().into()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RefKind {
TypedBy,
Specializes,
Redefines,
Subsets,
References,
Expression,
Other,
}
impl RefKind {
pub fn is_type_reference(&self) -> bool {
matches!(self, RefKind::TypedBy | RefKind::Specializes)
}
pub fn is_feature_reference(&self) -> bool {
matches!(
self,
RefKind::Redefines | RefKind::Subsets | RefKind::References
)
}
pub fn from_normalized(kind: NormalizedRelKind) -> Self {
match kind {
NormalizedRelKind::TypedBy => RefKind::TypedBy,
NormalizedRelKind::Specializes => RefKind::Specializes,
NormalizedRelKind::Redefines => RefKind::Redefines,
NormalizedRelKind::Subsets => RefKind::Subsets,
NormalizedRelKind::References => RefKind::References,
NormalizedRelKind::Expression => RefKind::Expression,
_ => RefKind::Other,
}
}
pub fn display(&self) -> &'static str {
match self {
RefKind::TypedBy => "typed by",
RefKind::Specializes => "specializes",
RefKind::Redefines => "redefines",
RefKind::Subsets => "subsets",
RefKind::References => "references",
RefKind::Expression => "expression",
RefKind::Other => "other",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RelationshipKind {
Specializes,
TypedBy,
Redefines,
Subsets,
References,
Satisfies,
Performs,
Exhibits,
Includes,
Asserts,
Verifies,
}
impl RelationshipKind {
pub fn from_normalized(kind: NormalizedRelKind) -> Option<Self> {
match kind {
NormalizedRelKind::Specializes => Some(RelationshipKind::Specializes),
NormalizedRelKind::TypedBy => Some(RelationshipKind::TypedBy),
NormalizedRelKind::Redefines => Some(RelationshipKind::Redefines),
NormalizedRelKind::Subsets => Some(RelationshipKind::Subsets),
NormalizedRelKind::References => Some(RelationshipKind::References),
NormalizedRelKind::Satisfies => Some(RelationshipKind::Satisfies),
NormalizedRelKind::Performs => Some(RelationshipKind::Performs),
NormalizedRelKind::Exhibits => Some(RelationshipKind::Exhibits),
NormalizedRelKind::Includes => Some(RelationshipKind::Includes),
NormalizedRelKind::Asserts => Some(RelationshipKind::Asserts),
NormalizedRelKind::Verifies => Some(RelationshipKind::Verifies),
_ => None,
}
}
pub fn display(&self) -> &'static str {
match self {
RelationshipKind::Specializes => "Specializes",
RelationshipKind::TypedBy => "Typed by",
RelationshipKind::Redefines => "Redefines",
RelationshipKind::Subsets => "Subsets",
RelationshipKind::References => "References",
RelationshipKind::Satisfies => "Satisfies",
RelationshipKind::Performs => "Performs",
RelationshipKind::Exhibits => "Exhibits",
RelationshipKind::Includes => "Includes",
RelationshipKind::Asserts => "Asserts",
RelationshipKind::Verifies => "Verifies",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct HirRelationship {
pub kind: RelationshipKind,
pub target: Arc<str>,
pub resolved_target: Option<Arc<str>>,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
}
impl HirRelationship {
pub fn new(kind: RelationshipKind, target: impl Into<Arc<str>>) -> Self {
Self {
kind,
target: target.into(),
resolved_target: None,
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
}
}
pub fn with_span(
kind: RelationshipKind,
target: impl Into<Arc<str>>,
start_line: u32,
start_col: u32,
end_line: u32,
end_col: u32,
) -> Self {
Self {
kind,
target: target.into(),
resolved_target: None,
start_line,
start_col,
end_line,
end_col,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeRef {
pub target: Arc<str>,
pub resolved_target: Option<Arc<str>>,
pub kind: RefKind,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
}
impl TypeRef {
pub fn new(
target: impl Into<Arc<str>>,
kind: RefKind,
start_line: u32,
start_col: u32,
end_line: u32,
end_col: u32,
) -> Self {
Self {
target: target.into(),
resolved_target: None,
kind,
start_line,
start_col,
end_line,
end_col,
}
}
pub fn contains(&self, line: u32, col: u32) -> bool {
let after_start =
line > self.start_line || (line == self.start_line && col >= self.start_col);
let before_end = line < self.end_line || (line == self.end_line && col <= self.end_col);
after_start && before_end
}
pub fn immediately_precedes(&self, other: &TypeRef) -> bool {
if self.end_line != other.start_line {
return false;
}
self.end_col + 1 == other.start_col
}
pub fn effective_target(&self) -> &Arc<str> {
self.resolved_target.as_ref().unwrap_or(&self.target)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeRefKind {
Simple(TypeRef),
Chain(TypeRefChain),
}
impl TypeRefKind {
pub fn as_refs(&self) -> Vec<&TypeRef> {
match self {
TypeRefKind::Simple(r) => vec![r],
TypeRefKind::Chain(c) => c.parts.iter().collect(),
}
}
pub fn is_chain(&self) -> bool {
matches!(self, TypeRefKind::Chain(_))
}
pub fn first_target(&self) -> &Arc<str> {
match self {
TypeRefKind::Simple(r) => &r.target,
TypeRefKind::Chain(c) => &c.parts[0].target,
}
}
pub fn contains(&self, line: u32, col: u32) -> bool {
match self {
TypeRefKind::Simple(r) => r.contains(line, col),
TypeRefKind::Chain(c) => c.parts.iter().any(|r| r.contains(line, col)),
}
}
pub fn part_at(&self, line: u32, col: u32) -> Option<(usize, &TypeRef)> {
match self {
TypeRefKind::Simple(r) if r.contains(line, col) => Some((0, r)),
TypeRefKind::Chain(c) => c
.parts
.iter()
.enumerate()
.find(|(_, r)| r.contains(line, col)),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeRefChain {
pub parts: Vec<TypeRef>,
}
impl TypeRefChain {
pub fn as_dotted_string(&self) -> String {
self.parts
.iter()
.map(|p| p.target.as_ref())
.collect::<Vec<_>>()
.join(".")
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct HirSymbol {
pub name: Arc<str>,
pub short_name: Option<Arc<str>>,
pub qualified_name: Arc<str>,
pub element_id: Arc<str>,
pub kind: SymbolKind,
pub file: FileId,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub short_name_start_line: Option<u32>,
pub short_name_start_col: Option<u32>,
pub short_name_end_line: Option<u32>,
pub short_name_end_col: Option<u32>,
pub doc: Option<Arc<str>>,
pub supertypes: Vec<Arc<str>>,
pub relationships: Vec<HirRelationship>,
pub type_refs: Vec<TypeRefKind>,
pub is_public: bool,
pub view_data: Option<crate::hir::views::ViewData>,
pub metadata_annotations: Vec<Arc<str>>,
pub is_abstract: bool,
pub is_variation: bool,
pub is_readonly: bool,
pub is_derived: bool,
pub is_parallel: bool,
pub is_individual: bool,
pub is_end: bool,
pub is_default: bool,
pub is_ordered: bool,
pub is_nonunique: bool,
pub is_portion: bool,
pub direction: Option<Direction>,
pub multiplicity: Option<Multiplicity>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SymbolKind {
Package,
PartDefinition,
ItemDefinition,
ActionDefinition,
PortDefinition,
AttributeDefinition,
ConnectionDefinition,
InterfaceDefinition,
AllocationDefinition,
RequirementDefinition,
ConstraintDefinition,
StateDefinition,
CalculationDefinition,
UseCaseDefinition,
AnalysisCaseDefinition,
ConcernDefinition,
ViewDefinition,
ViewpointDefinition,
RenderingDefinition,
ViewUsage,
ViewpointUsage,
RenderingUsage,
EnumerationDefinition,
MetadataDefinition,
Interaction,
DataType,
Class,
Structure,
Behavior,
Function,
Association,
PartUsage,
ItemUsage,
ActionUsage,
PortUsage,
AttributeUsage,
ConnectionUsage,
InterfaceUsage,
AllocationUsage,
RequirementUsage,
ConstraintUsage,
StateUsage,
TransitionUsage,
CalculationUsage,
ReferenceUsage,
OccurrenceUsage,
FlowConnectionUsage,
ExposeRelationship,
Import,
Alias,
Comment,
Dependency,
Other,
}
impl SymbolKind {
pub fn from_definition_kind(kind: &NormalizedDefKind) -> Self {
match kind {
NormalizedDefKind::Part => Self::PartDefinition,
NormalizedDefKind::Item => Self::ItemDefinition,
NormalizedDefKind::Action => Self::ActionDefinition,
NormalizedDefKind::Port => Self::PortDefinition,
NormalizedDefKind::Attribute => Self::AttributeDefinition,
NormalizedDefKind::Connection => Self::ConnectionDefinition,
NormalizedDefKind::Interface => Self::InterfaceDefinition,
NormalizedDefKind::Allocation => Self::AllocationDefinition,
NormalizedDefKind::Requirement => Self::RequirementDefinition,
NormalizedDefKind::Constraint => Self::ConstraintDefinition,
NormalizedDefKind::State => Self::StateDefinition,
NormalizedDefKind::Calculation => Self::CalculationDefinition,
NormalizedDefKind::UseCase => Self::UseCaseDefinition,
NormalizedDefKind::AnalysisCase => Self::AnalysisCaseDefinition,
NormalizedDefKind::Concern => Self::ConcernDefinition,
NormalizedDefKind::View => Self::ViewDefinition,
NormalizedDefKind::Viewpoint => Self::ViewpointDefinition,
NormalizedDefKind::Rendering => Self::RenderingDefinition,
NormalizedDefKind::Enumeration => Self::EnumerationDefinition,
NormalizedDefKind::DataType => Self::DataType,
NormalizedDefKind::Class => Self::Class,
NormalizedDefKind::Structure => Self::Structure,
NormalizedDefKind::Behavior => Self::Behavior,
NormalizedDefKind::Function => Self::Function,
NormalizedDefKind::Association => Self::Association,
NormalizedDefKind::Metaclass => Self::MetadataDefinition,
NormalizedDefKind::Interaction => Self::Interaction,
NormalizedDefKind::Other => Self::Other,
}
}
pub fn from_usage_kind(kind: &NormalizedUsageKind) -> Self {
match kind {
NormalizedUsageKind::Part => Self::PartUsage,
NormalizedUsageKind::Item => Self::ItemUsage,
NormalizedUsageKind::Action => Self::ActionUsage,
NormalizedUsageKind::Port => Self::PortUsage,
NormalizedUsageKind::Attribute => Self::AttributeUsage,
NormalizedUsageKind::Connection => Self::ConnectionUsage,
NormalizedUsageKind::Interface => Self::InterfaceUsage,
NormalizedUsageKind::Allocation => Self::AllocationUsage,
NormalizedUsageKind::Requirement => Self::RequirementUsage,
NormalizedUsageKind::Constraint => Self::ConstraintUsage,
NormalizedUsageKind::State => Self::StateUsage,
NormalizedUsageKind::Calculation => Self::CalculationUsage,
NormalizedUsageKind::Reference => Self::ReferenceUsage,
NormalizedUsageKind::Occurrence => Self::OccurrenceUsage,
NormalizedUsageKind::Flow => Self::FlowConnectionUsage,
NormalizedUsageKind::Transition => Self::TransitionUsage,
NormalizedUsageKind::Accept => Self::ActionUsage, NormalizedUsageKind::End => Self::PortUsage, NormalizedUsageKind::Fork => Self::ActionUsage, NormalizedUsageKind::Join => Self::ActionUsage, NormalizedUsageKind::Merge => Self::ActionUsage, NormalizedUsageKind::Decide => Self::ActionUsage, NormalizedUsageKind::Feature => Self::PartUsage, NormalizedUsageKind::View => Self::ViewUsage,
NormalizedUsageKind::Viewpoint => Self::ViewpointUsage,
NormalizedUsageKind::Rendering => Self::RenderingUsage,
NormalizedUsageKind::Other => Self::Other,
}
}
pub fn display(&self) -> &'static str {
match self {
Self::Package => "Package",
Self::PartDefinition => "Part def",
Self::ItemDefinition => "Item def",
Self::ActionDefinition => "Action def",
Self::PortDefinition => "Port def",
Self::AttributeDefinition => "Attribute def",
Self::ConnectionDefinition => "Connection def",
Self::InterfaceDefinition => "Interface def",
Self::AllocationDefinition => "Allocation def",
Self::RequirementDefinition => "Requirement def",
Self::ConstraintDefinition => "Constraint def",
Self::StateDefinition => "State def",
Self::CalculationDefinition => "Calc def",
Self::UseCaseDefinition => "Use case def",
Self::AnalysisCaseDefinition => "Analysis case def",
Self::ConcernDefinition => "Concern def",
Self::ViewDefinition => "View def",
Self::ViewpointDefinition => "Viewpoint def",
Self::RenderingDefinition => "Rendering def",
Self::ViewUsage => "View",
Self::ViewpointUsage => "Viewpoint",
Self::RenderingUsage => "Rendering",
Self::EnumerationDefinition => "Enum def",
Self::MetadataDefinition => "Metaclass def",
Self::Interaction => "Interaction def",
Self::DataType => "Datatype",
Self::Class => "Class",
Self::Structure => "Struct",
Self::Behavior => "Behavior",
Self::Function => "Function",
Self::Association => "Assoc",
Self::PartUsage => "Part",
Self::ItemUsage => "Item",
Self::ActionUsage => "Action",
Self::PortUsage => "Port",
Self::AttributeUsage => "Attribute",
Self::ConnectionUsage => "Connection",
Self::InterfaceUsage => "Interface",
Self::AllocationUsage => "Allocation",
Self::RequirementUsage => "Requirement",
Self::ConstraintUsage => "Constraint",
Self::StateUsage => "State",
Self::TransitionUsage => "Transition",
Self::CalculationUsage => "Calc",
Self::ReferenceUsage => "Ref",
Self::OccurrenceUsage => "Occurrence",
Self::FlowConnectionUsage => "Flow",
Self::ExposeRelationship => "Expose",
Self::Import => "Import",
Self::Alias => "Alias",
Self::Comment => "Comment",
Self::Dependency => "Dependency",
Self::Other => "Element",
}
}
pub fn from_normalized_def_kind(kind: NormalizedDefKind) -> Self {
match kind {
NormalizedDefKind::Part => Self::PartDefinition,
NormalizedDefKind::Item => Self::ItemDefinition,
NormalizedDefKind::Action => Self::ActionDefinition,
NormalizedDefKind::Port => Self::PortDefinition,
NormalizedDefKind::Attribute => Self::AttributeDefinition,
NormalizedDefKind::Connection => Self::ConnectionDefinition,
NormalizedDefKind::Interface => Self::InterfaceDefinition,
NormalizedDefKind::Allocation => Self::AllocationDefinition,
NormalizedDefKind::Requirement => Self::RequirementDefinition,
NormalizedDefKind::Constraint => Self::ConstraintDefinition,
NormalizedDefKind::State => Self::StateDefinition,
NormalizedDefKind::Calculation => Self::CalculationDefinition,
NormalizedDefKind::UseCase => Self::UseCaseDefinition,
NormalizedDefKind::AnalysisCase => Self::AnalysisCaseDefinition,
NormalizedDefKind::Concern => Self::ConcernDefinition,
NormalizedDefKind::View => Self::ViewDefinition,
NormalizedDefKind::Viewpoint => Self::ViewpointDefinition,
NormalizedDefKind::Rendering => Self::RenderingDefinition,
NormalizedDefKind::Enumeration => Self::EnumerationDefinition,
NormalizedDefKind::DataType => Self::DataType,
NormalizedDefKind::Class => Self::Class,
NormalizedDefKind::Structure => Self::Structure,
NormalizedDefKind::Behavior => Self::Behavior,
NormalizedDefKind::Function => Self::Function,
NormalizedDefKind::Association => Self::Association,
NormalizedDefKind::Metaclass => Self::MetadataDefinition,
NormalizedDefKind::Interaction => Self::Interaction,
NormalizedDefKind::Other => Self::Other,
}
}
pub fn from_normalized_usage_kind(kind: NormalizedUsageKind) -> Self {
match kind {
NormalizedUsageKind::Part => Self::PartUsage,
NormalizedUsageKind::Item => Self::ItemUsage,
NormalizedUsageKind::Action => Self::ActionUsage,
NormalizedUsageKind::Port => Self::PortUsage,
NormalizedUsageKind::Attribute => Self::AttributeUsage,
NormalizedUsageKind::Connection => Self::ConnectionUsage,
NormalizedUsageKind::Interface => Self::InterfaceUsage,
NormalizedUsageKind::Allocation => Self::AllocationUsage,
NormalizedUsageKind::Requirement => Self::RequirementUsage,
NormalizedUsageKind::Constraint => Self::ConstraintUsage,
NormalizedUsageKind::State => Self::StateUsage,
NormalizedUsageKind::Calculation => Self::CalculationUsage,
NormalizedUsageKind::Reference => Self::ReferenceUsage,
NormalizedUsageKind::Occurrence => Self::OccurrenceUsage,
NormalizedUsageKind::Flow => Self::FlowConnectionUsage,
NormalizedUsageKind::Transition => Self::TransitionUsage,
NormalizedUsageKind::Accept => Self::ActionUsage, NormalizedUsageKind::End => Self::PortUsage, NormalizedUsageKind::Fork => Self::ActionUsage, NormalizedUsageKind::Join => Self::ActionUsage, NormalizedUsageKind::Merge => Self::ActionUsage, NormalizedUsageKind::Decide => Self::ActionUsage, NormalizedUsageKind::View => Self::ViewUsage,
NormalizedUsageKind::Viewpoint => Self::ViewpointUsage,
NormalizedUsageKind::Rendering => Self::RenderingUsage,
NormalizedUsageKind::Feature => Self::AttributeUsage,
NormalizedUsageKind::Other => Self::Other,
}
}
}
struct ExtractionContext {
file: FileId,
prefix: String,
anon_counter: u32,
scope_stack: Vec<String>,
line_index: crate::base::LineIndex,
}
impl ExtractionContext {
fn qualified_name(&self, name: &str) -> String {
if self.prefix.is_empty() {
name.to_string()
} else {
format!("{}::{}", self.prefix, name)
}
}
fn current_scope_name(&self) -> String {
self.prefix.clone()
}
fn push_scope(&mut self, name: &str) {
self.scope_stack.push(name.to_string());
if self.prefix.is_empty() {
self.prefix = name.to_string();
} else {
self.prefix = format!("{}::{}", self.prefix, name);
}
}
fn pop_scope(&mut self) {
if let Some(popped) = self.scope_stack.pop() {
let suffix_len = if self.scope_stack.is_empty() {
popped.len()
} else {
popped.len() + 2 };
self.prefix
.truncate(self.prefix.len().saturating_sub(suffix_len));
}
}
fn next_anon_scope(&mut self, rel_prefix: &str, target: &str, line: u32) -> String {
self.anon_counter += 1;
format!("<{}{}#{}@L{}>", rel_prefix, target, self.anon_counter, line)
}
fn range_to_info(&self, range: Option<rowan::TextRange>) -> SpanInfo {
match range {
Some(r) => {
let start = self.line_index.line_col(r.start());
let end = self.line_index.line_col(r.end());
SpanInfo {
start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
}
}
None => SpanInfo::default(),
}
}
fn range_to_optional(
&self,
range: Option<rowan::TextRange>,
) -> (Option<u32>, Option<u32>, Option<u32>, Option<u32>) {
match range {
Some(r) => {
let start = self.line_index.line_col(r.start());
let end = self.line_index.line_col(r.end());
(
Some(start.line),
Some(start.col),
Some(end.line),
Some(end.col),
)
}
None => (None, None, None, None),
}
}
}
#[derive(Debug, Default)]
pub struct ExtractionResult {
pub symbols: Vec<HirSymbol>,
pub scope_filters: Vec<(Arc<str>, Vec<String>)>,
pub import_filters: Vec<(Arc<str>, Vec<String>)>,
}
pub fn extract_symbols_unified(file: FileId, syntax: &crate::syntax::SyntaxFile) -> Vec<HirSymbol> {
extract_with_filters(file, syntax).symbols
}
pub fn extract_with_filters(file: FileId, syntax: &crate::syntax::SyntaxFile) -> ExtractionResult {
let mut result = ExtractionResult::default();
let line_index = syntax.line_index();
let mut context = ExtractionContext {
file,
prefix: String::new(),
anon_counter: 0,
scope_stack: Vec::new(),
line_index,
};
if let Some(source_file) = syntax.source_file() {
for member in source_file.members() {
let normalized = NormalizedElement::from_rowan(&member);
extract_from_normalized(&mut result, &mut context, &normalized);
}
}
result
}
fn extract_from_normalized(
result: &mut ExtractionResult,
ctx: &mut ExtractionContext,
element: &NormalizedElement,
) {
match element {
NormalizedElement::Package(pkg) => extract_from_normalized_package(result, ctx, pkg),
NormalizedElement::Definition(def) => {
extract_from_normalized_definition(&mut result.symbols, ctx, def)
}
NormalizedElement::Usage(usage) => {
extract_from_normalized_usage(&mut result.symbols, ctx, usage)
}
NormalizedElement::Import(import) => {
extract_from_normalized_import(&mut result.symbols, ctx, import);
if !import.filters.is_empty() {
let import_qname = ctx.qualified_name(&format!("import:{}", import.path));
result
.import_filters
.push((Arc::from(import_qname.as_str()), import.filters.clone()));
}
}
NormalizedElement::Alias(alias) => {
extract_from_normalized_alias(&mut result.symbols, ctx, alias)
}
NormalizedElement::Comment(comment) => {
extract_from_normalized_comment(&mut result.symbols, ctx, comment)
}
NormalizedElement::Dependency(dep) => {
extract_from_normalized_dependency(&mut result.symbols, ctx, dep)
}
NormalizedElement::Filter(filter) => {
let scope = ctx.current_scope_name();
if !filter.metadata_refs.is_empty() {
result.scope_filters.push((
Arc::from(scope.as_str()),
filter.metadata_refs.iter().map(|s| s.to_string()).collect(),
));
}
if !filter.all_refs.is_empty() {
let type_refs: Vec<TypeRefKind> = filter
.all_refs
.iter()
.map(|(name, range)| {
let start = ctx.line_index.line_col(range.start());
let end = ctx.line_index.line_col(range.end());
TypeRefKind::Simple(TypeRef {
target: Arc::from(name.as_str()),
resolved_target: None,
kind: RefKind::Other, start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
})
})
.collect();
let span = ctx.range_to_info(filter.range);
let filter_qname = ctx.qualified_name(&format!("<filter@L{}>", span.start_line));
result.symbols.push(HirSymbol {
name: Arc::from("<filter>"),
short_name: None,
qualified_name: Arc::from(filter_qname.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Other,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs,
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
}
}
NormalizedElement::Expose(_expose) => {
}
}
}
fn extract_from_normalized_into_symbols(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
element: &NormalizedElement,
) {
match element {
NormalizedElement::Package(pkg) => {
let mut result = ExtractionResult::default();
extract_from_normalized_package(&mut result, ctx, pkg);
symbols.extend(result.symbols);
}
NormalizedElement::Definition(def) => extract_from_normalized_definition(symbols, ctx, def),
NormalizedElement::Usage(usage) => extract_from_normalized_usage(symbols, ctx, usage),
NormalizedElement::Import(import) => extract_from_normalized_import(symbols, ctx, import),
NormalizedElement::Alias(alias) => extract_from_normalized_alias(symbols, ctx, alias),
NormalizedElement::Comment(comment) => {
extract_from_normalized_comment(symbols, ctx, comment)
}
NormalizedElement::Dependency(dep) => extract_from_normalized_dependency(symbols, ctx, dep),
NormalizedElement::Filter(_filter) => {
}
NormalizedElement::Expose(_expose) => {
}
}
}
fn extract_from_normalized_package(
result: &mut ExtractionResult,
ctx: &mut ExtractionContext,
pkg: &NormalizedPackage,
) {
let name = match &pkg.name {
Some(n) => strip_quotes(n),
None => return,
};
let qualified_name = ctx.qualified_name(&name);
let span = ctx.range_to_info(pkg.name_range.or(pkg.range));
let doc = pkg.doc.as_ref().map(|s| Arc::from(s.trim()));
result.symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: pkg.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Package,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs: Vec::new(),
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
ctx.push_scope(&name);
for child in &pkg.children {
extract_from_normalized(result, ctx, child);
}
ctx.pop_scope();
}
fn implicit_supertype_for_def_kind(kind: NormalizedDefKind) -> Option<&'static str> {
match kind {
NormalizedDefKind::Part => Some("Parts::Part"),
NormalizedDefKind::Item => Some("Items::Item"),
NormalizedDefKind::Action => Some("Actions::Action"),
NormalizedDefKind::State => Some("States::StateAction"),
NormalizedDefKind::Constraint => Some("Constraints::ConstraintCheck"),
NormalizedDefKind::Requirement => Some("Requirements::RequirementCheck"),
NormalizedDefKind::Calculation => Some("Calculations::Calculation"),
NormalizedDefKind::Port => Some("Ports::Port"),
NormalizedDefKind::Connection => Some("Connections::BinaryConnection"),
NormalizedDefKind::Interface => Some("Interfaces::Interface"),
NormalizedDefKind::Allocation => Some("Allocations::Allocation"),
NormalizedDefKind::UseCase => Some("UseCases::UseCase"),
NormalizedDefKind::AnalysisCase => Some("AnalysisCases::AnalysisCase"),
NormalizedDefKind::Attribute => Some("Attributes::AttributeValue"),
_ => None,
}
}
fn implicit_supertype_for_usage_kind(kind: NormalizedUsageKind) -> Option<&'static str> {
match kind {
NormalizedUsageKind::Part => Some("Parts::Part"),
NormalizedUsageKind::Item => Some("Items::Item"),
NormalizedUsageKind::Action => Some("Actions::Action"),
NormalizedUsageKind::State => Some("States::StateAction"),
NormalizedUsageKind::Flow => Some("Flows::Message"),
NormalizedUsageKind::Connection => Some("Connections::Connection"),
NormalizedUsageKind::Interface => Some("Interfaces::Interface"),
NormalizedUsageKind::Allocation => Some("Allocations::Allocation"),
NormalizedUsageKind::Requirement => Some("Requirements::RequirementCheck"),
NormalizedUsageKind::Constraint => Some("Constraints::ConstraintCheck"),
NormalizedUsageKind::Calculation => Some("Calculations::Calculation"),
NormalizedUsageKind::Port => Some("Ports::Port"),
NormalizedUsageKind::Attribute => Some("Attributes::AttributeValue"),
_ => None,
}
}
fn extract_relationships_from_normalized(
relationships: &[NormalizedRelationship],
line_index: &crate::base::LineIndex,
) -> Vec<HirRelationship> {
relationships
.iter()
.filter_map(|rel| {
RelationshipKind::from_normalized(rel.kind).map(|kind| {
let (start_line, start_col, end_line, end_col) = rel
.range
.map(|r| {
let start = line_index.line_col(r.start());
let end = line_index.line_col(r.end());
(start.line, start.col, end.line, end.col)
})
.unwrap_or((0, 0, 0, 0));
HirRelationship::with_span(
kind,
rel.target.as_str().as_ref(),
start_line,
start_col,
end_line,
end_col,
)
})
})
.collect()
}
fn extract_metadata_annotations(
relationships: &[NormalizedRelationship],
children: &[NormalizedElement],
) -> Vec<Arc<str>> {
let mut annotations = Vec::new();
for rel in relationships.iter() {
if matches!(rel.kind, NormalizedRelKind::Meta) {
let target = rel.target.as_str();
let simple_name = target.rsplit("::").next().unwrap_or(&target);
annotations.push(Arc::from(simple_name));
}
}
for child in children.iter() {
if let NormalizedElement::Usage(usage) = child {
if usage.name.is_none() {
for rel in &usage.relationships {
if matches!(rel.kind, NormalizedRelKind::TypedBy) {
let target = rel.target.as_str();
let simple_name = target.rsplit("::").next().unwrap_or(&target);
annotations.push(Arc::from(simple_name));
}
}
}
}
}
annotations
}
fn extract_from_normalized_definition(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
def: &NormalizedDefinition,
) {
let name = match &def.name {
Some(n) => strip_quotes(n),
None => return,
};
let qualified_name = ctx.qualified_name(&name);
let kind = SymbolKind::from_normalized_def_kind(def.kind);
let span = ctx.range_to_info(def.name_range.or(def.range));
let (sn_start_line, sn_start_col, sn_end_line, sn_end_col) =
ctx.range_to_optional(def.short_name_range);
let mut supertypes: Vec<Arc<str>> = def
.relationships
.iter()
.filter(|r| matches!(r.kind, NormalizedRelKind::Specializes))
.map(|r| Arc::from(r.target.as_str().as_ref()))
.collect();
if supertypes.is_empty() {
if let Some(implicit) = implicit_supertype_for_def_kind(def.kind) {
supertypes.push(Arc::from(implicit));
}
}
let type_refs = extract_type_refs_from_normalized(&def.relationships, &ctx.line_index);
let relationships = extract_relationships_from_normalized(&def.relationships, &ctx.line_index);
let metadata_annotations = extract_metadata_annotations(&def.relationships, &def.children);
let doc = def.doc.as_ref().map(|s| Arc::from(s.trim()));
let view_data = extract_view_data_from_definition(def, def.kind);
symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: def.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: sn_start_line,
short_name_start_col: sn_start_col,
short_name_end_line: sn_end_line,
short_name_end_col: sn_end_col,
doc,
supertypes,
relationships,
type_refs,
is_public: false,
view_data,
metadata_annotations,
is_abstract: def.is_abstract,
is_variation: def.is_variation,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: def.is_individual,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
ctx.push_scope(&name);
for child in &def.children {
extract_from_normalized_into_symbols(symbols, ctx, child);
}
ctx.pop_scope();
}
fn extract_from_normalized_usage(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
usage: &NormalizedUsage,
) {
let type_refs = extract_type_refs_from_normalized(&usage.relationships, &ctx.line_index);
let relationships =
extract_relationships_from_normalized(&usage.relationships, &ctx.line_index);
let metadata_annotations = extract_metadata_annotations(&usage.relationships, &usage.children);
let name = match &usage.name {
Some(n) => strip_quotes(n),
None => {
if !type_refs.is_empty() {
if let Some(parent) = symbols
.iter_mut()
.rev()
.find(|s| s.qualified_name.as_ref() == ctx.prefix)
{
if parent.kind != SymbolKind::Package {
let typing_refs: Vec<_> = type_refs
.iter()
.filter(
|tr| matches!(tr, TypeRefKind::Simple(r) if r.kind == RefKind::TypedBy),
)
.cloned()
.collect();
parent.type_refs.extend(typing_refs);
}
}
}
let line = usage
.range
.map(|r| ctx.line_index.line_col(r.start()).line)
.unwrap_or(0);
let anon_scope = usage
.relationships
.iter()
.find(|r| !matches!(r.kind, NormalizedRelKind::Expression))
.map(|r| {
let prefix = match r.kind {
NormalizedRelKind::Subsets => ":>",
NormalizedRelKind::TypedBy => ":",
NormalizedRelKind::Specializes => ":>:",
NormalizedRelKind::Redefines => ":>>",
NormalizedRelKind::About => "about:",
NormalizedRelKind::Performs => "perform:",
NormalizedRelKind::Satisfies => "satisfy:",
NormalizedRelKind::Exhibits => "exhibit:",
NormalizedRelKind::Includes => "include:",
NormalizedRelKind::Asserts => "assert:",
NormalizedRelKind::Verifies => "verify:",
NormalizedRelKind::References => "ref:",
NormalizedRelKind::Meta => "meta:",
NormalizedRelKind::Crosses => "crosses:",
NormalizedRelKind::Expression => "~",
NormalizedRelKind::FeatureChain => "chain:",
NormalizedRelKind::Conjugates => "~:",
NormalizedRelKind::TransitionSource => "from:",
NormalizedRelKind::TransitionTarget => "then:",
NormalizedRelKind::SuccessionSource => "first:",
NormalizedRelKind::SuccessionTarget => "then:",
NormalizedRelKind::AcceptedMessage => "accept:",
NormalizedRelKind::AcceptVia => "via:",
NormalizedRelKind::SentMessage => "send:",
NormalizedRelKind::SendVia => "via:",
NormalizedRelKind::SendTo => "to:",
NormalizedRelKind::MessageSource => "from:",
NormalizedRelKind::MessageTarget => "to:",
NormalizedRelKind::Assumes => "assume:",
NormalizedRelKind::Requires => "require:",
NormalizedRelKind::AllocateSource => "allocate:",
NormalizedRelKind::AllocateTo => "to:",
NormalizedRelKind::BindSource => "bind:",
NormalizedRelKind::BindTarget => "=:",
NormalizedRelKind::ConnectSource => "connect:",
NormalizedRelKind::ConnectTarget => "to:",
NormalizedRelKind::FlowItem => "flow:",
NormalizedRelKind::FlowSource => "from:",
NormalizedRelKind::FlowTarget => "to:",
NormalizedRelKind::InterfaceEnd => "end:",
NormalizedRelKind::Exposes => "expose:",
NormalizedRelKind::Renders => "render:",
NormalizedRelKind::Filters => "filter:",
NormalizedRelKind::DependencySource => "dep:",
NormalizedRelKind::DependencyTarget => "to:",
};
ctx.next_anon_scope(prefix, &r.target.as_str(), line)
})
.unwrap_or_else(|| ctx.next_anon_scope("anon", "", line));
let qualified_name = ctx.qualified_name(&anon_scope);
let kind = SymbolKind::from_normalized_usage_kind(usage.kind);
let anon_span_range = usage
.relationships
.iter()
.find(|r| !matches!(r.kind, NormalizedRelKind::Expression))
.and_then(|r| r.range)
.or(usage.range);
let span = ctx.range_to_info(anon_span_range);
let mut anon_supertypes: Vec<Arc<str>> = usage
.relationships
.iter()
.filter(|r| {
matches!(
r.kind,
NormalizedRelKind::TypedBy
| NormalizedRelKind::Subsets
| NormalizedRelKind::Specializes
| NormalizedRelKind::Redefines
| NormalizedRelKind::Satisfies
| NormalizedRelKind::Verifies
)
})
.map(|r| Arc::from(r.target.as_str().as_ref()))
.collect();
let is_expression_scope = usage.name.is_none()
&& usage
.relationships
.iter()
.all(|r| matches!(r.kind, NormalizedRelKind::Expression));
let is_connection_kind = matches!(
usage.kind,
NormalizedUsageKind::Connection
| NormalizedUsageKind::Flow
| NormalizedUsageKind::Interface
| NormalizedUsageKind::Allocation
);
if !is_expression_scope && !is_connection_kind {
if let Some(parent) = symbols
.iter()
.rev()
.find(|s| s.qualified_name.as_ref() == ctx.prefix)
{
for supertype in &parent.supertypes {
if !anon_supertypes.contains(supertype) {
anon_supertypes.push(supertype.clone());
}
}
}
}
let anon_symbol = HirSymbol {
file: ctx.file,
name: Arc::from(anon_scope.as_str()),
short_name: None,
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
supertypes: anon_supertypes,
relationships: relationships.clone(),
type_refs,
doc: None,
is_public: false,
view_data: None,
metadata_annotations: metadata_annotations.clone(),
is_abstract: usage.is_abstract,
is_variation: usage.is_variation,
is_readonly: usage.is_readonly,
is_derived: usage.is_derived,
is_parallel: usage.is_parallel,
is_individual: usage.is_individual,
is_end: usage.is_end,
is_default: usage.is_default,
is_ordered: usage.is_ordered,
is_nonunique: usage.is_nonunique,
is_portion: usage.is_portion,
direction: usage.direction,
multiplicity: usage.multiplicity,
};
symbols.push(anon_symbol);
ctx.push_scope(&anon_scope);
for child in &usage.children {
extract_from_normalized_into_symbols(symbols, ctx, child);
}
ctx.pop_scope();
return;
}
};
let qualified_name = ctx.qualified_name(&name);
let kind = SymbolKind::from_normalized_usage_kind(usage.kind);
let span = ctx.range_to_info(usage.name_range.or(usage.range));
let (sn_start_line, sn_start_col, sn_end_line, sn_end_col) =
ctx.range_to_optional(usage.short_name_range);
let mut supertypes: Vec<Arc<str>> = usage
.relationships
.iter()
.filter(|r| {
matches!(
r.kind,
NormalizedRelKind::TypedBy
| NormalizedRelKind::Subsets
| NormalizedRelKind::Specializes
| NormalizedRelKind::Redefines
| NormalizedRelKind::Performs
| NormalizedRelKind::Exhibits
| NormalizedRelKind::Includes
| NormalizedRelKind::Satisfies
| NormalizedRelKind::Asserts
| NormalizedRelKind::Verifies
)
})
.map(|r| Arc::from(r.target.as_str().as_ref()))
.collect();
if supertypes.is_empty() && !ctx.prefix.is_empty() {
if let Some(parent) = symbols
.iter()
.rev()
.find(|s| s.qualified_name.as_ref() == ctx.prefix)
{
if let Some(parent_type) = parent.supertypes.first() {
let parent_type_qualified = symbols
.iter()
.find(|s| {
s.name.as_ref() == parent_type.as_ref()
|| s.qualified_name.as_ref() == parent_type.as_ref()
})
.map(|s| s.qualified_name.clone());
if let Some(type_qname) = parent_type_qualified {
let potential_redef = format!("{}::{}", type_qname, name);
if symbols
.iter()
.any(|s| s.qualified_name.as_ref() == potential_redef)
{
supertypes.push(Arc::from(potential_redef));
}
}
}
}
}
if supertypes.is_empty() {
if let Some(implicit) = implicit_supertype_for_usage_kind(usage.kind) {
supertypes.push(Arc::from(implicit));
}
}
let doc = usage.doc.as_ref().map(|s| Arc::from(s.trim()));
let typed_by = supertypes.first();
let view_data = extract_view_data_from_usage(usage, usage.kind, typed_by);
symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: usage.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: sn_start_line,
short_name_start_col: sn_start_col,
short_name_end_line: sn_end_line,
short_name_end_col: sn_end_col,
doc,
supertypes,
relationships,
type_refs,
is_public: false,
view_data,
metadata_annotations,
is_abstract: usage.is_abstract,
is_variation: usage.is_variation,
is_readonly: usage.is_readonly,
is_derived: usage.is_derived,
is_parallel: usage.is_parallel,
is_individual: usage.is_individual,
is_end: usage.is_end,
is_default: usage.is_default,
is_ordered: usage.is_ordered,
is_nonunique: usage.is_nonunique,
is_portion: usage.is_portion,
direction: usage.direction,
multiplicity: usage.multiplicity,
});
ctx.push_scope(&name);
for child in &usage.children {
extract_from_normalized_into_symbols(symbols, ctx, child);
}
ctx.pop_scope();
}
fn extract_from_normalized_import(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
import: &NormalizedImport,
) {
let path = &import.path;
let qualified_name = ctx.qualified_name(&format!("import:{}", path));
let span = import
.path_range
.map(|r| ctx.range_to_info(Some(r)))
.unwrap_or_else(|| ctx.range_to_info(import.range));
let target_path = path
.strip_suffix("::**")
.or_else(|| path.strip_suffix("::*"))
.unwrap_or(path);
let type_refs = if let Some(r) = import.path_range {
let start = ctx.line_index.line_col(r.start());
let end = ctx.line_index.line_col(r.end());
vec![TypeRefKind::Simple(TypeRef {
target: Arc::from(target_path),
resolved_target: None,
kind: RefKind::Other, start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
})]
} else {
Vec::new()
};
symbols.push(HirSymbol {
name: Arc::from(path.as_str()),
short_name: None, qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Import,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs,
is_public: import.is_public,
view_data: None,
metadata_annotations: Vec::new(), is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
}
fn extract_from_normalized_alias(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
alias: &NormalizedAlias,
) {
let name = match &alias.name {
Some(n) => strip_quotes(n),
None => return,
};
let qualified_name = ctx.qualified_name(&name);
let span = ctx.range_to_info(alias.name_range.or(alias.range));
let type_refs = if let Some(r) = alias.target_range {
let start = ctx.line_index.line_col(r.start());
let end = ctx.line_index.line_col(r.end());
vec![TypeRefKind::Simple(TypeRef {
target: Arc::from(alias.target.as_str()),
resolved_target: None,
kind: RefKind::Other, start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
})]
} else {
Vec::new()
};
symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: alias.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Alias,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None, short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: vec![Arc::from(alias.target.as_str())],
relationships: Vec::new(),
type_refs,
is_public: false,
view_data: None,
metadata_annotations: Vec::new(), is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
}
fn extract_from_normalized_comment(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
comment: &NormalizedComment,
) {
let type_refs = extract_type_refs_from_normalized(&comment.about, &ctx.line_index);
let (name, is_anonymous) = match &comment.name {
Some(n) => (strip_quotes(n), false),
None => {
if type_refs.is_empty() {
return; }
let anon_name = if let Some(r) = comment.range {
let pos = ctx.line_index.line_col(r.start());
format!("<anonymous_comment_{}_{}>", pos.line, pos.col)
} else {
"<anonymous_comment>".to_string()
};
(anon_name, true)
}
};
let qualified_name = ctx.qualified_name(&name);
let span = ctx.range_to_info(comment.range);
symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: comment.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Comment,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None, short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: if is_anonymous {
None
} else {
Some(Arc::from(comment.content.as_str()))
},
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs,
is_public: false,
view_data: None,
metadata_annotations: Vec::new(), is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
}
fn extract_type_refs_from_normalized(
relationships: &[NormalizedRelationship],
line_index: &crate::base::LineIndex,
) -> Vec<TypeRefKind> {
use crate::syntax::normalized::RelTarget;
let mut type_refs = Vec::new();
for rel in relationships.iter() {
let ref_kind = RefKind::from_normalized(rel.kind);
match &rel.target {
RelTarget::Chain(chain) => {
let num_parts = chain.parts.len();
let parts: Vec<TypeRef> = chain
.parts
.iter()
.enumerate()
.map(|(idx, part)| {
let (start_line, start_col, end_line, end_col) = if let Some(r) = part.range
{
let start = line_index.line_col(r.start());
let end = line_index.line_col(r.end());
(start.line, start.col, end.line, end.col)
} else if idx == num_parts - 1 {
if let Some(r) = rel.range {
let start = line_index.line_col(r.start());
let end = line_index.line_col(r.end());
(start.line, start.col, end.line, end.col)
} else {
(0, 0, 0, 0)
}
} else {
(0, 0, 0, 0)
};
TypeRef {
target: Arc::from(part.name.as_str()),
resolved_target: None,
kind: ref_kind,
start_line,
start_col,
end_line,
end_col,
}
})
.collect();
if !parts.is_empty() {
type_refs.push(TypeRefKind::Chain(TypeRefChain { parts }));
}
}
RelTarget::Simple(target) => {
if let Some(r) = rel.range {
let start = line_index.line_col(r.start());
let end = line_index.line_col(r.end());
type_refs.push(TypeRefKind::Simple(TypeRef {
target: Arc::from(target.as_str()),
resolved_target: None,
kind: ref_kind,
start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
}));
let parts: Vec<&str> = target.split("::").collect();
if parts.len() > 1 {
let mut prefix = String::new();
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
break;
}
if !prefix.is_empty() {
prefix.push_str("::");
}
prefix.push_str(part);
type_refs.push(TypeRefKind::Simple(TypeRef {
target: Arc::from(prefix.as_str()),
resolved_target: None,
kind: ref_kind,
start_line: start.line,
start_col: start.col,
end_line: end.line,
end_col: end.col,
}));
}
}
}
}
}
}
type_refs
}
fn extract_from_normalized_dependency(
symbols: &mut Vec<HirSymbol>,
ctx: &mut ExtractionContext,
dep: &NormalizedDependency,
) {
let mut type_refs = extract_type_refs_from_normalized(&dep.sources, &ctx.line_index);
type_refs.extend(extract_type_refs_from_normalized(
&dep.targets,
&ctx.line_index,
));
type_refs.extend(extract_type_refs_from_normalized(
&dep.relationships,
&ctx.line_index,
));
if let Some(name) = &dep.name {
let qualified_name = ctx.qualified_name(name);
let span = ctx.range_to_info(dep.range);
symbols.push(HirSymbol {
name: Arc::from(name.as_str()),
short_name: dep.short_name.as_ref().map(|s| Arc::from(s.as_str())),
qualified_name: Arc::from(qualified_name.as_str()),
element_id: new_element_id(),
kind: SymbolKind::Dependency,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs,
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
} else if !type_refs.is_empty() {
let span = ctx.range_to_info(dep.range);
symbols.push(HirSymbol {
name: Arc::from("<anonymous-dependency>"),
short_name: None,
qualified_name: Arc::from(format!("{}::<anonymous-dependency>", ctx.prefix)),
element_id: new_element_id(),
kind: SymbolKind::Dependency,
file: ctx.file,
start_line: span.start_line,
start_col: span.start_col,
end_line: span.end_line,
end_col: span.end_col,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs,
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
});
}
}
#[derive(Clone, Copy, Debug, Default)]
struct SpanInfo {
start_line: u32,
start_col: u32,
end_line: u32,
end_col: u32,
}
fn strip_quotes(s: &str) -> String {
if s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2 {
s[1..s.len() - 1].to_string()
} else {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_kind_display() {
assert_eq!(SymbolKind::PartDefinition.display(), "Part def");
assert_eq!(SymbolKind::PartUsage.display(), "Part");
}
#[test]
fn test_strip_quotes() {
assert_eq!(strip_quotes("'hello'"), "hello");
assert_eq!(strip_quotes("hello"), "hello");
assert_eq!(strip_quotes("'"), "'");
}
#[test]
fn test_extraction_context() {
let mut ctx = ExtractionContext {
file: FileId::new(0),
prefix: String::new(),
anon_counter: 0,
scope_stack: Vec::new(),
line_index: crate::base::LineIndex::new(""),
};
assert_eq!(ctx.qualified_name("Foo"), "Foo");
ctx.push_scope("Outer");
assert_eq!(ctx.qualified_name("Inner"), "Outer::Inner");
ctx.push_scope("Deep");
assert_eq!(ctx.qualified_name("Leaf"), "Outer::Deep::Leaf");
ctx.pop_scope();
assert_eq!(ctx.qualified_name("Sibling"), "Outer::Sibling");
ctx.pop_scope();
assert_eq!(ctx.qualified_name("Root"), "Root");
}
#[test]
fn test_direction_and_multiplicity_extraction() {
use crate::base::FileId;
use crate::syntax::parser::parse_content;
let source = r#"part def Vehicle {
in port fuelIn : FuelType[1];
out port exhaust : GasType[0..*];
inout port control : ControlType[1..5];
part wheels[4];
}"#;
let syntax = parse_content(source, std::path::Path::new("test.sysml")).unwrap();
let symbols = super::extract_symbols_unified(FileId::new(0), &syntax);
let fuel_in = symbols
.iter()
.find(|s| s.name.as_ref() == "fuelIn")
.unwrap();
assert_eq!(fuel_in.direction, Some(Direction::In));
assert_eq!(
fuel_in.multiplicity,
Some(Multiplicity {
lower: Some(1),
upper: Some(1)
})
);
let exhaust = symbols
.iter()
.find(|s| s.name.as_ref() == "exhaust")
.unwrap();
assert_eq!(exhaust.direction, Some(Direction::Out));
assert_eq!(
exhaust.multiplicity,
Some(Multiplicity {
lower: Some(0),
upper: None
})
);
let control = symbols
.iter()
.find(|s| s.name.as_ref() == "control")
.unwrap();
assert_eq!(control.direction, Some(Direction::InOut));
assert_eq!(
control.multiplicity,
Some(Multiplicity {
lower: Some(1),
upper: Some(5)
})
);
let wheels = symbols
.iter()
.find(|s| s.name.as_ref() == "wheels")
.unwrap();
assert_eq!(wheels.direction, None);
assert_eq!(
wheels.multiplicity,
Some(Multiplicity {
lower: Some(4),
upper: Some(4)
})
);
}
}
#[cfg(test)]
mod test_package_span {
use super::*;
use crate::base::FileId;
use crate::syntax::parser::parse_content;
#[test]
fn test_hir_simple_package_span() {
let source = "package SimpleVehicleModel { }";
let syntax = parse_content(source, std::path::Path::new("test.sysml")).unwrap();
let symbols = extract_symbols_unified(FileId(1), &syntax);
let pkg_sym = symbols
.iter()
.find(|s| s.name.as_ref() == "SimpleVehicleModel")
.unwrap();
println!(
"Package symbol: name='{}' start=({},{}), end=({},{})",
pkg_sym.name, pkg_sym.start_line, pkg_sym.start_col, pkg_sym.end_line, pkg_sym.end_col
);
assert_eq!(pkg_sym.start_col, 8, "start_col should be 8");
assert_eq!(pkg_sym.end_col, 26, "end_col should be 26");
}
#[test]
fn test_hir_nested_package_span() {
let source = r#"package VehicleIndividuals {
package IndividualDefinitions {
}
}"#;
let syntax = parse_content(source, std::path::Path::new("test.sysml")).unwrap();
let symbols = extract_symbols_unified(FileId(1), &syntax);
for sym in &symbols {
println!(
"Symbol: name='{}' kind={:?} start=({},{}), end=({},{})",
sym.name, sym.kind, sym.start_line, sym.start_col, sym.end_line, sym.end_col
);
}
let outer = symbols
.iter()
.find(|s| s.name.as_ref() == "VehicleIndividuals")
.unwrap();
assert_eq!(outer.start_col, 8, "outer start_col should be 8");
assert_eq!(
outer.end_col, 26,
"outer end_col should be 26 (8 + 18 = 26)"
);
let nested = symbols
.iter()
.find(|s| s.name.as_ref() == "IndividualDefinitions")
.unwrap();
println!(
"Nested package: start_col={}, end_col={}",
nested.start_col, nested.end_col
);
assert_eq!(nested.start_col, 9, "nested start_col should be 9");
assert_eq!(nested.end_col, 30, "nested end_col should be 30");
}
}
fn extract_view_data_from_definition(
def: &NormalizedDefinition,
kind: NormalizedDefKind,
) -> Option<crate::hir::views::ViewData> {
use crate::hir::views::{
ExposeRelationship, FilterCondition, ViewData, ViewDefinition, WildcardKind,
};
match kind {
NormalizedDefKind::View => {
let mut view_def = ViewDefinition::new();
for child in &def.children {
match child {
NormalizedElement::Expose(expose) => {
let wildcard = if expose.is_recursive {
WildcardKind::Recursive
} else if expose.import_path.ends_with("::*") {
WildcardKind::Direct
} else {
WildcardKind::None
};
let expose_rel = ExposeRelationship::new(
Arc::from(expose.import_path.as_str()),
wildcard,
);
view_def.add_expose(expose_rel);
}
NormalizedElement::Filter(filter) => {
for meta_ref in &filter.metadata_refs {
let filter_cond =
FilterCondition::metadata(Arc::from(meta_ref.as_str()));
view_def.add_filter(filter_cond);
}
}
_ => {} }
}
Some(ViewData::ViewDefinition(view_def))
}
NormalizedDefKind::Viewpoint => {
Some(ViewData::ViewpointDefinition(
crate::hir::views::ViewpointDefinition {
stakeholders: Vec::new(),
concerns: Vec::new(),
span: None, },
))
}
NormalizedDefKind::Rendering => {
Some(ViewData::RenderingDefinition(
crate::hir::views::RenderingDefinition {
layout: None,
span: None, },
))
}
_ => None,
}
}
fn extract_view_data_from_usage(
_usage: &NormalizedUsage,
_kind: NormalizedUsageKind,
_typed_by: Option<&Arc<str>>,
) -> Option<crate::hir::views::ViewData> {
use crate::hir::views::{ViewData, ViewUsage};
match _kind {
NormalizedUsageKind::View => {
Some(ViewData::ViewUsage(ViewUsage::new(_typed_by.cloned())))
}
NormalizedUsageKind::Viewpoint => Some(ViewData::ViewpointUsage(
crate::hir::views::ViewpointUsage {
viewpoint_def: _typed_by.cloned(),
span: None, },
)),
NormalizedUsageKind::Rendering => Some(ViewData::RenderingUsage(
crate::hir::views::RenderingUsage {
rendering_def: _typed_by.cloned(),
span: None, },
)),
_ => None,
}
}