@@ -15,13 +15,13 @@ use std::collections::HashSet;
1515
1616use class_diagram:: {
1717 EntityType , FunctionArgument , MemberVariable , Method , MethodModifier , RelationType ,
18- Relationship , SimpleEntity , TemplateParameter , TypeAlias , Visibility ,
18+ Relationship , SimpleEntity , SourceLocation , TemplateParameter , TypeAlias , Visibility ,
1919} ;
2020
21- use crate :: class_parser_helper:: {
22- render_type_for_display, resolve_type, to_workspace_relative_or_abs_path, ResolvedType ,
21+ use crate :: class_parser_helper:: { render_type_for_display, resolve_type, ResolvedType } ;
22+ use crate :: context:: {
23+ ParsedBaseClass , ParsedClassInfo , ParsedMethodType , ParsedVariableType , VisitContext ,
2324} ;
24- use crate :: context:: { ParsedClassInfo , ParsedMethodType , ParsedVariableType , VisitContext } ;
2525use crate :: visitor:: AstVisitor ;
2626
2727pub struct ClassVisitor ;
@@ -87,7 +87,7 @@ impl ClassVisitor {
8787
8888 class_entity. entity_type = infer_entity_type_from_members ( entity. get_kind ( ) , & class_entity) ;
8989
90- ( class_entity. source_file , class_entity . source_line ) = parse_source_location ( entity) ;
90+ class_entity. source_location = parse_source_location ( entity) ;
9191
9292 Some ( ( builder, class_entity) )
9393 }
@@ -96,7 +96,10 @@ impl ClassVisitor {
9696 match entity. get_kind ( ) {
9797 EntityKind :: BaseSpecifier => {
9898 if let Some ( base_type) = entity. get_type ( ) {
99- builder. base_classes . push ( resolve_type ( & base_type) ) ;
99+ builder. base_classes . push ( ParsedBaseClass {
100+ resolved_type : resolve_type ( & base_type) ,
101+ source_location : parse_source_location ( entity) ,
102+ } ) ;
100103 }
101104 }
102105 EntityKind :: Method | EntityKind :: Constructor | EntityKind :: Destructor => {
@@ -138,18 +141,16 @@ impl ClassVisitor {
138141 }
139142}
140143
141- pub ( crate ) fn parse_source_location ( entity : & Entity ) -> ( Option < String > , Option < u32 > ) {
144+ pub ( crate ) fn parse_source_location ( entity : & Entity ) -> SourceLocation {
142145 let Some ( location) = entity. get_location ( ) else {
143- return ( None , None ) ;
146+ return SourceLocation :: default ( ) ;
144147 } ;
145148
146149 let file_location = location. get_file_location ( ) ;
147150 let source_file = file_location
148151 . file
149- . map ( |f| to_workspace_relative_or_abs_path ( f. get_path ( ) ) ) ;
150- let source_line = Some ( file_location. line ) ;
151-
152- ( source_file, source_line)
152+ . map ( |f| f. get_path ( ) . to_string_lossy ( ) . to_string ( ) ) ;
153+ SourceLocation :: new ( source_file. unwrap_or_default ( ) , file_location. line )
153154}
154155
155156fn collect_variable_type ( entity : & Entity ) -> Option < ParsedVariableType > {
@@ -159,6 +160,7 @@ fn collect_variable_type(entity: &Entity) -> Option<ParsedVariableType> {
159160 Some ( ParsedVariableType {
160161 name,
161162 resolved_type : resolve_type ( & field_type) ,
163+ source_location : parse_source_location ( entity) ,
162164 } )
163165}
164166
@@ -178,6 +180,7 @@ fn collect_method_type(entity: &Entity, builder: &mut ParsedClassInfo) -> Parsed
178180 name,
179181 return_type,
180182 parameter_types,
183+ source_location : parse_source_location ( entity) ,
181184 } ;
182185 builder. method_types . push ( parsed_method_type. clone ( ) ) ;
183186
@@ -210,6 +213,7 @@ fn parse_type_alias(entity: &Entity) -> Option<TypeAlias> {
210213 Some ( TypeAlias {
211214 alias,
212215 original_type,
216+ source_location : parse_source_location ( entity) ,
213217 } )
214218}
215219
@@ -265,6 +269,7 @@ fn parse_method(entity: &Entity, parsed_method_type: &ParsedMethodType) -> Optio
265269 ( kind == EntityKind :: Constructor , MethodModifier :: Constructor ) ,
266270 ( kind == EntityKind :: Destructor , MethodModifier :: Destructor ) ,
267271 ] ) ,
272+ source_location : parse_source_location ( entity) ,
268273 } )
269274}
270275
@@ -279,6 +284,7 @@ fn parse_variable(
279284 } ) ,
280285 visibility : parse_visibility ( entity) ,
281286 is_static : entity. get_kind ( ) == EntityKind :: VarDecl ,
287+ source_location : parse_source_location ( entity) ,
282288 } )
283289}
284290
@@ -400,13 +406,16 @@ fn infer_entity_type_from_members(kind: EntityKind, class: &SimpleEntity) -> Ent
400406// Relationship part
401407fn build_relationships_for_class ( ctx : & mut VisitContext , builder : & ParsedClassInfo ) {
402408 for base in & builder. base_classes {
403- let resolved_base = base. referenced_entity_id ( ) . unwrap_or_else ( || {
404- panic ! (
405- "Unresolved base type '{}' referenced by '{}'" ,
406- base. render_for_display( ) ,
407- builder. id
408- )
409- } ) ;
409+ let resolved_base = base
410+ . resolved_type
411+ . referenced_entity_id ( )
412+ . unwrap_or_else ( || {
413+ panic ! (
414+ "Unresolved base type '{}' referenced by '{}'" ,
415+ base. resolved_type. render_for_display( ) ,
416+ builder. id
417+ )
418+ } ) ;
410419
411420 let Some ( target_class) = ctx. types . get ( resolved_base) else {
412421 // Base type is not in the type map — it is likely an external dependency
@@ -431,11 +440,21 @@ fn build_relationships_for_class(ctx: &mut VisitContext, builder: &ParsedClassIn
431440 . get_mut ( & builder. id )
432441 . expect ( "Source class must exist before building relationships" ) ;
433442
434- add_relationship ( class, resolved_base. to_string ( ) , relation_type) ;
443+ add_relationship (
444+ class,
445+ resolved_base. to_string ( ) ,
446+ relation_type,
447+ & base. source_location ,
448+ ) ;
435449 }
436450}
437451
438- fn add_relationship ( class : & mut SimpleEntity , target : String , relation_type : RelationType ) {
452+ fn add_relationship (
453+ class : & mut SimpleEntity ,
454+ target : String ,
455+ relation_type : RelationType ,
456+ source_location : & SourceLocation ,
457+ ) {
439458 if target == class. id {
440459 return ;
441460 }
@@ -446,9 +465,18 @@ fn add_relationship(class: &mut SimpleEntity, target: String, relation_type: Rel
446465 relation_type,
447466 source_multiplicity : None ,
448467 target_multiplicity : None ,
468+ source_location : source_location. clone ( ) ,
449469 } ;
450470
451- if !class. relationships . contains ( & relationship) {
471+ let duplicate = class. relationships . iter ( ) . any ( |existing| {
472+ existing. source == relationship. source
473+ && existing. target == relationship. target
474+ && existing. relation_type == relationship. relation_type
475+ && existing. source_multiplicity == relationship. source_multiplicity
476+ && existing. target_multiplicity == relationship. target_multiplicity
477+ } ) ;
478+
479+ if !duplicate {
452480 class. relationships . push ( relationship) ;
453481 }
454482}
@@ -478,6 +506,7 @@ fn infer_variable_relationships(
478506 known_class_ids,
479507 RelationType :: Aggregation ,
480508 RelationType :: Composition ,
509+ & variable. source_location ,
481510 ) ;
482511 }
483512}
@@ -494,6 +523,7 @@ fn infer_method_relationships(
494523 known_class_ids,
495524 RelationType :: Dependency ,
496525 RelationType :: Association ,
526+ & method. source_location ,
497527 ) ;
498528
499529 for parameter_type in & method. parameter_types {
@@ -503,6 +533,7 @@ fn infer_method_relationships(
503533 known_class_ids,
504534 RelationType :: Dependency ,
505535 RelationType :: Association ,
536+ & method. source_location ,
506537 ) ;
507538 }
508539 }
@@ -514,6 +545,7 @@ fn add_relationship_from_resolved_type(
514545 known_class_ids : & HashSet < String > ,
515546 non_owning_relation : RelationType ,
516547 owning_relation : RelationType ,
548+ source_location : & SourceLocation ,
517549) {
518550 let Some ( raw_target) = resolved_type. relationship_target_entity_id ( ) else {
519551 return ;
@@ -529,7 +561,7 @@ fn add_relationship_from_resolved_type(
529561 owning_relation
530562 } ;
531563
532- add_relationship ( class, target, relation_type) ;
564+ add_relationship ( class, target, relation_type, source_location ) ;
533565}
534566
535567fn resolve_in_model_target (
0 commit comments