From e267d295f6e822e818662706326cbb61e53c3a80 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 6 Mar 2021 12:12:35 +0100 Subject: [PATCH] Added initial class template handling --- src/cx/cursor.h | 20 +++++++++++ src/cx/type.h | 24 ++++++++++++- src/puml/class_diagram_generator.h | 13 +++++-- src/uml/class_diagram_model.h | 6 +++- src/uml/class_diagram_visitor.h | 39 ++++++++++++++++----- tests/t00008/.clanguml | 12 +++++++ tests/t00008/t00008.cc | 13 +++++++ tests/t00008/test_case.h | 55 ++++++++++++++++++++++++++++++ tests/test_cases.cc | 2 ++ tests/test_cases.h | 8 +++++ 10 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 tests/t00008/.clanguml create mode 100644 tests/t00008/t00008.cc create mode 100644 tests/t00008/test_case.h diff --git a/src/cx/cursor.h b/src/cx/cursor.h index a23fc891..b6e549bc 100644 --- a/src/cx/cursor.h +++ b/src/cx/cursor.h @@ -194,6 +194,26 @@ public: return clang_getCXXAccessSpecifier(m_cursor); } + int template_argument_count() const + { + return clang_Cursor_getNumTemplateArguments(m_cursor); + } + + CXTemplateArgumentKind template_argument_kind(unsigned i) const + { + return clang_Cursor_getTemplateArgumentKind(m_cursor, i); + } + + cx::type template_argument_type(unsigned i) const + { + return clang_Cursor_getTemplateArgumentType(m_cursor, i); + } + + long long template_argument_value(unsigned i) const + { + return clang_Cursor_getTemplateArgumentValue(m_cursor, i); + } + std::string usr() const { return to_string(clang_getCursorUSR(m_cursor)); } const CXCursor &get() const { return m_cursor; } diff --git a/src/cx/type.h b/src/cx/type.h index 4ff39f2f..c6bf1d8e 100644 --- a/src/cx/type.h +++ b/src/cx/type.h @@ -88,7 +88,7 @@ public: CXTypeKind kind() const { return m_type.kind; } - std::string kind_spelling() + std::string kind_spelling() const { return to_string(clang_getTypeKindSpelling(m_type.kind)); } @@ -182,6 +182,11 @@ public: bool is_template() const { return template_arguments_count() > 0; } + bool is_template_parameter() const + { + return canonical().spelling().find("type-parameter-") == 0; + } + int template_arguments_count() const { return clang_Type_getNumTemplateArguments(m_type); @@ -225,3 +230,20 @@ private: }; } } + +template <> struct fmt::formatter { + template constexpr auto parse(ParseContext &ctx) + { + return ctx.begin(); + } + + template + auto format(const clanguml::cx::type &t, FormatContext &ctx) + { + return fmt::format_to(ctx.out(), + "(cx::type spelling={}, kind={}, pointee={}, " + "is_pod={}, canonical={}, is_relationship={})", + t.spelling(), t.kind_spelling(), t.pointee_type().spelling(), + t.is_pod(), t.canonical().spelling(), t.is_relationship()); + } +}; diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index 62fddc2c..758fd54f 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -100,8 +100,17 @@ public: else ostr << "class "; - ostr << ns_relative(m_config.using_namespace, c.name) << " {" - << std::endl; + ostr << ns_relative(m_config.using_namespace, c.name); + + if (!c.templates.empty()) { + std::vector tnames; + std::transform(c.templates.cbegin(), c.templates.cend(), + std::back_inserter(tnames), + [](const auto &tmplt) { return tmplt.name; }); + ostr << fmt::format("<{}>", fmt::join(tnames, ", ")); + } + + ostr << " {" << std::endl; // // Process methods diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index bdd30134..31942ee8 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -87,6 +87,10 @@ struct class_relationship { std::string label; }; +struct class_template { + std::string name; +}; + struct class_ : public element { bool is_struct{false}; bool is_template{false}; @@ -94,8 +98,8 @@ struct class_ : public element { std::vector methods; std::vector bases; std::vector inner_classes; - std::vector relationships; + std::vector templates; bool is_abstract() const { diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 4ea7af41..73419992 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -37,6 +37,7 @@ using clanguml::model::class_diagram::class_member; using clanguml::model::class_diagram::class_method; using clanguml::model::class_diagram::class_parent; using clanguml::model::class_diagram::class_relationship; +using clanguml::model::class_diagram::class_template; using clanguml::model::class_diagram::diagram; using clanguml::model::class_diagram::enum_; using clanguml::model::class_diagram::relationship_t; @@ -235,6 +236,9 @@ static enum CXChildVisitResult class_visitor( c.name = cursor.fully_qualified(); c.namespace_ = ctx->ctx->namespace_; + spdlog::info("Class {} has {} template arguments.", c.name, + cursor.template_argument_count()); + auto class_ctx = element_visitor_context(c); class_ctx.ctx = ctx->ctx; @@ -280,6 +284,25 @@ static enum CXChildVisitResult class_visitor( }); ret = CXChildVisit_Continue; break; + case CXCursor_TemplateTypeParameter: { + spdlog::info( + "Found template type parameter: {}", cursor.spelling()); + class_template ct; + ct.name = cursor.spelling(); + ctx->element.templates.emplace_back(std::move(ct)); + + ret = CXChildVisit_Continue; + } break; + case CXCursor_NonTypeTemplateParameter: + spdlog::info( + "Found template nontype parameter: {}", cursor.spelling()); + ret = CXChildVisit_Continue; + break; + case CXCursor_TemplateTemplateParameter: + spdlog::info( + "Found template template parameter: {}", cursor.spelling()); + ret = CXChildVisit_Continue; + break; case CXCursor_CXXMethod: case CXCursor_Constructor: case CXCursor_Destructor: @@ -317,18 +340,18 @@ static enum CXChildVisitResult class_visitor( auto t = cursor.type(); class_member m; m.name = cursor.spelling(); - m.type = t.is_template() ? t.unqualified() - : t.canonical().unqualified(); + if (t.is_template()) + m.type = t.unqualified(); + else if (t.is_template_parameter()) + m.type = t.spelling(); + else + m.type = t.canonical().unqualified(); m.scope = cx_access_specifier_to_scope( cursor.cxxaccess_specifier()); m.is_static = cursor.is_static(); - spdlog::info("Adding member {} {}::{} (type kind: {} | {} " - "| {} | {} | {})", - m.type, ctx->element.name, cursor.spelling(), - t.kind_spelling(), t.pointee_type().spelling(), - t.is_pod(), t.canonical().spelling(), - t.is_relationship()); + spdlog::info("Adding member {} {}::{} {}", m.type, + ctx->element.name, cursor.spelling(), t); relationship_t relationship_type = relationship_t::kNone; diff --git a/tests/t00008/.clanguml b/tests/t00008/.clanguml new file mode 100644 index 00000000..07c18890 --- /dev/null +++ b/tests/t00008/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00008_class: + type: class + glob: + - ../../tests/t00008/t00008.cc + using_namespace: + - clanguml::t00008 + include: + namespaces: + - clanguml::t00008 diff --git a/tests/t00008/t00008.cc b/tests/t00008/t00008.cc new file mode 100644 index 00000000..94c0b5bf --- /dev/null +++ b/tests/t00008/t00008.cc @@ -0,0 +1,13 @@ +#include + +namespace clanguml { +namespace t00008 { +template class A { +public: + T value; + T *pointer; + T &reference; + std::vector

values; +}; +} +} diff --git a/tests/t00008/test_case.h b/tests/t00008/test_case.h new file mode 100644 index 00000000..efcba415 --- /dev/null +++ b/tests/t00008/test_case.h @@ -0,0 +1,55 @@ +/** + * tests/t00008/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 t00008", "[unit-test]") +{ + spdlog::set_level(spdlog::level::debug); + + auto [config, db] = load_config("t00008"); + + auto diagram = config.diagrams["t00008_class"]; + + REQUIRE(diagram->name == "t00008_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00008"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00008::A")); + REQUIRE(diagram->should_include("clanguml::t00008::B")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00008_class"); + + auto puml = generate_class_puml(diagram, model); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, IsClassTemplate("A", "T, P")); + + REQUIRE_THAT(puml, IsField(Public("T value"))); + REQUIRE_THAT(puml, IsField(Public("T * pointer"))); + REQUIRE_THAT(puml, IsField(Public("T & reference"))); + REQUIRE_THAT(puml, IsField(Public("std::vector

values"))); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 75efc94d..d860343c 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -100,6 +100,7 @@ using clanguml::test::matchers::IsAggregation; using clanguml::test::matchers::IsAssociation; using clanguml::test::matchers::IsBaseClass; using clanguml::test::matchers::IsClass; +using clanguml::test::matchers::IsClassTemplate; using clanguml::test::matchers::IsComposition; using clanguml::test::matchers::IsEnum; using clanguml::test::matchers::IsField; @@ -116,3 +117,4 @@ using clanguml::test::matchers::Static; #include "t00005/test_case.h" #include "t00006/test_case.h" #include "t00007/test_case.h" +#include "t00008/test_case.h" diff --git a/tests/test_cases.h b/tests/test_cases.h index c19c35c7..a3a26a7f 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -196,6 +196,14 @@ ContainsMatcher IsClass(std::string const &str, return ContainsMatcher(CasedString("class " + str, caseSensitivity)); } +ContainsMatcher IsClassTemplate(std::string const &str, + std::string const &tmplt, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher( + CasedString(fmt::format("class {}<{}>", str, tmplt), caseSensitivity)); +} + ContainsMatcher IsEnum(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) {