diff --git a/src/class_diagram/generators/json/class_diagram_generator.cc b/src/class_diagram/generators/json/class_diagram_generator.cc index 66966d5e..9cfad127 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.cc +++ b/src/class_diagram/generators/json/class_diagram_generator.cc @@ -23,6 +23,14 @@ namespace clanguml::class_diagram::model { using nlohmann::json; +void set_module(nlohmann::json &j, const common::model::element &e) +{ + if (e.module()) { + j["module"]["name"] = e.module().value(); + j["module"]["is_private"] = e.module_private(); + } +} + void to_json(nlohmann::json &j, const class_element &c) { j["name"] = c.name(); @@ -94,6 +102,8 @@ void to_json(nlohmann::json &j, const class_ &c) j["methods"] = c.methods(); j["bases"] = c.parents(); + set_module(j, c); + j["template_parameters"] = c.template_params(); } @@ -102,6 +112,8 @@ void to_json(nlohmann::json &j, const enum_ &c) j = dynamic_cast(c); j["is_nested"] = c.is_nested(); j["constants"] = c.constants(); + + set_module(j, c); } void to_json(nlohmann::json &j, const concept_ &c) @@ -109,6 +121,8 @@ void to_json(nlohmann::json &j, const concept_ &c) j = dynamic_cast(c); j["parameters"] = c.requires_parameters(); j["statements"] = c.requires_statements(); + + set_module(j, c); } } // namespace clanguml::class_diagram::model diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index bd501051..2e9b81fe 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -568,6 +568,31 @@ tvl::value_t access_filter::match( [&a](const auto &access) { return a == access; }); } +module_access_filter::module_access_filter( + filter_t type, std::vector access) + : filter_visitor{type} + , access_{std::move(access)} +{ +} + +tvl::value_t module_access_filter::match( + const diagram & /*d*/, const element &e) const +{ + if (!e.module().has_value()) + return {}; + + if (access_.empty()) + return {}; + + return tvl::any_of( + access_.begin(), access_.end(), [&e](const auto &access) { + if (access == module_access_t::kPublic) + return !e.module_private(); + else + return e.module_private(); + }); +} + context_filter::context_filter( filter_t type, std::vector context) : filter_visitor{type} @@ -924,6 +949,9 @@ void diagram_filter::init_filters(const config::diagram &c) add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.include().modules)); + add_inclusive_filter(std::make_unique( + filter_t::kInclusive, c.include().module_access)); + add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.include().relationships)); @@ -1037,6 +1065,9 @@ void diagram_filter::init_filters(const config::diagram &c) add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().modules)); + add_exclusive_filter(std::make_unique( + filter_t::kExclusive, c.exclude().module_access)); + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.root_directory(), c.exclude().paths)); diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index 9344b27b..416411c6 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -457,6 +457,20 @@ private: std::vector access_; }; +/** + * Match diagram elements based on module access (public or private). + */ +struct module_access_filter : public filter_visitor { + module_access_filter(filter_t type, std::vector access); + + ~module_access_filter() override = default; + + tvl::value_t match(const diagram &d, const element &a) const override; + +private: + std::vector access_; +}; + /** * Match diagram elements which are in within a 'radius' distance relationship * to any of the elements specified in context. diff --git a/src/common/model/element.h b/src/common/model/element.h index d94ce76a..0707c246 100644 --- a/src/common/model/element.h +++ b/src/common/model/element.h @@ -101,6 +101,23 @@ public: */ std::optional module() const { return module_; } + /** + * Set whether the element is in a private module + * + * @param module C++20 module. + */ + void set_module_private(const bool module_private) + { + module_private_ = module_private; + } + + /** + * Check whether the element is in a private module. + * + * @return C++20 module. + */ + bool module_private() const { return module_private_; } + /** * Return elements full name. * @@ -135,5 +152,6 @@ private: namespace_ ns_; namespace_ using_namespace_; std::optional module_; + bool module_private_{false}; }; } // namespace clanguml::common::model diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index 84e352d0..8b553413 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -70,6 +70,19 @@ std::string to_string(access_t a) } } +std::string to_string(module_access_t a) +{ + switch (a) { + case module_access_t::kPublic: + return "public"; + case module_access_t::kPrivate: + return "private"; + default: + assert(false); + return ""; + } +} + std::string to_string(message_t r) { switch (r) { diff --git a/src/common/model/enums.h b/src/common/model/enums.h index e0810c1d..03d5236e 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -23,6 +23,7 @@ namespace clanguml::common::model { enum class diagram_t { kClass, kSequence, kPackage, kInclude }; +enum class module_access_t { kPublic, kPrivate }; enum class access_t { kPublic, kProtected, kPrivate, kNone }; enum class relationship_t { @@ -77,6 +78,8 @@ std::string to_string(relationship_t r); std::string to_string(access_t r); +std::string to_string(module_access_t r); + std::string to_string(message_t m); std::string to_string(diagram_t r); diff --git a/src/common/visitor/translation_unit_visitor.cc b/src/common/visitor/translation_unit_visitor.cc index f6741758..8c4212e7 100644 --- a/src/common/visitor/translation_unit_visitor.cc +++ b/src/common/visitor/translation_unit_visitor.cc @@ -168,7 +168,13 @@ void translation_unit_visitor::set_owning_module( { if (const clang::Module *module = decl.getOwningModule(); module != nullptr) { - element.set_module(module->Name); + std::string module_name = module->Name; + if (module->isPrivateModule()) { + // Clang just maps private modules names to "" + module_name = module->getTopLevelModule()->Name; + } + element.set_module(module_name); + element.set_module_private(module->isPrivateModule()); } } } // namespace clanguml::common::visitor \ No newline at end of file diff --git a/src/config/config.h b/src/config/config.h index 8dc54064..22a1da17 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -194,6 +194,22 @@ struct filter { */ std::vector modules; + /*! @brief Access type filter + * + * This filter allows to filter class members methods based on their access: + * - public + * - private + * + * Example: + * + * ```yaml + * include: + * module_access: + * - public + * ``` + */ + std::vector module_access; + /*! @brief Elements filter * * Example: @@ -245,8 +261,8 @@ struct filter { * * ```yaml * include: - * relationships: - * - inheritance + * access: + * - public * ``` */ std::vector access; diff --git a/src/config/schema.h b/src/config/schema.h index bf8e2ea0..4f5b9df2 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -91,6 +91,9 @@ types: - public - protected - private + module_access_filter_t: !variant + - public + - private method_type_filter_t: !variant - constructor - destructor @@ -123,6 +126,7 @@ types: element_types: !optional [element_types_filter_t] relationships: !optional [relationship_filter_t] access: !optional [access_filter_t] + module_access: !optional [module_access_filter_t] subclasses: !optional [regex_or_string_t] parents: !optional [regex_or_string_t] specializations: !optional [regex_or_string_t] diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 82c7c0ef..0337832f 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -29,6 +29,7 @@ namespace YAML { using clanguml::common::namespace_or_regex; using clanguml::common::string_or_regex; using clanguml::common::model::access_t; +using clanguml::common::model::module_access_t; using clanguml::common::model::relationship_t; using clanguml::config::callee_type; using clanguml::config::class_diagram; @@ -241,6 +242,23 @@ template <> struct convert { } }; +// +// config module_access_t decoder +// +template <> struct convert { + static bool decode(const Node &node, module_access_t &rhs) + { + if (node.as() == "public") + rhs = module_access_t::kPublic; + else if (node.as() == "private") + rhs = module_access_t::kPrivate; + else + return false; + + return true; + } +}; + // // config method_type decoder // @@ -483,6 +501,10 @@ template <> struct convert { rhs.modules.push_back({ns}); } + if (node["module_access"]) + rhs.module_access = + node["module_access"].as(); + if (node["relationships"]) rhs.relationships = node["relationships"].as(); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index c850287b..ddf40344 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -69,6 +69,12 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const access_t &a) return out; } +YAML::Emitter &operator<<(YAML::Emitter &out, const module_access_t &a) +{ + out << to_string(a); + return out; +} + YAML::Emitter &operator<<(YAML::Emitter &out, const diagram_t &d) { out << to_string(d); @@ -124,6 +130,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f) out << YAML::Key << "namespaces" << YAML::Value << f.namespaces; if (!f.modules.empty()) out << YAML::Key << "modules" << YAML::Value << f.modules; + if (!f.module_access.empty()) + out << YAML::Key << "module_access" << YAML::Value << f.module_access; if (!f.access.empty()) out << YAML::Key << "access" << YAML::Value << f.access; if (!f.context.empty()) diff --git a/tests/t00070/.clang-uml b/tests/t00070/.clang-uml index 6fa20189..8a9971a6 100644 --- a/tests/t00070/.clang-uml +++ b/tests/t00070/.clang-uml @@ -9,4 +9,6 @@ diagrams: exclude: modules: - t00070.lib2 + module_access: + - private using_namespace: clanguml::t00070 \ No newline at end of file diff --git a/tests/t00070/src/lib1.cppm b/tests/t00070/src/lib1.cppm index 102c0aa7..4b0530bf 100644 --- a/tests/t00070/src/lib1.cppm +++ b/tests/t00070/src/lib1.cppm @@ -8,4 +8,9 @@ template class BB { }; enum class BBB { bbb1, bbb2 }; +} + +module :private; +namespace clanguml::t00070 { +class BBBB { }; } \ No newline at end of file diff --git a/tests/t00070/t00070.cc b/tests/t00070/t00070.cc index 8d1b6420..4035efce 100644 --- a/tests/t00070/t00070.cc +++ b/tests/t00070/t00070.cc @@ -1,3 +1,4 @@ +import t00070; import t00070.lib1; import t00070.lib2; diff --git a/tests/t00070/test_case.h b/tests/t00070/test_case.h index fff287a3..b7e74754 100644 --- a/tests/t00070/test_case.h +++ b/tests/t00070/test_case.h @@ -35,7 +35,7 @@ TEST_CASE("t00070", "[test-case][class]") REQUIRE_THAT(src, StartsWith("@startuml")); REQUIRE_THAT(src, EndsWith("@enduml\n")); - REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("A"))); REQUIRE_THAT(src, IsClass(_A("B"))); REQUIRE_THAT(src, !IsClass(_A("C"))); @@ -43,6 +43,7 @@ TEST_CASE("t00070", "[test-case][class]") REQUIRE_THAT(src, !IsClassTemplate("CC", "T")); REQUIRE_THAT(src, IsEnum(_A("BBB"))); + REQUIRE_THAT(src, !IsClass(_A("BBBB"))); REQUIRE_THAT(src, !IsEnum(_A("CCC"))); save_puml(config.output_directory(), diagram->name + ".puml", src); @@ -53,6 +54,15 @@ TEST_CASE("t00070", "[test-case][class]") using namespace json; + REQUIRE(IsClass(j, "A")); + REQUIRE(IsClass(j, "B")); + REQUIRE(!IsClass(j, "C")); + + REQUIRE(InPublicModule(j, "A", "t00070")); + REQUIRE(InPublicModule(j, "B", "t00070.lib1")); + + REQUIRE(!IsClass(j, "BBBB")); + save_json(config.output_directory(), diagram->name + ".json", j); } @@ -63,7 +73,7 @@ TEST_CASE("t00070", "[test-case][class]") using mermaid::IsClass; using mermaid::IsEnum; - REQUIRE_THAT(src, !IsClass(_A("A"))); + REQUIRE_THAT(src, IsClass(_A("A"))); REQUIRE_THAT(src, IsClass(_A("B"))); REQUIRE_THAT(src, !IsClass(_A("C"))); @@ -71,6 +81,7 @@ TEST_CASE("t00070", "[test-case][class]") REQUIRE_THAT(src, !IsClass(_A("CC"))); REQUIRE_THAT(src, IsEnum(_A("BBB"))); + REQUIRE_THAT(src, !IsClass(_A("BBBB"))); REQUIRE_THAT(src, !IsEnum(_A("CCC"))); save_mermaid(config.output_directory(), diagram->name + ".mmd", src); diff --git a/tests/test_cases.h b/tests/test_cases.h index bf4e9249..0c9860ee 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -1278,6 +1278,22 @@ bool IsClass(const nlohmann::json &j, const std::string &name) return e && e->at("type") == "class"; } +bool InPublicModule(const nlohmann::json &j, const std::string &element, + const std::string &module) +{ + auto e = get_element(j, expand_name(j, element)); + return e && e->contains("module") && e->at("module")["name"] == module && + !e->at("module")["is_private"]; +} + +bool InPrivateModule(const nlohmann::json &j, const std::string &element, + const std::string &module) +{ + auto e = get_element(j, expand_name(j, element)); + return e && e->contains("module") && e->at("module")["name"] == module && + e->at("module")["is_private"]; +} + bool IsAbstractClass(const nlohmann::json &j, const std::string &name) { auto e = get_element(j, expand_name(j, name));