diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index 09acd848..5953b26b 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -194,12 +194,15 @@ struct diagram { std::string usr_to_name(const std::vector &using_namespaces, const std::string &usr) const { + if (usr.empty()) + throw std::runtime_error("Empty USR"); + for (const auto &c : classes) { if (c.usr == usr) return c.full_name(using_namespaces); } - throw std::runtime_error("Cannot resolve USR: " + usr); + return usr; } }; } diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 520fe920..0b6c364a 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -369,23 +369,45 @@ static enum CXChildVisitResult class_visitor( cursor.cxxaccess_specifier()); m.is_static = cursor.is_static(); - spdlog::info("Adding member {} {}::{} {}, {}", m.type, - ctx->element.name, cursor.spelling(), t, tr); + spdlog::info("Adding member {} {}::{} {}, {}, {}", m.type, + ctx->element.name, cursor.spelling(), t, tr, + tr.type_declaration()); if (tr.is_unexposed()) { if (tr.is_template_instantiation() && - tr.type_declaration() - .specialized_cursor_template() - .kind() != CXCursor_InvalidFile) { - spdlog::info( - "Found template instantiation: {} ..|> {}", - tr.type_declaration(), + (tr.type_declaration().kind() != + CXCursor_InvalidFile || tr.type_declaration() - .specialized_cursor_template()); + .specialized_cursor_template() + .kind() != CXCursor_InvalidFile)) { + + bool partial_specialization = false; + auto template_type = + tr.type_declaration() + .specialized_cursor_template(); + if (template_type.kind() == CXCursor_InvalidFile) { + partial_specialization = true; + template_type = tr.type_declaration(); + } + + spdlog::info( + "Found template instantiation: {} ..|> {}", tr, + template_type); + class_ tinst; - tinst.name = tr.type_declaration().spelling(); + if (partial_specialization) { + tinst.name = template_type.spelling(); + } + else { + tinst.name = template_type.spelling(); + } tinst.is_template_instantiation = true; - tinst.usr = tr.type_declaration().usr(); + if (partial_specialization) { + tinst.usr = template_type.usr(); + } + else { + tinst.usr = tr.type_declaration().usr(); + } for (int i = 0; i < tr.template_arguments_count(); i++) { class_template ct; @@ -393,10 +415,12 @@ static enum CXChildVisitResult class_visitor( tr.template_argument_type(i).spelling(); tinst.templates.emplace_back(std::move(ct)); } - tinst.base_template_usr = - tr.type_declaration() - .specialized_cursor_template() - .usr(); + if (partial_specialization) { + tinst.base_template_usr = template_type.usr(); + } + else { + tinst.base_template_usr = template_type.usr(); + } class_relationship r; r.destination = tinst.base_template_usr; @@ -404,7 +428,12 @@ static enum CXChildVisitResult class_visitor( r.label = ""; class_relationship a; - a.destination = tinst.usr; + if (partial_specialization) { + a.destination = tr.spelling(); + } + else { + a.destination = tinst.usr; + } if (t.is_pointer() || t.is_reference()) a.type = relationship_t::kAssociation; else diff --git a/tests/t00010/.clanguml b/tests/t00010/.clanguml new file mode 100644 index 00000000..1388e6f1 --- /dev/null +++ b/tests/t00010/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00010_class: + type: class + glob: + - ../../tests/t00010/t00010.cc + using_namespace: + - clanguml::t00010 + include: + namespaces: + - clanguml::t00010 diff --git a/tests/t00010/t00010.cc b/tests/t00010/t00010.cc new file mode 100644 index 00000000..3aded309 --- /dev/null +++ b/tests/t00010/t00010.cc @@ -0,0 +1,23 @@ +#include +#include + +namespace clanguml { +namespace t00010 { + +template class A { +public: + T first; + P second; +}; + +template class B { +public: + A astring; +}; + +class C { +public: + B aintstring; +}; +} +} diff --git a/tests/t00010/test_case.h b/tests/t00010/test_case.h new file mode 100644 index 00000000..20fffd5c --- /dev/null +++ b/tests/t00010/test_case.h @@ -0,0 +1,62 @@ +/** + * tests/t00010/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("Test t00010", "[unit-test]") +{ + spdlog::set_level(spdlog::level::debug); + + auto [config, db] = load_config("t00010"); + + auto diagram = config.diagrams["t00010_class"]; + + REQUIRE(diagram->name == "t00010_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00010"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00010::A")); + REQUIRE(diagram->should_include("clanguml::t00010::B")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00010_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, IsClassTemplate("A", "T, P")); + REQUIRE_THAT(puml, IsClassTemplate("B", "T")); + + REQUIRE_THAT(puml, IsField(Public("A astring"))); + REQUIRE_THAT(puml, IsField(Public("B aintstring"))); + + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT(puml, IsInstantiation(_A("B"), _A("B"))); + + REQUIRE_THAT( + puml, IsComposition(_A("B"), _A("A"), "astring")); + REQUIRE_THAT(puml, IsComposition(_A("C"), _A("B"), "aintstring")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 1eebd140..dd9ce549 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -121,3 +121,4 @@ using clanguml::test::matchers::Static; #include "t00007/test_case.h" #include "t00008/test_case.h" #include "t00009/test_case.h" +#include "t00010/test_case.h"