diff --git a/src/class_diagram/generators/json/class_diagram_generator.cc b/src/class_diagram/generators/json/class_diagram_generator.cc index 14a67dde..e637de97 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.cc +++ b/src/class_diagram/generators/json/class_diagram_generator.cc @@ -139,6 +139,11 @@ void generator::generate_diagram(nlohmann::json &parent) const if (config().using_namespace) parent["using_namespace"] = config().using_namespace().to_string(); + if (config().using_module) + parent["using_module"] = config().using_module(); + + if (config().generate_packages.has_value) + parent["package_type"] = to_string(config().package_type()); parent["elements"] = std::vector{}; parent["relationships"] = std::vector{}; diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 64c53dc6..0831b830 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -116,6 +116,18 @@ bool diagram::add_with_filesystem_path( return add_element(ns, std::move(p)); } +template <> +bool diagram::add_with_module_path( + const common::model::path & /*parent_path*/, + std::unique_ptr &&p) +{ + LOG_DBG("Adding module package: {}, {}", p->name(), p->full_name(true)); + + auto ns = p->get_relative_namespace(); + + return add_element(ns, std::move(p)); +} + void diagram::get_parents( clanguml::common::reference_set &parents) const { diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 546bb717..12906916 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -203,6 +203,10 @@ public: return add_with_namespace_path(std::move(e)); } + if (parent_path.type() == common::model::path_type::kModule) { + return add_with_module_path(parent_path, std::move(e)); + } + return add_with_filesystem_path(parent_path, std::move(e)); } @@ -224,8 +228,6 @@ public: */ void get_parents(clanguml::common::reference_set &parents) const; - friend void print_diagram_tree(const diagram &d, int level); - /** * @brief Check if diagram contains element by id. * @@ -252,6 +254,10 @@ private: template bool add_with_namespace_path(std::unique_ptr &&e); + template + bool add_with_module_path( + const common::model::path &parent_path, std::unique_ptr &&e); + template bool add_with_filesystem_path( const common::model::path &parent_path, std::unique_ptr &&e); @@ -317,19 +323,53 @@ bool diagram::add_with_namespace_path(std::unique_ptr &&e) return false; } +template +bool diagram::add_with_module_path( + const common::model::path &parent_path, std::unique_ptr &&e) +{ + const auto element_type = e->type_name(); + + // Make sure all parent modules are already packages in the + // model + for (auto it = parent_path.begin(); it != parent_path.end(); it++) { + auto pkg = std::make_unique( + e->using_namespace(), parent_path.type()); + pkg->set_name(*it); + auto ns = + common::model::path(parent_path.begin(), it, parent_path.type()); + // ns.pop_back(); + pkg->set_namespace(ns); + pkg->set_id(common::to_id(pkg->full_name(false))); + + add(ns, std::move(pkg)); + } + + const auto base_name = e->name(); + const auto full_name = e->full_name(false); + auto &e_ref = *e; + + if (add_element(parent_path, std::move(e))) { + element_view::add(std::ref(e_ref)); + return true; + } + + return false; +} + template bool diagram::add_with_filesystem_path( const common::model::path &parent_path, std::unique_ptr &&e) { const auto element_type = e->type_name(); - // Make sure all parent directories are already packages in the + // Make sure all parent modules are already packages in the // model for (auto it = parent_path.begin(); it != parent_path.end(); it++) { - auto pkg = - std::make_unique(e->using_namespace()); + auto pkg = std::make_unique( + e->using_namespace(), parent_path.type()); pkg->set_name(*it); - auto ns = common::model::path(parent_path.begin(), it); + auto ns = + common::model::path(parent_path.begin(), it, parent_path.type()); // ns.pop_back(); pkg->set_namespace(ns); pkg->set_id(common::to_id(pkg->full_name(false))); @@ -405,6 +445,11 @@ template <> bool diagram::add_with_namespace_path( std::unique_ptr &&p); +template <> +bool diagram::add_with_module_path( + const common::model::path &parent_path, + std::unique_ptr &&p); + template <> bool diagram::add_with_filesystem_path( const common::model::path &parent_path, diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 489b48b3..907c742f 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -213,7 +213,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( } if (!template_specialization.template_specialization_found()) { - // Only do this if we haven't found a bettern specialization during + // Only do this if we haven't found a better specialization during // construction of the template specialization const auto maybe_id = id_mapper().get_global_id(cls->getSpecializedTemplate()->getID()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ac5a3ed..594857ad 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,7 @@ file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml test_compilation_database_data/*.json) set(TEST_CASES_REQUIRING_CXX20 t00056 t00058 t00059 t00065 t00069) -set(TEST_CASES_REQUIRING_CXX20_MODULES t00070 t00071 +set(TEST_CASES_REQUIRING_CXX20_MODULES t00070 t00071 t00072 t30012 t30013 t30014 t30015) if(ENABLE_CXX_MODULES_TEST_CASES) diff --git a/tests/t00072/.clang-uml b/tests/t00072/.clang-uml new file mode 100644 index 00000000..29b85640 --- /dev/null +++ b/tests/t00072/.clang-uml @@ -0,0 +1,12 @@ +diagrams: + t00072_class: + type: class + glob: + - t00072.cc + generate_packages: true + package_type: module + include: + modules: + - t00072 + using_module: t00072 + using_namespace: clanguml::t00072 \ No newline at end of file diff --git a/tests/t00072/src/lib1.cppm b/tests/t00072/src/lib1.cppm new file mode 100644 index 00000000..8c4cc928 --- /dev/null +++ b/tests/t00072/src/lib1.cppm @@ -0,0 +1,13 @@ +export module t00072.app:lib1; + +export namespace clanguml::t00072 { +class B { }; + +template class BB { + T t; +}; + +namespace detail { +enum class BBB { bbb1, bbb2 }; +} // namespace detail +} \ No newline at end of file diff --git a/tests/t00072/src/lib1mod1.cppm b/tests/t00072/src/lib1mod1.cppm new file mode 100644 index 00000000..106b822e --- /dev/null +++ b/tests/t00072/src/lib1mod1.cppm @@ -0,0 +1,5 @@ +export module t00072.app:lib1.mod1; + +export namespace clanguml::t00072 { +class D { }; +} \ No newline at end of file diff --git a/tests/t00072/src/lib1mod2.cppm b/tests/t00072/src/lib1mod2.cppm new file mode 100644 index 00000000..4cb25b28 --- /dev/null +++ b/tests/t00072/src/lib1mod2.cppm @@ -0,0 +1,5 @@ +export module t00072.app:lib1.mod2; + +export namespace clanguml::t00072 { +class E { }; +} \ No newline at end of file diff --git a/tests/t00072/src/lib2.cppm b/tests/t00072/src/lib2.cppm new file mode 100644 index 00000000..617c545d --- /dev/null +++ b/tests/t00072/src/lib2.cppm @@ -0,0 +1,13 @@ +export module t00072.app:lib2; + +export namespace clanguml::t00072 { +class C { }; + +template class CC { + T t; +}; + +namespace detail { +enum class CCC { ccc1, ccc2 }; +} +} \ No newline at end of file diff --git a/tests/t00072/src/t00072_mod.cppm b/tests/t00072/src/t00072_mod.cppm new file mode 100644 index 00000000..ad905d03 --- /dev/null +++ b/tests/t00072/src/t00072_mod.cppm @@ -0,0 +1,13 @@ +export module t00072.app; +export import :lib1; +export import :lib1.mod1; +export import :lib1.mod2; +export import :lib2; + +export namespace clanguml::t00072 { +class A { + int get() { return a; } + + int a; +}; +} \ No newline at end of file diff --git a/tests/t00072/t00072.cc b/tests/t00072/t00072.cc new file mode 100644 index 00000000..d7dc2f1f --- /dev/null +++ b/tests/t00072/t00072.cc @@ -0,0 +1,6 @@ +import t00072.app; + +namespace clanguml { +namespace t00072 { +} +} \ No newline at end of file diff --git a/tests/t00072/test_case.h b/tests/t00072/test_case.h new file mode 100644 index 00000000..e67071fc --- /dev/null +++ b/tests/t00072/test_case.h @@ -0,0 +1,102 @@ +/** + * tests/t00072/test_case.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t00072", "[test-case][class]") +{ + auto [config, db] = load_config("t00072"); + + auto diagram = config.diagrams["t00072_class"]; + + REQUIRE(diagram->name == "t00072_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00072_class"); + + { + auto src = generate_class_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + // Check if all classes exist + REQUIRE_THAT(src, IsPackage("app")); + REQUIRE_THAT(src, IsPackage(":lib1")); + REQUIRE_THAT(src, IsPackage(":lib2")); + REQUIRE_THAT(src, IsPackage("mod1")); + REQUIRE_THAT(src, IsPackage("mod2")); + + REQUIRE_THAT(src, IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("C"))); + REQUIRE_THAT(src, IsClassTemplate("CC", "T")); + REQUIRE_THAT(src, IsEnum(_A("detail::CCC"))); + + REQUIRE_THAT(src, IsClass(_A("B"))); + REQUIRE_THAT(src, IsClassTemplate("BB", "T")); + REQUIRE_THAT(src, IsEnum(_A("detail::BBB"))); + + REQUIRE_THAT(src, IsClass(_A("D"))); + REQUIRE_THAT(src, IsClass(_A("E"))); + + save_puml(config.output_directory(), diagram->name + ".puml", src); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + REQUIRE(IsClass(j, "clanguml::t00072::A")); + REQUIRE(IsClass(j, "clanguml::t00072::B")); + REQUIRE(IsClass(j, "clanguml::t00072::C")); + REQUIRE(IsClass(j, "clanguml::t00072::D")); + REQUIRE(IsEnum(j, "clanguml::t00072::detail::CCC")); + REQUIRE(IsEnum(j, "clanguml::t00072::detail::BBB")); + + REQUIRE(IsPackage(j, "app", "module")); + REQUIRE(IsPackage(j, "app:lib1", "module")); + REQUIRE(IsPackage(j, "app:lib2", "module")); + REQUIRE(IsPackage(j, "app:lib1.mod1", "module")); + REQUIRE(IsPackage(j, "app:lib1.mod2", "module")); + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto src = generate_class_mermaid(diagram, *model); + + mermaid::AliasMatcher _A(src); + using mermaid::IsClass; + using mermaid::IsEnum; + + REQUIRE_THAT(src, IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("C"))); + REQUIRE_THAT(src, IsClass(_A("CC"))); + REQUIRE_THAT(src, IsEnum(_A("detail::CCC"))); + + REQUIRE_THAT(src, IsClass(_A("B"))); + REQUIRE_THAT(src, IsClass(_A("BB"))); + REQUIRE_THAT(src, IsEnum(_A("detail::BBB"))); + + REQUIRE_THAT(src, IsClass(_A("D"))); + REQUIRE_THAT(src, IsClass(_A("E"))); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } +} \ No newline at end of file diff --git a/tests/t30012/test_case.h b/tests/t30012/test_case.h index d5e51c03..a572a28c 100644 --- a/tests/t30012/test_case.h +++ b/tests/t30012/test_case.h @@ -50,11 +50,11 @@ TEST_CASE("t30012", "[test-case][package]") using namespace json; - REQUIRE(IsPackage(j, "app", "module")); - REQUIRE(IsPackage(j, "app.lib1", "module")); - REQUIRE(IsPackage(j, "app.lib2", "module")); - REQUIRE(IsPackage(j, "app.lib1.mod1", "module")); - REQUIRE(IsPackage(j, "app.lib1.mod2", "module")); + REQUIRE(IsPackage(j, "t30012.app", "module")); + REQUIRE(IsPackage(j, "t30012.app.lib1", "module")); + REQUIRE(IsPackage(j, "t30012.app.lib2", "module")); + REQUIRE(IsPackage(j, "t30012.app.lib1.mod1", "module")); + REQUIRE(IsPackage(j, "t30012.app.lib1.mod2", "module")); save_json(config.output_directory(), diagram->name + ".json", j); } diff --git a/tests/t30013/test_case.h b/tests/t30013/test_case.h index c87b21cd..75067652 100644 --- a/tests/t30013/test_case.h +++ b/tests/t30013/test_case.h @@ -83,25 +83,25 @@ TEST_CASE("t30013", "[test-case][package]") using namespace json; - REQUIRE(IsPackage(j, "app", "module")); - REQUIRE(IsPackage(j, "mod1", "module")); - REQUIRE(IsPackage(j, "mod2", "module")); - REQUIRE(IsPackage(j, "mod3", "module")); - REQUIRE(IsPackage(j, "mod4", "module")); - REQUIRE(IsPackage(j, "mod5", "module")); - REQUIRE(IsPackage(j, "mod6", "module")); - REQUIRE(IsPackage(j, "mod7", "module")); - REQUIRE(IsPackage(j, "mod8", "module")); - REQUIRE(IsPackage(j, "mod9", "module")); - REQUIRE(IsPackage(j, "mod10", "module")); - REQUIRE(IsPackage(j, "mod11", "module")); - REQUIRE(IsPackage(j, "mod12", "module")); - REQUIRE(IsPackage(j, "mod13", "module")); - REQUIRE(IsPackage(j, "mod14", "module")); - REQUIRE(IsPackage(j, "mod15", "module")); - REQUIRE(IsPackage(j, "mod16", "module")); - REQUIRE(IsPackage(j, "mod17", "module")); - REQUIRE(IsPackage(j, "mod18", "module")); + REQUIRE(IsPackage(j, "t30013.app", "module")); + REQUIRE(IsPackage(j, "t30013.mod1", "module")); + REQUIRE(IsPackage(j, "t30013.mod2", "module")); + REQUIRE(IsPackage(j, "t30013.mod3", "module")); + REQUIRE(IsPackage(j, "t30013.mod4", "module")); + REQUIRE(IsPackage(j, "t30013.mod5", "module")); + REQUIRE(IsPackage(j, "t30013.mod6", "module")); + REQUIRE(IsPackage(j, "t30013.mod7", "module")); + REQUIRE(IsPackage(j, "t30013.mod8", "module")); + REQUIRE(IsPackage(j, "t30013.mod9", "module")); + REQUIRE(IsPackage(j, "t30013.mod10", "module")); + REQUIRE(IsPackage(j, "t30013.mod11", "module")); + REQUIRE(IsPackage(j, "t30013.mod12", "module")); + REQUIRE(IsPackage(j, "t30013.mod13", "module")); + REQUIRE(IsPackage(j, "t30013.mod14", "module")); + REQUIRE(IsPackage(j, "t30013.mod15", "module")); + REQUIRE(IsPackage(j, "t30013.mod16", "module")); + REQUIRE(IsPackage(j, "t30013.mod17", "module")); + REQUIRE(IsPackage(j, "t30013.mod18", "module")); save_json(config.output_directory(), diagram->name + ".json", j); } diff --git a/tests/t30014/test_case.h b/tests/t30014/test_case.h index 898ca2ba..10e90bb0 100644 --- a/tests/t30014/test_case.h +++ b/tests/t30014/test_case.h @@ -50,11 +50,11 @@ TEST_CASE("t30014", "[test-case][package]") using namespace json; - REQUIRE(IsPackage(j, "app", "module")); - REQUIRE(IsPackage(j, "app:lib1", "module")); - REQUIRE(IsPackage(j, "app:lib2", "module")); - REQUIRE(IsPackage(j, "app:lib1.mod1", "module")); - REQUIRE(!IsPackage(j, "app:lib1.mod2", "module")); + REQUIRE(IsPackage(j, "t30014.app", "module")); + REQUIRE(IsPackage(j, "t30014.app:lib1", "module")); + REQUIRE(IsPackage(j, "t30014.app:lib2", "module")); + REQUIRE(IsPackage(j, "t30014.app:lib1.mod1", "module")); + REQUIRE(!IsPackage(j, "t30014.app:lib1.mod2", "module")); save_json(config.output_directory(), diagram->name + ".json", j); } diff --git a/tests/t30015/test_case.h b/tests/t30015/test_case.h index 8aab68fb..3b40ef67 100644 --- a/tests/t30015/test_case.h +++ b/tests/t30015/test_case.h @@ -84,26 +84,26 @@ TEST_CASE("t30015", "[test-case][package]") using namespace json; - REQUIRE(IsPackage(j, "app", "module")); - REQUIRE(IsPackage(j, "lib1", "module")); - REQUIRE(IsPackage(j, "lib1:mod1", "module")); - REQUIRE(IsPackage(j, "lib1:mod2", "module")); - REQUIRE(IsPackage(j, "lib1:mod3", "module")); - REQUIRE(IsPackage(j, "lib1:mod4", "module")); - REQUIRE(IsPackage(j, "lib1:mod5", "module")); - REQUIRE(IsPackage(j, "lib1:mod6", "module")); - REQUIRE(IsPackage(j, "lib1:mod7", "module")); - REQUIRE(IsPackage(j, "lib1:mod8", "module")); - REQUIRE(IsPackage(j, "lib1:mod9", "module")); - REQUIRE(IsPackage(j, "lib1:mod10", "module")); - REQUIRE(IsPackage(j, "lib1:mod11", "module")); - REQUIRE(IsPackage(j, "lib1:mod12", "module")); - REQUIRE(IsPackage(j, "lib1:mod13", "module")); - REQUIRE(IsPackage(j, "lib1:mod14", "module")); - REQUIRE(IsPackage(j, "lib1:mod15", "module")); - REQUIRE(IsPackage(j, "lib1:mod16", "module")); - REQUIRE(IsPackage(j, "lib1:mod17", "module")); - REQUIRE(IsPackage(j, "lib1:mod18", "module")); + REQUIRE(IsPackage(j, "t30015.app", "module")); + REQUIRE(IsPackage(j, "t30015.lib1", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod1", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod2", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod3", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod4", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod5", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod6", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod7", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod8", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod9", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod10", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod11", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod12", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod13", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod14", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod15", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod16", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod17", "module")); + REQUIRE(IsPackage(j, "t30015.lib1:mod18", "module")); save_json(config.output_directory(), diagram->name + ".json", j); } diff --git a/tests/test_cases.cc b/tests/test_cases.cc index c53a9840..e67d3964 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -410,6 +410,7 @@ using namespace clanguml::test::matchers; #if defined(ENABLE_CXX_MODULES_TEST_CASES) #include "t00070/test_case.h" #include "t00071/test_case.h" +#include "t00072/test_case.h" #endif /// diff --git a/tests/test_cases.h b/tests/test_cases.h index ede7b074..4b82c499 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -1211,7 +1211,7 @@ std::optional get_element( return {e}; if (e["type"] == "namespace" || e["type"] == "folder" || - e["type"] == "module") { + e["type"] == "directory" || e["type"] == "module") { auto maybe_e = get_element(e, name); if (maybe_e) return maybe_e; @@ -1275,16 +1275,18 @@ std::string expand_name(const nlohmann::json &j, const std::string &name) } if (j["package_type"] == to_string(package_type_t::kModule)) { - if (!j.contains("using_module")) - return name; - - auto full_path = - path{j["using_module"].get(), path_type::kModule}; - full_path |= path{name, path_type::kModule}; - - auto res = full_path.to_string(); - - return res; + return name; + // if (!j.contains("using_module")) + // return name; + // + // auto full_path = + // path{j["using_module"].get(), + // path_type::kModule}; + // full_path |= path{name, path_type::kModule}; + // + // auto res = full_path.to_string(); + // + // return res; } return name; diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 3f208020..544070c7 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -210,6 +210,9 @@ test_cases: - name: t00071 title: Class diagram with C++20 modules generated as packages description: + - name: t00072 + title: Class diagram with C++20 module partitions generated as packages + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case