diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 68af5b36..0f8ae605 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -52,6 +52,13 @@ void generator::generate_link( ostr << "]]]"; } +std::string generator::render_name(std::string name) const +{ + util::replace_all(name, "##", "::"); + + return name; +} + void generator::generate_alias(const class_ &c, std::ostream &ostr) const { std::string class_type{"class"}; @@ -64,10 +71,9 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const else full_name = c.full_name(); - if (full_name.empty()) - full_name = "<>"; + assert(!full_name.empty()); - ostr << class_type << " \"" << full_name; + ostr << class_type << " \"" << render_name(full_name); ostr << "\" as " << c.alias() << '\n'; @@ -82,7 +88,7 @@ void generator::generate_alias(const enum_ &e, std::ostream &ostr) const << " \"" << e.name(); else ostr << "enum" - << " \"" << e.full_name(); + << " \"" << render_name(e.full_name()); ostr << "\" as " << e.alias() << '\n'; @@ -225,7 +231,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const ostr << "{static} "; ostr << plantuml_common::to_plantuml(m.access()) << m.name() << " : " - << uns.relative(m.type()); + << render_name(uns.relative(m.type())); if (m_config.generate_links) { generate_link(ostr, m); diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.h b/src/class_diagram/generators/plantuml/class_diagram_generator.h index a5b2a1a6..1c774019 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.h +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.h @@ -76,6 +76,9 @@ public: void generate_relationships(const package &p, std::ostream &ostr) const; void generate(std::ostream &ostr) const override; + +private: + std::string render_name(std::string name) const; }; } diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 1f3d4c15..da30df25 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -146,7 +146,8 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) std::string qualified_name = common::get_qualified_name(*enm); namespace_ ns{qualified_name}; ns.pop_back(); - e.set_name(enm->getNameAsString()); + + e.set_name(common::get_tag_name(*enm)); e.set_namespace(ns); e.set_id(common::to_id(*enm)); set_ast_local_id(enm->getID(), e.id()); @@ -358,7 +359,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) forward_declarations_.erase(id); if (diagram_.should_include(class_model)) { - LOG_DBG("Adding class {} with id {}", class_model.full_name(), + LOG_DBG("Adding class {} with id {}", class_model.full_name(false), class_model.id()); diagram_.add_class(std::move(c_ptr)); @@ -387,7 +388,8 @@ std::unique_ptr translation_unit_visitor::create_class_declaration( namespace_ ns{qualified_name}; ns.pop_back(); - c.set_name(cls->getNameAsString()); + + c.set_name(common::get_tag_name(*cls)); c.set_namespace(ns); c.set_id(common::to_id(*cls)); @@ -415,6 +417,7 @@ void translation_unit_visitor::process_class_declaration( if (cls.getParent()->isRecord()) { process_record_containment(cls, c); + c.nested(true); } c.complete(true); @@ -1809,10 +1812,6 @@ void translation_unit_visitor::process_field( common::to_string(field_type, field_declaration.getASTContext()); // The field name const auto field_name = field_declaration.getNameAsString(); - // If for any reason clang reports the type as empty string, make sure - // it has some default name - if (type_name.empty()) - type_name = "<>"; class_member field{ detail::access_specifier_to_access_t(field_declaration.getAccess()), diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 90ef71c1..5297cb1a 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -39,6 +39,35 @@ std::optional get_enclosing_namespace( common::get_qualified_name(*namespace_declaration)}; } +std::string get_tag_name(const clang::TagDecl &declaration) +{ + auto base_name = declaration.getNameAsString(); + if (base_name.empty()) { + base_name = + fmt::format("(anonymous_{})", std::to_string(declaration.getID())); + } + + if (declaration.getParent() && declaration.getParent()->isRecord()) { + // If the record is nested within another record (e.g. class or struct) + // we have to maintain a containment namespace in order to ensure + // unique names within the diagram + std::deque record_parent_names; + record_parent_names.push_front(base_name); + + auto *cls_parent{declaration.getParent()}; + while (cls_parent->isRecord()) { + auto parent_name = + static_cast(cls_parent) + ->getNameAsString(); + record_parent_names.push_front(parent_name); + cls_parent = cls_parent->getParent(); + } + return fmt::format("{}", fmt::join(record_parent_names, "##")); + } + + return base_name; +} + std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, bool try_canonical) { @@ -66,6 +95,14 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, } } + // If for any reason clang reports the type as empty string, make sure + // it has some default name + if (result.empty()) + result = "(anonymous)"; + else if (util::contains(result, "unnamed struct")) { + result = common::get_tag_name(*type->getAsTagDecl()); + } + // Remove trailing spaces after commas in template arguments clanguml::util::replace_all(result, ", ", ","); diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 0395219c..1e1da03e 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -22,6 +22,7 @@ #include +#include #include #include @@ -30,12 +31,23 @@ class NamespaceDecl; } namespace clanguml::common { +std::string get_tag_name(const clang::TagDecl &declaration); template std::string get_qualified_name(const T &declaration) { auto qualified_name = declaration.getQualifiedNameAsString(); util::replace_all(qualified_name, "(anonymous namespace)", ""); util::replace_all(qualified_name, "::::", "::"); + + if constexpr (std::is_base_of_v) { + auto base_name = get_tag_name(declaration); + model::namespace_ ns{qualified_name}; + ns.pop_back(); + ns = ns | base_name; + + return ns.to_string(); + } + return qualified_name; } diff --git a/src/common/model/diagram_element.cc b/src/common/model/diagram_element.cc index e7bee8e2..5199c01b 100644 --- a/src/common/model/diagram_element.cc +++ b/src/common/model/diagram_element.cc @@ -28,6 +28,7 @@ std::atomic_uint64_t diagram_element::m_nextId = 1; diagram_element::diagram_element() : id_{0} + , nested_{false} , complete_{false} { } @@ -84,6 +85,10 @@ inja::json diagram_element::context() const return ctx; } +bool diagram_element::is_nested() const { return nested_; } + +void diagram_element::nested(bool nested) { nested_ = nested; } + bool diagram_element::complete() const { return complete_; } void diagram_element::complete(bool completed) { complete_ = completed; } diff --git a/src/common/model/diagram_element.h b/src/common/model/diagram_element.h index 69753dbb..2d58bb62 100644 --- a/src/common/model/diagram_element.h +++ b/src/common/model/diagram_element.h @@ -65,6 +65,10 @@ public: virtual inja::json context() const; + bool is_nested() const; + + void nested(bool nested); + bool complete() const; void complete(bool completed); @@ -73,7 +77,7 @@ private: id_t id_; std::string name_; std::vector relationships_; - + bool nested_; bool complete_; static std::atomic_uint64_t m_nextId; diff --git a/tests/t00004/t00004.cc b/tests/t00004/t00004.cc index 3216ba7a..d2f0a128 100644 --- a/tests/t00004/t00004.cc +++ b/tests/t00004/t00004.cc @@ -1,6 +1,11 @@ namespace clanguml { namespace t00004 { +class B { +public: + enum AA { AA_1, AA_2, AA_3 }; +}; + class A { public: void foo() const { } @@ -15,5 +20,6 @@ public: void foo2() const { } }; + } } diff --git a/tests/t00004/test_case.h b/tests/t00004/test_case.h index 4433098a..d0dd351f 100644 --- a/tests/t00004/test_case.h +++ b/tests/t00004/test_case.h @@ -41,12 +41,14 @@ TEST_CASE("t00004", "[test-case][class]") REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, EndsWith("@enduml\n")); REQUIRE_THAT(puml, IsClass(_A("A"))); - REQUIRE_THAT(puml, IsClass(_A("AA"))); - REQUIRE_THAT(puml, IsClass(_A("AAA"))); - REQUIRE_THAT(puml, IsEnum(_A("Lights"))); - REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); - REQUIRE_THAT(puml, IsInnerClass(_A("AA"), _A("AAA"))); - REQUIRE_THAT(puml, IsInnerClass(_A("AA"), _A("Lights"))); + REQUIRE_THAT(puml, IsClass(_A("A::AA"))); + REQUIRE_THAT(puml, IsClass(_A("A::AA::AAA"))); + REQUIRE_THAT(puml, IsEnum(_A("B::AA"))); + REQUIRE_THAT(puml, IsEnum(_A("A::AA::Lights"))); + REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("A::AA"))); + REQUIRE_THAT(puml, IsInnerClass(_A("A::AA"), _A("A::AA::AAA"))); + REQUIRE_THAT(puml, IsInnerClass(_A("A::AA"), _A("A::AA::Lights"))); + REQUIRE_THAT(puml, (IsMethod("foo"))); REQUIRE_THAT(puml, (IsMethod("foo2"))); diff --git a/tests/t00037/t00037.cc b/tests/t00037/t00037.cc index c2b53adb..2d17af61 100644 --- a/tests/t00037/t00037.cc +++ b/tests/t00037/t00037.cc @@ -8,6 +8,11 @@ struct ST { double y; double z; } dimensions; + + struct { + double c; + double h; + } units; }; struct A { @@ -17,6 +22,9 @@ struct A { st.dimensions.x = 1; st.dimensions.y = 1; st.dimensions.z = 1; + + st.units.c = 1; + st.units.h = 1; } ST st; diff --git a/tests/t00037/test_case.h b/tests/t00037/test_case.h index f722aac2..06bf6435 100644 --- a/tests/t00037/test_case.h +++ b/tests/t00037/test_case.h @@ -37,7 +37,7 @@ TEST_CASE("t00037", "[test-case][class]") REQUIRE_THAT(puml, IsClass(_A("ST"))); REQUIRE_THAT(puml, IsClass(_A("A"))); - REQUIRE_THAT(puml, IsInnerClass(_A("ST"), _A("<>"))); + REQUIRE_THAT(puml, IsClass(_A("ST::\\(anonymous_\\d+\\)"))); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml);