From 467c021e17bf94ecab195ebd34a9b0727ff00a71 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Fri, 26 May 2023 21:05:28 +0200 Subject: [PATCH] Added generation of package diagrams from directory structure instead of namespaces --- src/class_diagram/model/diagram.cc | 4 +- src/config/yaml_decoders.cc | 7 + src/package_diagram/model/diagram.cc | 30 ++- src/package_diagram/model/diagram.h | 7 +- .../visitor/translation_unit_visitor.cc | 200 ++++++++++++++++-- .../visitor/translation_unit_visitor.h | 13 +- tests/t30002/t30002.cc | 11 +- tests/t30002/test_case.h | 2 + tests/test_cases.cc | 1 + tests/test_cases.yaml | 3 + util/templates/test_cases/test_case.h | 2 +- 11 files changed, 248 insertions(+), 32 deletions(-) diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 1d3afdaf..473d2e10 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -251,10 +251,10 @@ bool diagram::add_class_fs( const auto base_name = c->name(); const auto full_name = c->full_name(false); const auto id = c->id(); - auto &cc = *c; + auto cc = std::ref(*c); if (add_element(parent_path, std::move(c))) { - classes_.push_back(std::ref(cc)); + classes_.push_back(cc); return true; } else { diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 0ec9f095..fc885031 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -469,6 +469,13 @@ template <> struct convert { return false; get_option(node, rhs.layout); + get_option(node, rhs.relative_to); + get_option(node, rhs.package_type); + + // Ensure relative_to has a value + if (!rhs.relative_to.has_value) + rhs.relative_to.set( + std::filesystem::current_path().lexically_normal()); return true; } diff --git a/src/package_diagram/model/diagram.cc b/src/package_diagram/model/diagram.cc index ff197f1f..dfe64537 100644 --- a/src/package_diagram/model/diagram.cc +++ b/src/package_diagram/model/diagram.cc @@ -36,7 +36,8 @@ diagram::packages() const void diagram::add_package(std::unique_ptr &&p) { - LOG_DBG("Adding package: {}, {}", p->name(), p->full_name(true)); + LOG_DBG( + "Adding package: {}, {}, [{}]", p->name(), p->full_name(true), p->id()); auto ns = p->get_relative_namespace(); @@ -45,6 +46,31 @@ void diagram::add_package(std::unique_ptr &&p) add_element(ns, std::move(p)); } +void diagram::add_package_fs(const common::model::path &parent_path, + std::unique_ptr &&p) +{ + LOG_DBG("Adding package: {}, {}", p->name(), p->full_name(true)); + + // Make sure all parent directories are already packages in the + // model + for (auto it = parent_path.begin(); it != parent_path.end(); it++) { + auto pkg = + std::make_unique(p->using_namespace()); + pkg->set_name(*it); + auto ns = common::model::path(parent_path.begin(), it); + // ns.pop_back(); + pkg->set_namespace(ns); + pkg->set_id(common::to_id(pkg->full_name(false))); + + add_package_fs(ns, std::move(pkg)); + } + + auto pp = std::ref(*p); + if (add_element(parent_path, std::move(p))) { + packages_.emplace_back(pp); + } +} + common::optional_ref diagram::get_package( const std::string &name) const { @@ -79,6 +105,8 @@ common::optional_ref diagram::get( common::optional_ref diagram::get( const clanguml::common::model::diagram_element::id_t id) const { + LOG_DBG("Looking for package with id {}", id); + return get_package(id); } diff --git a/src/package_diagram/model/diagram.h b/src/package_diagram/model/diagram.h index 07e0d1a4..4c7cf528 100644 --- a/src/package_diagram/model/diagram.h +++ b/src/package_diagram/model/diagram.h @@ -48,14 +48,17 @@ public: common::optional_ref get( clanguml::common::model::diagram_element::id_t id) const override; - void add_package(std::unique_ptr &&p); - common::optional_ref get_package( const std::string &name) const; common::optional_ref get_package( clanguml::common::model::diagram_element::id_t id) const; + void add_package(std::unique_ptr &&p); + + void add_package_fs(const common::model::path &parent_path, + std::unique_ptr &&p); + std::string to_alias( clanguml::common::model::diagram_element::id_t /*id*/) const; diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index 3519a5cb..2c0b824c 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -45,6 +45,9 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) { assert(ns != nullptr); + if (config().package_type() == config::package_type_t::kDirectory) + return true; + if (ns->isAnonymousNamespace() || ns->isInline()) return true; @@ -144,24 +147,91 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) return true; } -void translation_unit_visitor::add_relationships( - clang::DeclContext *cls, found_relationships_t &relationships) -{ - int64_t current_package_id{0}; - const auto *namespace_context = cls->getEnclosingNamespaceContext(); - if (namespace_context != nullptr && namespace_context->isNamespace()) { - current_package_id = - common::to_id(*llvm::cast(namespace_context)); +bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *decl) +{ + assert(decl != nullptr); + + // Skip system headers + if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin())) + return true; + + found_relationships_t relationships; + + if (decl->isCompleteDefinition()) { + process_record_children(*decl, relationships); + add_relationships(decl, relationships); } + return true; +} + +bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *decl) +{ + assert(decl != nullptr); + + // Skip system headers + if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin())) + return true; + + found_relationships_t relationships; + + if (decl->isCompleteDefinition()) { + add_relationships(decl, relationships); + } + + return true; +} + +bool translation_unit_visitor::VisitClassTemplateDecl( + clang::ClassTemplateDecl *decl) +{ + assert(decl != nullptr); + + // Skip system headers + if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin())) + return true; + + found_relationships_t relationships; + + process_class_declaration(*decl->getTemplatedDecl(), relationships); + add_relationships(decl, relationships); + + return true; +} + +void translation_unit_visitor::add_relationships( + clang::Decl *cls, found_relationships_t &relationships) +{ + // If this diagram has directory packages, first make sure that the + // package for current directory is already in the model + if (config().package_type() == config::package_type_t::kDirectory) { + auto file = source_manager().getFilename(cls->getLocation()).str(); + auto relative_file = + util::path_to_url(config().make_path_relative(file)); + + common::model::path parent_path{ + relative_file, common::model::path_type::kFilesystem}; + parent_path.pop_back(); + auto pkg_name = parent_path.name(); + parent_path.pop_back(); + + auto pkg = std::make_unique( + config().using_namespace()); + + pkg->set_name(pkg_name); + pkg->set_id(get_package_id(cls)); + + diagram().add_package_fs(parent_path, std::move(pkg)); + } + + auto current_package_id = get_package_id(cls); + if (current_package_id == 0) // These are relationships to a global namespace, and we don't care // about those return; - assert(current_package_id != 0); - auto current_package = diagram().get(current_package_id); if (current_package) { @@ -175,6 +245,33 @@ void translation_unit_visitor::add_relationships( } } +common::model::diagram_element::id_t translation_unit_visitor::get_package_id( + const clang::Decl *cls) +{ + if (config().package_type() == config::package_type_t::kNamespace) { + const auto *namespace_context = + cls->getDeclContext()->getEnclosingNamespaceContext(); + if (namespace_context != nullptr && namespace_context->isNamespace()) { + return common::to_id( + *llvm::cast(namespace_context)); + } + + return {}; + } + else { + auto file = source_manager() + .getFilename(cls->getSourceRange().getBegin()) + .str(); + auto relative_file = + util::path_to_url(config().make_path_relative(file)); + common::model::path parent_path{ + relative_file, common::model::path_type::kFilesystem}; + parent_path.pop_back(); + + return common::to_id(parent_path.to_string()); + } +} + void translation_unit_visitor::process_class_declaration( const clang::CXXRecordDecl &cls, found_relationships_t &relationships) { @@ -255,6 +352,44 @@ void translation_unit_visitor::process_method( } } +void translation_unit_visitor::process_record_children( + const clang::RecordDecl &cls, found_relationships_t &relationships) +{ + if (const auto *decl_context = + clang::dyn_cast_or_null(&cls); + decl_context != nullptr) { + // Iterate over class template methods + for (auto const *decl_iterator : decl_context->decls()) { + auto const *method_template = + llvm::dyn_cast_or_null( + decl_iterator); + if (method_template == nullptr) + continue; + + process_template_method(*method_template, relationships); + } + } + + // Iterate over regular class fields + for (const auto *field : cls.fields()) { + if (field != nullptr) + process_field(*field, relationships); + } + + // Static fields have to be processed by iterating over variable + // declarations + for (const auto *decl : cls.decls()) { + if (decl->getKind() == clang::Decl::Var) { + const clang::VarDecl *variable_declaration{ + clang::dyn_cast_or_null(decl)}; + if ((variable_declaration != nullptr) && + variable_declaration->isStaticDataMember()) { + process_static_field(*variable_declaration, relationships); + } + } + } +} + void translation_unit_visitor::process_template_method( const clang::FunctionTemplateDecl &method, found_relationships_t &relationships) @@ -334,15 +469,22 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type, relationships, relationship_t::kAggregation); } else if (type->isEnumeralType()) { - if (const auto *enum_type = type->getAs(); - enum_type != nullptr) { + if (const auto *enum_decl = type->getAs()->getDecl(); + enum_decl != nullptr) { relationships.emplace_back( - common::to_id(*enum_type), relationship_hint); + get_package_id(enum_decl), relationship_hint); } } else if (const auto *template_specialization_type = type->getAs()) { if (template_specialization_type != nullptr) { + // Add dependency to template declaration + relationships.emplace_back( + get_package_id(template_specialization_type->getTemplateName() + .getAsTemplateDecl()), + relationship_hint); + + // Add dependencies to template arguments for (const auto &template_argument : template_specialization_type->template_arguments()) { const auto template_argument_kind = template_argument.getKind(); @@ -389,17 +531,29 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type, } } else if (type->isRecordType() && type->getAsCXXRecordDecl()) { - const auto *namespace_context = - type->getAsCXXRecordDecl()->getEnclosingNamespaceContext(); - if (namespace_context != nullptr && namespace_context->isNamespace()) { - const auto *namespace_declaration = - clang::cast(namespace_context); + if (config().package_type() == config::package_type_t::kNamespace) { + const auto *namespace_context = + type->getAsCXXRecordDecl()->getEnclosingNamespaceContext(); + if (namespace_context != nullptr && + namespace_context->isNamespace()) { + const auto *namespace_declaration = + clang::cast(namespace_context); - if (namespace_declaration != nullptr && - diagram().should_include( - common::get_qualified_name(*namespace_declaration))) { - const auto target_id = common::to_id( - *clang::cast(namespace_context)); + if (namespace_declaration != nullptr && + diagram().should_include( + common::get_qualified_name(*namespace_declaration))) { + const auto target_id = + get_package_id(type->getAsCXXRecordDecl()); + relationships.emplace_back(target_id, relationship_hint); + result = true; + } + } + } + else { + if (diagram().should_include( + common::get_qualified_name(*type->getAsCXXRecordDecl()))) { + const auto target_id = + get_package_id(type->getAsCXXRecordDecl()); relationships.emplace_back(target_id, relationship_hint); result = true; } diff --git a/src/package_diagram/visitor/translation_unit_visitor.h b/src/package_diagram/visitor/translation_unit_visitor.h index eb6a1243..67859e86 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.h +++ b/src/package_diagram/visitor/translation_unit_visitor.h @@ -49,8 +49,14 @@ public: virtual bool VisitNamespaceDecl(clang::NamespaceDecl *ns); + virtual bool VisitEnumDecl(clang::EnumDecl *decl); + virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *cls); + virtual bool VisitRecordDecl(clang::RecordDecl *cls); + + virtual bool VisitClassTemplateDecl(clang::ClassTemplateDecl *decl); + virtual bool VisitFunctionDecl(clang::FunctionDecl *function_declaration); clanguml::package_diagram::model::diagram &diagram() { return diagram_; } @@ -60,12 +66,17 @@ public: void finalize() { } private: + common::model::diagram_element::id_t get_package_id(const clang::Decl *cls); + void process_class_declaration( const clang::CXXRecordDecl &cls, found_relationships_t &relationships); void process_class_children( const clang::CXXRecordDecl &cls, found_relationships_t &relationships); + void process_record_children( + const clang::RecordDecl &cls, found_relationships_t &relationships); + void process_class_bases( const clang::CXXRecordDecl &cls, found_relationships_t &relationships); @@ -90,7 +101,7 @@ private: common::model::relationship_t::kDependency); void add_relationships( - clang::DeclContext *cls, found_relationships_t &relationships); + clang::Decl *cls, found_relationships_t &relationships); // Reference to the output diagram model clanguml::package_diagram::model::diagram &diagram_; diff --git a/tests/t30002/t30002.cc b/tests/t30002/t30002.cc index 864cd485..59cc7500 100644 --- a/tests/t30002/t30002.cc +++ b/tests/t30002/t30002.cc @@ -11,7 +11,9 @@ namespace A1 { struct CA { }; } namespace A2 { -struct CB { }; +template struct CB { + T cb; +}; } namespace A3 { struct CC { }; @@ -58,12 +60,15 @@ struct CP { }; namespace A17 { struct CR { }; } +namespace A18 { +enum class S { s1, s2, s3 }; +} } namespace B::BB::BBB { class CBA : public A::AA::A6::CF { public: A::AA::A1::CA *ca_; - A::AA::A2::CB cb_; + A::AA::A2::CB cb_; std::shared_ptr cc_; std::map> *cd_; std::array co_; @@ -91,6 +96,8 @@ public: { return {}; } + + A::AA::A18::S s; }; void cj(std::unique_ptr /*cj_*/) { } diff --git a/tests/t30002/test_case.h b/tests/t30002/test_case.h index ef88d9bf..b589496b 100644 --- a/tests/t30002/test_case.h +++ b/tests/t30002/test_case.h @@ -51,6 +51,7 @@ TEST_CASE("t30002", "[test-case][package]") REQUIRE_THAT(puml, IsPackage("A15")); REQUIRE_THAT(puml, IsPackage("A16")); REQUIRE_THAT(puml, IsPackage("A17")); + REQUIRE_THAT(puml, IsPackage("A18")); REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A1"))); REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A2"))); @@ -69,6 +70,7 @@ TEST_CASE("t30002", "[test-case][package]") REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A15"))); REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A16"))); REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A17"))); + REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A18"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 0df23aa8..8605ad19 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -354,6 +354,7 @@ using namespace clanguml::test::matchers; #include "t30007/test_case.h" #include "t30008/test_case.h" #include "t30009/test_case.h" +#include "t30010/test_case.h" /// /// Include diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 15593dec..7302d814 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -308,6 +308,9 @@ test_cases: - name: t30009 title: Together layout hint test description: + - name: t30010 + title: Package diagram with packages from directory structure + description: Include diagrams: - name: t40001 title: Basic include graph diagram test case diff --git a/util/templates/test_cases/test_case.h b/util/templates/test_cases/test_case.h index c5f55f42..9289b364 100644 --- a/util/templates/test_cases/test_case.h +++ b/util/templates/test_cases/test_case.h @@ -42,7 +42,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]") } { - auto j = generate_class_json(diagram, *model); + auto j = generate_{{ type }}_json(diagram, *model); using namespace json;