diff --git a/src/cx/util.cc b/src/cx/util.cc index c9b7d025..721acc09 100644 --- a/src/cx/util.cc +++ b/src/cx/util.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -92,6 +93,27 @@ std::string ns(const cppast::cpp_entity &e) return fmt::format("{}", fmt::join(res, "::")); } +type_safe::optional_ref entity_ns( + const cppast::cpp_entity &e) +{ + std::vector res{}; + + if (e.kind() == cppast::cpp_entity_kind::namespace_t) + return type_safe::optional_ref( + static_cast(e)); + + auto it = e.parent(); + while (it) { + if (it.value().kind() == cppast::cpp_entity_kind::namespace_t) { + return type_safe::optional_ref( + static_cast(it.value())); + } + it = it.value().parent(); + } + + return {}; +} + bool is_inside_class(const cppast::cpp_entity &e) { auto it = e.parent(); diff --git a/src/cx/util.h b/src/cx/util.h index b7aa7401..9a0392e6 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -53,6 +53,9 @@ const cppast::cpp_type &unreferenced(const cppast::cpp_type &t); std::string ns(const cppast::cpp_entity &e); +type_safe::optional_ref entity_ns( + const cppast::cpp_entity &e); + std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); bool is_inside_class(const cppast::cpp_entity &e); diff --git a/src/package_diagram/visitor/translation_unit_context.cc b/src/package_diagram/visitor/translation_unit_context.cc index 519e8a58..c95355c2 100644 --- a/src/package_diagram/visitor/translation_unit_context.cc +++ b/src/package_diagram/visitor/translation_unit_context.cc @@ -32,6 +32,52 @@ translation_unit_context::translation_unit_context( { } +bool translation_unit_context::has_namespace_alias( + const std::string &full_name) const +{ + bool res = + namespace_alias_index_.find(full_name) != namespace_alias_index_.end(); + + LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not"); + + return res; +} + +void translation_unit_context::add_namespace_alias(const std::string &full_name, + type_safe::object_ref ref) +{ + if (!has_namespace_alias(full_name)) { + LOG_DBG("Stored type alias: {} -> {} ", full_name, ref.get().name()); + + namespace_alias_index_.emplace(full_name, std::move(ref)); + } +} + +type_safe::object_ref +translation_unit_context::get_namespace_alias( + const std::string &full_name) const +{ + assert(has_namespace_alias(full_name)); + + return namespace_alias_index_.at(full_name); +} + +type_safe::object_ref +translation_unit_context::get_namespace_alias_final( + const cppast::cpp_namespace &ns) const +{ + auto ns_full_name = cx::util::full_name({}, ns); + + ns_full_name = cx::util::ns(ns) + "::" + ns_full_name; + + if (has_namespace_alias(ns_full_name)) { + return get_namespace_alias_final( + namespace_alias_index_.at(ns_full_name).get()); + } + + return type_safe::ref(ns); +} + bool translation_unit_context::has_type_alias( const std::string &full_name) const { diff --git a/src/package_diagram/visitor/translation_unit_context.h b/src/package_diagram/visitor/translation_unit_context.h index be1a80d4..10c71a4f 100644 --- a/src/package_diagram/visitor/translation_unit_context.h +++ b/src/package_diagram/visitor/translation_unit_context.h @@ -21,6 +21,7 @@ #include "package_diagram/model/diagram.h" #include +#include #include #include @@ -32,6 +33,17 @@ public: clanguml::package_diagram::model::diagram &diagram, const clanguml::config::package_diagram &config); + bool has_namespace_alias(const std::string &full_name) const; + + void add_namespace_alias(const std::string &full_name, + type_safe::object_ref ref); + + type_safe::object_ref get_namespace_alias( + const std::string &full_name) const; + + type_safe::object_ref + get_namespace_alias_final(const cppast::cpp_namespace &t) const; + bool has_type_alias(const std::string &full_name) const; void add_type_alias(const std::string &full_name, @@ -80,7 +92,11 @@ private: // Reference to class diagram config const clanguml::config::package_diagram &config_; - // Map of discovered aliases (declared with 'using' keyword) + // Map of discovered aliases (declared with 'namespace' keyword) + std::map> + namespace_alias_index_; + + // Map of discovered type aliases (declared with 'using' keyword) std::map> alias_index_; diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index 7d33cd65..affc47a1 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -143,6 +143,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) ctx.pop_namespace(); } } + else if (e.kind() == cppast::cpp_entity_kind::namespace_alias_t) { + auto &na = static_cast(e); + + for (const auto &alias_target : + na.target().get(ctx.entity_index())) { + auto full_ns = cx::util::full_name(ctx.get_namespace(), na); + ctx.add_namespace_alias(full_ns, alias_target); + } + } else if (e.kind() == cppast::cpp_entity_kind::class_template_specialization_t) { LOG_DBG("========== Visiting '{}' - {}", @@ -207,8 +216,6 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta), type_safe::ref(ta.underlying_type())); - - // ctx.diagram().add_type_alias(std::move(t)); } else if (e.kind() == cppast::cpp_entity_kind::alias_template_t) { LOG_DBG("========== Visiting '{}' - {}", @@ -294,21 +301,15 @@ void translation_unit_visitor::process_class_declaration( for (auto &base : cls.bases()) { find_relationships( base.type(), relationships, relationship_t::kDependency); - - clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base); } for (const auto &dependency : relationships) { - auto type_ns = util::split(std::get<0>(dependency), "::"); - type_ns.pop_back(); + auto destination = util::split(std::get<0>(dependency), "::"); - relationship r{relationship_t::kDependency, - fmt::format("{}", fmt::join(type_ns, "::"))}; - - if (!starts_with(ctx.get_namespace(), type_ns) && - !starts_with(type_ns, ctx.get_namespace())) { - relationship r{relationship_t::kDependency, - fmt::format("{}", fmt::join(type_ns, "::"))}; + if (!starts_with(ctx.get_namespace(), destination) && + !starts_with(destination, ctx.get_namespace())) { + relationship r{ + relationship_t::kDependency, std::get<0>(dependency)}; current_package.value().add_relationship(std::move(r)); } } @@ -330,16 +331,12 @@ void translation_unit_visitor::process_function(const cppast::cpp_function &f) f.return_type(), relationships, relationship_t::kDependency); for (const auto &dependency : relationships) { - auto type_ns = util::split(std::get<0>(dependency), "::"); - type_ns.pop_back(); + auto destination = util::split(std::get<0>(dependency), "::"); - relationship r{relationship_t::kDependency, - fmt::format("{}", fmt::join(type_ns, "::"))}; - - if (!starts_with(ctx.get_namespace(), type_ns) && - !starts_with(type_ns, ctx.get_namespace())) { - relationship r{relationship_t::kDependency, - fmt::format("{}", fmt::join(type_ns, "::"))}; + if (!starts_with(ctx.get_namespace(), destination) && + !starts_with(destination, ctx.get_namespace())) { + relationship r{ + relationship_t::kDependency, std::get<0>(dependency)}; current_package.value().add_relationship(std::move(r)); } } @@ -352,8 +349,37 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, { bool found{false}; - const auto fn = - cx::util::full_name(cppast::remove_cv(t_), ctx.entity_index(), false); + const auto fn = cx::util::full_name( + resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false); + auto t_ns = util::split(fn, "::"); + t_ns.pop_back(); + + const auto &t_raw = resolve_alias(cppast::remove_cv(t_)); + + if (t_raw.kind() == cppast::cpp_type_kind::user_defined_t) { + + auto t_raw_ns = cx::util::ns(t_raw, ctx.entity_index()); + + const auto &type_entities = + static_cast(t_raw) + .entity() + .get(ctx.entity_index()); + if (type_entities.size() > 0) { + const auto &type_entity = type_entities[0]; + + const auto &t_raw_ns = cx::util::entity_ns(type_entity.get()); + + const auto &t_raw_ns_final = cx::util::ns(t_raw_ns.value()) + + "::" + cx::util::full_name({}, t_raw_ns.value()); + t_ns = util::split(t_raw_ns_final, "::"); + } + } + + std::vector possible_matches; + + possible_matches.push_back(util::join(t_ns, "::")); + + const auto fn_ns = cx::util::ns(cppast::remove_cv(t_), ctx.entity_index()); LOG_DBG("Finding relationships for type {}, {}, {}", cppast::to_string(t_), t_.kind(), fn); @@ -391,21 +417,19 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, LOG_DBG("User defined type: {} | {}", cppast::to_string(t_), cppast::to_string(t_.canonical())); - if (relationship_type != relationship_t::kNone) - relationships.emplace_back(cppast::to_string(t), relationship_type); - else - relationships.emplace_back( - cppast::to_string(t), relationship_t::kDependency); - // Check if t_ has an alias in the alias index if (ctx.has_type_alias(fn)) { - LOG_DBG("Find relationship in alias of {} | {}", fn, + LOG_DBG("Found relationship in alias of {} | {}", fn, cppast::to_string(ctx.get_type_alias(fn).get())); found = find_relationships( ctx.get_type_alias(fn).get(), relationships, relationship_type); if (found) return found; } + + for (const auto &pm : possible_matches) { + relationships.emplace_back(pm, relationship_t::kDependency); + } } else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { auto &tinst = diff --git a/src/util/util.cc b/src/util/util.cc index df47abfe..b84ec33c 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -64,6 +64,11 @@ std::vector split(std::string str, std::string delimiter) return result; } +std::string join(const std::vector &toks, std::string delimiter) +{ + return fmt::format("{}", fmt::join(toks, delimiter)); +} + std::string ns_relative( const std::vector &namespaces, const std::string &n) { diff --git a/src/util/util.h b/src/util/util.h index f76cedfd..e00396f3 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -64,6 +64,8 @@ std::string trim(const std::string &s); */ std::vector split(std::string str, std::string delimiter); +std::string join(const std::vector &toks, std::string delimiter); + /** * @brief Get name of the identifier relative to a set of namespaces * diff --git a/tests/t30005/.clang-uml b/tests/t30005/.clang-uml new file mode 100644 index 00000000..1512c67f --- /dev/null +++ b/tests/t30005/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t30005_package: + type: package + glob: + - ../../tests/t30005/t30005.cc + include: + namespaces: + - clanguml::t30005 + using_namespace: + - clanguml::t30005 + plantuml: + before: + - "' t30005 test package diagram" \ No newline at end of file diff --git a/tests/t30005/t30005.cc b/tests/t30005/t30005.cc new file mode 100644 index 00000000..f87fad0a --- /dev/null +++ b/tests/t30005/t30005.cc @@ -0,0 +1,27 @@ +namespace clanguml { +namespace t30005 { + +namespace A::AA::AAA { +struct C1 { +}; +} + +namespace B::BB::BBB { +namespace A6 = A::AA::AAA; +namespace ASix = A6; +struct C2 { + ASix::C1 *cb; +}; +} + +namespace C::CC::CCC { +namespace A6 = A::AA::AAA; +namespace ASix = A6; +using ADSix = ASix::C1; +struct C2 { + ADSix *cc; +}; +} +} + +} diff --git a/tests/t30005/test_case.h b/tests/t30005/test_case.h new file mode 100644 index 00000000..d50234a2 --- /dev/null +++ b/tests/t30005/test_case.h @@ -0,0 +1,54 @@ +/** + * tests/t30005/test_case.cc + * + * Copyright (c) 2021-2022 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("t30005", "[test-case][package]") +{ + auto [config, db] = load_config("t30005"); + + auto diagram = config.diagrams["t30005_package"]; + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t30005"})); + + REQUIRE(diagram->should_include("clanguml::t30005::A")); + REQUIRE(diagram->should_include("clanguml::t30005::C")); + REQUIRE(!diagram->should_include("std::vector")); + + REQUIRE(diagram->name == "t30005_package"); + + auto model = generate_package_diagram(db, diagram); + + REQUIRE(model.name() == "t30005_package"); + + auto puml = generate_package_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, IsPackage("AAA")); + REQUIRE_THAT(puml, IsPackage("BBB")); + REQUIRE_THAT(puml, IsPackage("CCC")); + + REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("AAA"))); + REQUIRE_THAT(puml, IsDependency(_A("CCC"), _A("AAA"))); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index af03a6a7..652a6af4 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -180,6 +180,7 @@ using namespace clanguml::test::matchers; #include "t30002/test_case.h" #include "t30003/test_case.h" #include "t30004/test_case.h" +#include "t30005/test_case.h" // // Other tests (e.g. configuration file)