diff --git a/src/class_diagram/model/enum.cc b/src/class_diagram/model/enum.cc index 2343559f..41bbdb77 100644 --- a/src/class_diagram/model/enum.cc +++ b/src/class_diagram/model/enum.cc @@ -41,9 +41,11 @@ std::string enum_::full_name(bool relative) const std::ostringstream ostr; if (relative) - ostr << namespace_{name()}.relative_to(using_namespace()).to_string(); + ostr << namespace_{name_and_ns()} + .relative_to(using_namespace()) + .to_string(); else - ostr << name(); + ostr << name_and_ns(); return ostr.str(); } diff --git a/src/class_diagram/visitor/translation_unit_context.cc b/src/class_diagram/visitor/translation_unit_context.cc index 3e200753..0031d010 100644 --- a/src/class_diagram/visitor/translation_unit_context.cc +++ b/src/class_diagram/visitor/translation_unit_context.cc @@ -209,4 +209,59 @@ translation_unit_context::using_namespace_directive( return using_ns_declarations_.at(ns.to_string()); } +type_safe::optional +translation_unit_context::get_name_with_namespace(const std::string &name) const +{ + using common::model::namespace_; + + std::set possible_matches; + possible_matches.emplace(name); + + possible_matches.emplace(get_namespace() | namespace_{name}); + auto parent = get_namespace().parent(); + while (parent.has_value()) { + possible_matches.emplace(parent.value() | namespace_{name}); + parent = parent.value().parent(); + } + + if (using_ns_declarations_.find(get_namespace().to_string()) != + using_ns_declarations_.end()) { + for (const auto &ns : + using_ns_declarations_.at(get_namespace().to_string())) { + possible_matches.emplace(ns | namespace_{name}); + auto parent = ns.parent(); + while (parent.has_value()) { + possible_matches.emplace(parent.value() | namespace_{name}); + parent = parent.value().parent(); + } + } + } + + // Search classes + for (const auto &c : diagram_.classes()) { + auto c_ns = namespace_{c->name_and_ns()}; + for (const auto &possible_match : possible_matches) { + if (c_ns == possible_match) { + return possible_match; + } + } + } + + // Search enums + for (const auto &e : diagram_.enums()) { + auto e_ns = namespace_{e->name_and_ns()}; + for (const auto &possible_match : possible_matches) { + if (e_ns == possible_match) { + return possible_match; + } + // Try to also match possible references to enum values + else if (possible_match.starts_with(e_ns)) { + return possible_match; + } + } + } + + return {}; +} + } diff --git a/src/class_diagram/visitor/translation_unit_context.h b/src/class_diagram/visitor/translation_unit_context.h index 188eb1cf..714f7eae 100644 --- a/src/class_diagram/visitor/translation_unit_context.h +++ b/src/class_diagram/visitor/translation_unit_context.h @@ -83,6 +83,9 @@ public: const std::set &using_namespace_directive( const common::model::namespace_ &ns) const; + type_safe::optional get_name_with_namespace( + const std::string &name) const; + private: // Current visitor namespace common::model::namespace_ ns_; @@ -90,6 +93,8 @@ private: // A map of 'using namespace' declared within a given namespace scope // This is necessary to properly establish the namespace of a given entity // for instance in unexposed template parameters + // - key - namespace + // - value - set of namespaces 'imported' within this namespace scope std::map> using_ns_declarations_; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 10394118..03debefb 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -179,14 +179,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) process_type_alias_template(at); } else if (e.kind() == cppast::cpp_entity_kind::using_directive_t) { + using common::model::namespace_; const auto &using_directive = static_cast(e); const auto ns_ref = using_directive.target(); + const auto &ns = ns_ref.get(ctx.entity_index()).at(0).get(); if (ns_ref.get(ctx.entity_index()).size() > 0) { - auto full_ns = cx::util::full_name(ctx.get_namespace(), - ns_ref.get(ctx.entity_index()).at(0).get()); + auto full_ns = namespace_{cx::util::ns(ns)} | ns.name(); ctx.add_using_namespace_directive(full_ns); } @@ -503,10 +504,16 @@ void translation_unit_visitor:: { auto ua = tspec.value().unexposed_arguments().as_string(); - auto template_params = cx::util::parse_unexposed_template_params(ua); + auto template_params = cx::util::parse_unexposed_template_params( + ua, [this](const std::string &t) { + auto full_type = ctx.get_name_with_namespace(t); + if (full_type.has_value()) + return full_type.value().to_string(); + return t; + }); found_relationships_t relationships; - for (const auto ¶m : template_params) { + for (auto ¶m : template_params) { find_relationships_in_unexposed_template_params(param, relationships); c.add_template(param); } @@ -1414,31 +1421,17 @@ bool translation_unit_visitor::find_relationships_in_unexposed_template_params( LOG_DBG("Finding relationships in user defined type: {}", ct.to_string(ctx.config().using_namespace())); - if (!util::contains(ct.type(), "::")) { - // The type name has no namespace - assume it is in the current - // namespace - // TODO: try also all namespaces declared through 'using namespace' - // directive in the current scope - if (ctx.config().should_include(ctx.get_namespace(), ct.type())) { - relationships.emplace_back(ct.type(), relationship_t::kDependency); - found = true; - } - } - else { - // Calculate the complete namespace of the type - auto type_ns = common::model::namespace_{ct.type()}; - auto type_name = type_ns.name(); - type_ns.pop_back(); + auto type_with_namespace = ctx.get_name_with_namespace(ct.type()); - auto current_ns = ctx.get_namespace(); - if (current_ns.ends_with(type_ns)) { - if (ctx.config().should_include(current_ns, type_name)) { - auto full_name = (current_ns | type_name).to_string(); - relationships.emplace_back( - full_name, relationship_t::kDependency); - found = true; - } - } + if (!type_with_namespace.has_value()) { + // Couldn't find declaration of this type + type_with_namespace = common::model::namespace_{ct.type()}; + } + + if (ctx.config().should_include(type_with_namespace.value())) { + relationships.emplace_back(type_with_namespace.value().to_string(), + relationship_t::kDependency); + found = true; } for (const auto &nested_template_params : ct.template_params_) { diff --git a/src/common/model/namespace.cc b/src/common/model/namespace.cc index 37f42a5f..ed25e50a 100644 --- a/src/common/model/namespace.cc +++ b/src/common/model/namespace.cc @@ -99,6 +99,17 @@ void namespace_::append(const namespace_ &ns) void namespace_::pop_back() { namespace_path_.pop_back(); } +type_safe::optional namespace_::parent() const +{ + if (size() <= 1) { + return {}; + } + + namespace_ res{*this}; + res.pop_back(); + return {std::move(res)}; +} + namespace_ namespace_::operator|(const namespace_ &right) const { namespace_ res{*this}; diff --git a/src/common/model/namespace.h b/src/common/model/namespace.h index e8c19c82..03d42bc9 100644 --- a/src/common/model/namespace.h +++ b/src/common/model/namespace.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include namespace clanguml::common::model { @@ -68,6 +69,8 @@ public: void pop_back(); + type_safe::optional parent() const; + bool starts_with(const namespace_ &right) const; bool ends_with(const namespace_ &right) const; namespace_ common_path(const namespace_ &right) const; diff --git a/src/cx/util.cc b/src/cx/util.cc index d462dc11..157eee80 100644 --- a/src/cx/util.cc +++ b/src/cx/util.cc @@ -297,7 +297,8 @@ const cppast::cpp_type &unreferenced(const cppast::cpp_type &t) } std::vector -parse_unexposed_template_params(const std::string ¶ms) +parse_unexposed_template_params(const std::string ¶ms, + std::function ns_resolve) { using class_diagram::model::class_template; @@ -331,7 +332,8 @@ parse_unexposed_template_params(const std::string ¶ms) } std::string nested_params_str( bracket_match_begin, bracket_match_end); - nested_params = parse_unexposed_template_params(nested_params_str); + nested_params = + parse_unexposed_template_params(nested_params_str, ns_resolve); if (nested_params.empty()) nested_params.emplace_back(class_template{nested_params_str}); it = bracket_match_end - 1; @@ -347,7 +349,7 @@ parse_unexposed_template_params(const std::string ¶ms) } if (complete_class_template) { class_template t; - t.set_type(clanguml::util::trim(type)); + t.set_type(ns_resolve(clanguml::util::trim(type))); type = ""; t.template_params_ = std::move(nested_params); @@ -359,7 +361,7 @@ parse_unexposed_template_params(const std::string ¶ms) if (!type.empty()) { class_template t; - t.set_type(clanguml::util::trim(type)); + t.set_type(ns_resolve(clanguml::util::trim(type))); type = ""; t.template_params_ = std::move(nested_params); diff --git a/src/cx/util.h b/src/cx/util.h index f32122d6..65118171 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -69,7 +69,8 @@ std::pair split_ns( bool is_inside_class(const cppast::cpp_entity &e); std::vector -parse_unexposed_template_params(const std::string ¶ms); +parse_unexposed_template_params(const std::string ¶ms, + std::function ns_resolve); } // namespace util } // namespace cx diff --git a/tests/t00038/.clang-uml b/tests/t00038/.clang-uml index 29318d35..a0f321ac 100644 --- a/tests/t00038/.clang-uml +++ b/tests/t00038/.clang-uml @@ -3,11 +3,12 @@ output_directory: puml diagrams: t00038_class: type: class - generate_packages: true + generate_packages: false glob: - ../../tests/t00038/t00038.cc using_namespace: - clanguml::t00038 include: namespaces: - - clanguml::t00038 \ No newline at end of file + - clanguml::t00038 + - thirdparty::ns1 \ No newline at end of file diff --git a/tests/t00038/t00038.cc b/tests/t00038/t00038.cc index 63dec538..e33b091e 100644 --- a/tests/t00038/t00038.cc +++ b/tests/t00038/t00038.cc @@ -3,6 +3,19 @@ #include #include +namespace thirdparty { +namespace ns1 { +enum class color_t { red, green, blue }; + +struct E { +}; +} // namespace ns1 +namespace ns2 { +struct F { +}; +} // namespace ns2 +} // namespace thirdparty + namespace clanguml { namespace t00038 { @@ -21,6 +34,11 @@ struct key_t { template struct map; +using namespace thirdparty::ns1; + +template <> struct map> : E { +}; + template <> struct map> : A { diff --git a/tests/t00038/test_case.h b/tests/t00038/test_case.h index 102f4982..512f1a39 100644 --- a/tests/t00038/test_case.h +++ b/tests/t00038/test_case.h @@ -23,7 +23,7 @@ TEST_CASE("t00038", "[test-case][class]") auto diagram = config.diagrams["t00038_class"]; REQUIRE(diagram->name == "t00038_class"); - REQUIRE(diagram->generate_packages() == true); + REQUIRE(diagram->generate_packages() == false); auto model = generate_class_diagram(db, diagram); @@ -38,6 +38,7 @@ TEST_CASE("t00038", "[test-case][class]") REQUIRE_THAT(puml, IsClass(_A("A"))); REQUIRE_THAT(puml, IsClass(_A("B"))); REQUIRE_THAT(puml, IsClass(_A("C"))); + REQUIRE_THAT(puml, IsClass(_A("thirdparty::ns1::E"))); REQUIRE_THAT(puml, IsClass(_A("key_t"))); REQUIRE_THAT(puml, IsClassTemplate("map", "T")); REQUIRE_THAT(puml, @@ -45,8 +46,7 @@ TEST_CASE("t00038", "[test-case][class]") "std::integral_constant")); REQUIRE_THAT(puml, IsClassTemplate("map", - "std::vector>")); REQUIRE_THAT(puml, IsClassTemplate("map", @@ -70,9 +70,8 @@ TEST_CASE("t00038", "[test-case][class]") REQUIRE_THAT(puml, IsDependency(_A("map<" - "std::vector>>"), + "std::vector>>"), _A("property_t"))); REQUIRE_THAT(puml, @@ -85,6 +84,11 @@ TEST_CASE("t00038", "[test-case][class]") "property_t,property_t::property_c>>>>"), _A("key_t"))); + REQUIRE_THAT(puml, + IsDependency(_A("map>"), + _A("thirdparty::ns1::color_t"))); + save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); } diff --git a/tests/test_cases.h b/tests/test_cases.h index b20dbd8b..7d6e20fd 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -31,6 +31,7 @@ #include "util/util.h" #define CATCH_CONFIG_RUNNER +#define CATCH_CONFIG_CONSOLE_WIDTH 512 #include "catch.h" diff --git a/tests/test_util.cc b/tests/test_util.cc index 1df6d573..6e9be6fd 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -75,7 +75,8 @@ TEST_CASE("Test parse_unexposed_template_params", "[unit-test]") const std::string int_template_str{"ns1::ns2::class1"}; - auto int_template = parse_unexposed_template_params(int_template_str); + auto int_template = parse_unexposed_template_params( + int_template_str, [](const auto &n) { return n; }); CHECK(int_template.size() == 1); CHECK(int_template[0].template_params_.size() == 1); @@ -84,8 +85,8 @@ TEST_CASE("Test parse_unexposed_template_params", "[unit-test]") const std::string int_int_template_str{"ns1::ns2::class1"}; - auto int_int_template = - parse_unexposed_template_params(int_int_template_str); + auto int_int_template = parse_unexposed_template_params( + int_int_template_str, [](const auto &n) { return n; }); CHECK(int_int_template.size() == 1); CHECK(int_int_template[0].template_params_.size() == 2); @@ -96,7 +97,8 @@ TEST_CASE("Test parse_unexposed_template_params", "[unit-test]") const std::string nested_template_str{ "class1>>"}; - auto nested_template = parse_unexposed_template_params(nested_template_str); + auto nested_template = parse_unexposed_template_params( + nested_template_str, [](const auto &n) { return n; }); CHECK(nested_template.size() == 1); CHECK(nested_template[0].template_params_.size() == 2);