Added initial class template handling

This commit is contained in:
Bartek Kryza
2021-03-06 12:12:35 +01:00
parent 5be5172bd3
commit e267d295f6
10 changed files with 180 additions and 12 deletions

View File

@@ -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; }

View File

@@ -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<clanguml::cx::type> {
template <typename ParseContext> constexpr auto parse(ParseContext &ctx)
{
return ctx.begin();
}
template <typename FormatContext>
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());
}
};

View File

@@ -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<std::string> 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

View File

@@ -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<class_method> methods;
std::vector<class_parent> bases;
std::vector<std::string> inner_classes;
std::vector<class_relationship> relationships;
std::vector<class_template> templates;
bool is_abstract() const
{

View File

@@ -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<class_>(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;

12
tests/t00008/.clanguml Normal file
View File

@@ -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

13
tests/t00008/t00008.cc Normal file
View File

@@ -0,0 +1,13 @@
#include <vector>
namespace clanguml {
namespace t00008 {
template <typename T, typename P = T> class A {
public:
T value;
T *pointer;
T &reference;
std::vector<P> values;
};
}
}

55
tests/t00008/test_case.h Normal file
View File

@@ -0,0 +1,55 @@
/**
* tests/t00008/test_case.cc
*
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
*
* 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<P> values")));
save_puml(
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
}

View File

@@ -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"

View File

@@ -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)
{