diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index f8d59d4c..277f980d 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -485,13 +485,15 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, ctx.d.add_class(std::move(c)); } -void tu_visitor::process_field_with_template_instantiation( +bool tu_visitor::process_field_with_template_instantiation( const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr, class_ &c, cppast::cpp_access_specifier_kind as) { LOG_DBG("Processing field with template instatiation type {}", cppast::to_string(tr)); + bool res = false; + const auto &template_instantiation_type = static_cast(tr); if (template_instantiation_type.primary_template() @@ -549,17 +551,23 @@ void tu_visitor::process_field_with_template_instantiation( c.add_relationship(std::move(rr)); + res = true; + LOG_DBG("Created template instantiation: {}, {}", tinst.name, tinst.usr); ctx.d.add_class(std::move(tinst)); } } + + return res; } void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, cppast::cpp_access_specifier_kind as) { + bool template_instantiation_added_as_aggregation{false}; + class_member m; m.name = mv.name(); m.type = cppast::to_string(mv.type()); @@ -579,7 +587,9 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, cppast::to_string(tr), mv.name()); } else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) { - process_field_with_template_instantiation(mv, resolve_alias(tr), c, as); + template_instantiation_added_as_aggregation = + process_field_with_template_instantiation( + mv, resolve_alias(tr), c, as); } else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) { LOG_DBG( @@ -587,11 +597,12 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, // TODO } - if (tr.kind() != cppast::cpp_type_kind::builtin_t && - tr.kind() != cppast::cpp_type_kind::template_parameter_t) { + if (!template_instantiation_added_as_aggregation && + (tr.kind() != cppast::cpp_type_kind::builtin_t) && + (tr.kind() != cppast::cpp_type_kind::template_parameter_t)) { const auto &ttt = resolve_alias(mv.type()); std::vector> relationships; - find_relationships(ttt, relationships); + auto found = find_relationships(ttt, relationships); for (const auto &[type, relationship_type] : relationships) { if (relationship_type != relationship_t::kNone) { @@ -951,10 +962,12 @@ void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent) parent.add_relationship(std::move(r)); } -void tu_visitor::find_relationships(const cppast::cpp_type &t_, +bool tu_visitor::find_relationships(const cppast::cpp_type &t_, std::vector> &relationships, relationship_t relationship_hint) { + bool found{false}; + LOG_DBG("Finding relationships for type {}, {}", cppast::to_string(t_), t_.kind()); @@ -963,60 +976,19 @@ void tu_visitor::find_relationships(const cppast::cpp_type &t_, if (t.kind() == cppast::cpp_type_kind::array_t) { auto &a = static_cast(t); - find_relationships( + found = find_relationships( a.value_type(), relationships, relationship_t::kAggregation); - return; + return found; } auto name = cppast::to_string(t); - if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { - class_relationship r; - - auto &tinst = - static_cast(t); - - if (!tinst.arguments_exposed()) { - LOG_DBG("Template instantiation {} has no exposed arguments", name); - - return; - } - - const auto &args = tinst.arguments().value(); - - // Try to match common containers - // TODO: Refactor to a separate class with configurable - // container list - if (name.find("std::unique_ptr") == 0) { - find_relationships(args[0u].type().value(), relationships, - relationship_t::kAggregation); - } - else if (name.find("std::shared_ptr") == 0) { - find_relationships(args[0u].type().value(), relationships, - relationship_t::kAssociation); - } - else if (name.find("std::weak_ptr") == 0) { - find_relationships(args[0u].type().value(), relationships, - relationship_t::kAssociation); - } - else if (name.find("std::vector") == 0) { - find_relationships(args[0u].type().value(), relationships, - relationship_t::kAggregation); - } - else { - for (const auto &arg : args) { - if (arg.type()) - find_relationships( - arg.type().value(), relationships, relationship_type); - } - } - } - else if (t_.kind() == cppast::cpp_type_kind::pointer_t) { + if (t_.kind() == cppast::cpp_type_kind::pointer_t) { auto &p = static_cast(t_); auto rt = relationship_t::kAssociation; if (relationship_hint == relationship_t::kDependency) rt = relationship_hint; - find_relationships(p.pointee(), relationships, rt); + found = find_relationships(p.pointee(), relationships, rt); } else if (t_.kind() == cppast::cpp_type_kind::reference_t) { auto &r = static_cast(t_); @@ -1026,12 +998,12 @@ void tu_visitor::find_relationships(const cppast::cpp_type &t_, } if (relationship_hint == relationship_t::kDependency) rt = relationship_hint; - find_relationships(r.referee(), relationships, rt); + found = find_relationships(r.referee(), relationships, rt); } - else if (cppast::remove_cv(t_).kind() == - cppast::cpp_type_kind::user_defined_t) { + if (cppast::remove_cv(t_).kind() == cppast::cpp_type_kind::user_defined_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 @@ -1044,10 +1016,56 @@ void tu_visitor::find_relationships(const cppast::cpp_type &t_, if (ctx.has_type_alias(fn)) { LOG_DBG("Find relationship in alias of {} | {}", fn, cppast::to_string(ctx.get_type_alias(fn).get())); - find_relationships( + found = find_relationships( ctx.get_type_alias(fn).get(), relationships, relationship_type); + if (found) + return found; } } + else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { + class_relationship r; + + auto &tinst = + static_cast(t); + + if (!tinst.arguments_exposed()) { + LOG_DBG("Template instantiation {} has no exposed arguments", name); + + return found; + } + + const auto &args = tinst.arguments().value(); + + // Try to match common containers + // TODO: Refactor to a separate class with configurable + // container list + if (name.find("std::unique_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAggregation); + } + else if (name.find("std::shared_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAssociation); + } + else if (name.find("std::weak_ptr") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAssociation); + } + else if (name.find("std::vector") == 0) { + found = find_relationships(args[0u].type().value(), relationships, + relationship_t::kAggregation); + } + else { + for (const auto &arg : args) { + if (arg.type()) { + found = find_relationships( + arg.type().value(), relationships, relationship_type); + } + } + } + } + + return found; } class_ tu_visitor::build_template_instantiation( diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 0234f723..48f4ef37 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -163,7 +163,7 @@ public: clanguml::model::class_diagram::class_ &c, cppast::cpp_access_specifier_kind as); - void process_field_with_template_instantiation( + bool process_field_with_template_instantiation( const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr, clanguml::model::class_diagram::class_ &c, cppast::cpp_access_specifier_kind as); @@ -196,7 +196,7 @@ public: clanguml::model::class_diagram::class_method &m, clanguml::model::class_diagram::class_ &c); - void find_relationships(const cppast::cpp_type &t, + bool find_relationships(const cppast::cpp_type &t, std::vector> &relationships, clanguml::model::class_diagram::relationship_t relationship_hint = diff --git a/tests/t00025/.clanguml b/tests/t00025/.clanguml new file mode 100644 index 00000000..4a32003c --- /dev/null +++ b/tests/t00025/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00025_class: + type: class + glob: + - ../../tests/t00025/t00025.cc + using_namespace: + - clanguml::t00025 + include: + namespaces: + - clanguml::t00025 diff --git a/tests/t00025/t00025.cc b/tests/t00025/t00025.cc new file mode 100644 index 00000000..c31ee5a8 --- /dev/null +++ b/tests/t00025/t00025.cc @@ -0,0 +1,37 @@ +#include + +namespace clanguml { +namespace t00025 { + +class Target1 { +public: + void m1() {} + void m2() {} +}; + +class Target2 { +public: + void m1() {} + void m2() {} +}; + +template class Proxy { +public: + Proxy(std::shared_ptr target) + : m_target{std::move(target)} + { + } + void m1() { m_target->m1(); } + void m2() { m_target->m2(); } + +private: + std::shared_ptr m_target; +}; + +class ProxyHolder { +public: + Proxy proxy1; + Proxy proxy2; +}; +} +} diff --git a/tests/t00025/test_case.h b/tests/t00025/test_case.h new file mode 100644 index 00000000..61e714ea --- /dev/null +++ b/tests/t00025/test_case.h @@ -0,0 +1,60 @@ +/** + * tests/t00025/test_case.cc + * + * Copyright (c) 2021 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("t00025", "[test-case][class]") +{ + auto [config, db] = load_config("t00025"); + + auto diagram = config.diagrams["t00025_class"]; + + REQUIRE(diagram->name == "t00025_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00025"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00025::A")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00025_class"); + + auto puml = generate_class_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, IsClass(_A("Target1"))); + REQUIRE_THAT(puml, IsClass(_A("Target2"))); + REQUIRE_THAT(puml, IsClassTemplate("Proxy", "T")); + REQUIRE_THAT(puml, IsInstantiation(_A("Proxy"), _A("Proxy"))); + REQUIRE_THAT(puml, IsInstantiation(_A("Proxy"), _A("Proxy"))); + REQUIRE_THAT(puml, + IsAggregation(_A("ProxyHolder"), _A("Proxy"), "+proxy1")); + REQUIRE_THAT(puml, + IsAggregation(_A("ProxyHolder"), _A("Proxy"), "+proxy2")); + REQUIRE_THAT( + puml, !IsAggregation(_A("ProxyHolder"), _A("Target1"), "+proxy1")); + REQUIRE_THAT( + puml, !IsAggregation(_A("ProxyHolder"), _A("Target2"), "+proxy2")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index be6f76f9..36c830a8 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -128,6 +128,7 @@ using namespace clanguml::test::matchers; #include "t00022/test_case.h" #include "t00023/test_case.h" #include "t00024/test_case.h" +#include "t00025/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 6090f7d6..ab33058f 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -69,6 +69,9 @@ test_cases: - name: t00024 title: Proxy pattern description: + - name: t00025 + title: Template proxy pattern + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram