Skip to content

Commit 371edcc

Browse files
xiangu-batwhoe-jo
authored andcommitted
add source location for class
1 parent c82be4d commit 371edcc

21 files changed

Lines changed: 1086 additions & 928 deletions

File tree

‎cpp/libclang/src/visitor/src/class_parser_helper.rs‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -660,19 +660,3 @@ fn build_fqn_from_entity(entity: &Entity) -> String {
660660
parts.reverse();
661661
collapse_std_internal_namespaces(parts).join("::")
662662
}
663-
664-
pub fn to_workspace_relative_or_abs_path(abs_path: std::path::PathBuf) -> String {
665-
// Make path relative to current working directory (workspace root)
666-
// so golden files stay portable across machines.
667-
if let Ok(cwd) = std::env::current_dir() {
668-
if let Ok(rel) = abs_path.strip_prefix(&cwd) {
669-
return normalize_source_path(rel.to_string_lossy().as_ref());
670-
}
671-
}
672-
673-
normalize_source_path(abs_path.to_string_lossy().as_ref())
674-
}
675-
676-
fn normalize_source_path(path: &str) -> String {
677-
path.strip_prefix("./").unwrap_or(path).to_string()
678-
}

‎cpp/libclang/src/visitor/src/class_visitor.rs‎

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ use std::collections::HashSet;
1515

1616
use 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};
2525
use crate::visitor::AstVisitor;
2626

2727
pub 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

155156
fn 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
401407
fn 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

535567
fn resolve_in_model_target(

‎cpp/libclang/src/visitor/src/context.rs‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use serde::{Deserialize, Serialize};
1414
use std::collections::HashMap;
1515

16-
use class_diagram::SimpleEntity;
16+
use class_diagram::{SimpleEntity, SourceLocation};
1717
use sequence_logic::FunctionDef;
1818

1919
use crate::class_parser_helper::ResolvedType;
@@ -30,21 +30,29 @@ pub struct VisitContext {
3030

3131
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
3232
pub struct ParsedClassInfo {
33-
pub id: String, // class fqn
34-
pub base_classes: Vec<ResolvedType>, // base classes for inheritance relationships
33+
pub id: String, // class fqn
34+
pub base_classes: Vec<ParsedBaseClass>,
3535
pub variable_types: Vec<ParsedVariableType>,
3636
pub method_types: Vec<ParsedMethodType>,
3737
}
3838

39+
#[derive(Debug, Clone, Serialize, Deserialize)]
40+
pub struct ParsedBaseClass {
41+
pub resolved_type: ResolvedType,
42+
pub source_location: SourceLocation,
43+
}
44+
3945
#[derive(Debug, Clone, Serialize, Deserialize)]
4046
pub struct ParsedVariableType {
4147
pub name: String,
4248
pub resolved_type: ResolvedType,
49+
pub source_location: SourceLocation,
4350
}
4451

4552
#[derive(Debug, Clone, Serialize, Deserialize)]
4653
pub struct ParsedMethodType {
4754
pub name: String,
4855
pub return_type: ResolvedType,
4956
pub parameter_types: Vec<ResolvedType>,
57+
pub source_location: SourceLocation,
5058
}

‎cpp/libclang/src/visitor/src/enum_visitor.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,15 @@ impl EnumVisitor {
3535
} else {
3636
name.clone()
3737
};
38-
let (source_file, source_line) = parse_source_location(&entity);
38+
let source_location = parse_source_location(&entity);
3939

4040
Some(SimpleEntity {
4141
id: full_qualified_id,
4242
name,
4343
enclosing_namespace_id: namespace_id,
4444
entity_type: EntityType::Enum,
4545
enum_literals: get_literals(entity),
46-
source_file,
47-
source_line,
46+
source_location,
4847
..Default::default()
4948
})
5049
}
@@ -86,6 +85,7 @@ fn get_literals(entity: Entity) -> Vec<EnumLiteral> {
8685
Some(EnumLiteral {
8786
name: c.get_name()?,
8887
value: constant_val,
88+
source_location: parse_source_location(&c),
8989
})
9090
})
9191
.collect()

‎cpp/libclang/src/visitor/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod enum_visitor;
1818
mod function_visitor;
1919
pub mod visitor;
2020

21+
pub use class_parser_helper::ResolvedType;
2122
pub use class_visitor::ClassVisitor;
2223
pub use context::VisitContext;
2324
pub use enum_visitor::EnumVisitor;

‎plantuml/parser/puml_lobster/src/lib.rs‎

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,29 @@ fn comp_model_to_lobster(model: &HashMap<String, LogicComponent>, source_path: &
5454
/// For [`EntityType::Interface`] entities every method becomes its own lobster
5555
/// item with id `{entity.id}.{method.name}` and kind `"Method"`. All other
5656
/// entity types are emitted as a single item (one per entity).
57-
///
58-
/// If an entity carries explicit source location metadata that is used;
59-
/// otherwise `source_path` is used and the line is emitted as `null` because
60-
/// LOBSTER does not accept `0`.
61-
fn class_model_to_lobster(model: &ClassDiagram, source_path: &str) -> Value {
57+
fn class_model_to_lobster(model: &ClassDiagram) -> Value {
6258
let items: Vec<Value> = model
6359
.entities
6460
.iter()
6561
.flat_map(|entity| {
66-
let source_file = entity.source_file.as_deref().unwrap_or(source_path);
62+
let source_file = entity.source_location.file.as_ref();
63+
let line = entity.source_location.line;
64+
let source_line = (line != 0).then_some(line);
6765

6866
if entity.entity_type == EntityType::Interface {
6967
entity
7068
.methods
7169
.iter()
7270
.map(|method| {
7371
let method_id = format!("{}.{}", entity.id, method.name);
74-
build_lobster_item(&method_id, source_file, entity.source_line, "Method")
72+
build_lobster_item(&method_id, source_file, source_line, "Method")
7573
})
7674
.collect::<Vec<_>>()
7775
} else {
7876
vec![build_lobster_item(
7977
&entity.id,
8078
source_file,
81-
entity.source_line,
79+
source_line,
8280
map_entity_type_to_kind(entity.entity_type),
8381
)]
8482
}
@@ -155,13 +153,10 @@ pub fn write_lobster_to_file(
155153
) -> io::Result<PathBuf> {
156154
let lobster = match model {
157155
LobsterModel::Component(component_model) => {
158-
let source_str = input_path.to_string_lossy().into_owned();
159-
comp_model_to_lobster(component_model, &source_str)
160-
}
161-
LobsterModel::Class(class_model) => {
162-
let source_str = input_path.to_string_lossy().into_owned();
163-
class_model_to_lobster(class_model, &source_str)
156+
let source_path = input_path.to_string_lossy();
157+
comp_model_to_lobster(component_model, source_path.as_ref())
164158
}
159+
LobsterModel::Class(class_model) => class_model_to_lobster(class_model),
165160
LobsterModel::Empty => empty_lobster_document(),
166161
};
167162

‎plantuml/parser/puml_parser/src/class_diagram/BUILD‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ rust_library(
3939
deps = [
4040
"//plantuml/parser/puml_parser:parser_core",
4141
"//plantuml/parser/puml_utils",
42+
"//tools/metamodel/common:source_location",
4243
"@crates//:log",
4344
"@crates//:pest",
4445
"@crates//:serde",

0 commit comments

Comments
 (0)