Fixed rendering of member variables with alias to template or alias template (t00014)

This commit is contained in:
Bartek Kryza
2022-02-22 13:02:04 +01:00
parent e8ebaad6c6
commit 1a5a7aefcb
21 changed files with 355 additions and 109 deletions

View File

@@ -89,7 +89,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
return mp.to_string(m_config.using_namespace()); return mp.to_string(m_config.using_namespace());
}); });
auto args_string = fmt::format("{}", fmt::join(params, ", ")); auto args_string = fmt::format("{}", fmt::join(params, ", "));
if (m_config.generate_method_arguments() != if (m_config.generate_method_arguments() ==
config::method_arguments::abbreviated) { config::method_arguments::abbreviated) {
args_string = clanguml::util::abbreviate(args_string, 10); args_string = clanguml::util::abbreviate(args_string, 10);
} }
@@ -277,21 +277,21 @@ void generator::generate(std::ostream &ostr) const
if (m_config.should_include_entities("classes")) { if (m_config.should_include_entities("classes")) {
for (const auto &c : m_model.classes()) { for (const auto &c : m_model.classes()) {
if (!m_config.should_include(c.get().name())) if (!m_config.should_include(c->get_namespace(), c->name()))
continue; continue;
generate_alias(*c, ostr); generate_alias(*c, ostr);
ostr << '\n'; ostr << '\n';
} }
for (const auto &e : m_model.enums()) { for (const auto &e : m_model.enums()) {
if (!m_config.should_include(e.get().name())) if (!m_config.should_include(e->get_namespace(), e->name()))
continue; continue;
generate_alias(*e, ostr); generate_alias(*e, ostr);
ostr << '\n'; ostr << '\n';
} }
for (const auto &c : m_model.classes()) { for (const auto &c : m_model.classes()) {
if (!m_config.should_include(c.get().name())) if (!m_config.should_include(c->get_namespace(), c->name()))
continue; continue;
generate(*c, ostr); generate(*c, ostr);
ostr << '\n'; ostr << '\n';

View File

@@ -101,10 +101,10 @@ std::string class_::full_name(bool relative) const
using namespace clanguml::util; using namespace clanguml::util;
std::ostringstream ostr; std::ostringstream ostr;
if (relative) if (relative && starts_with(get_namespace(), using_namespaces()))
ostr << ns_relative(using_namespaces(), name()); ostr << ns_relative(using_namespaces(), name());
else else
ostr << name(); ostr << name_and_ns();
if (!templates_.empty()) { if (!templates_.empty()) {
std::vector<std::string> tnames; std::vector<std::string> tnames;

View File

@@ -37,9 +37,9 @@ public:
class_(const std::vector<std::string> &using_namespaces); class_(const std::vector<std::string> &using_namespaces);
class_(const class_ &) = delete; class_(const class_ &) = delete;
class_(class_ &&) = default; class_(class_ &&) noexcept = delete;
class_ &operator=(const class_ &) = delete; class_ &operator=(const class_ &) = delete;
class_ &operator=(class_ &&) = default; class_ &operator=(class_ &&) = delete;
bool is_struct() const; bool is_struct() const;
void is_struct(bool is_struct); void is_struct(bool is_struct);

View File

@@ -21,14 +21,17 @@
#include "util/error.h" #include "util/error.h"
#include "util/util.h" #include "util/util.h"
#include <cassert>
#include <iostream>
namespace clanguml::class_diagram::model { namespace clanguml::class_diagram::model {
const std::vector<type_safe::object_ref<class_>> diagram::classes() const const std::vector<type_safe::object_ref<const class_>> diagram::classes() const
{ {
return classes_; return classes_;
} }
const std::vector<type_safe::object_ref<enum_>> diagram::enums() const const std::vector<type_safe::object_ref<const enum_>> diagram::enums() const
{ {
return enums_; return enums_;
} }
@@ -57,16 +60,32 @@ void diagram::add_package(std::unique_ptr<common::model::package> &&p)
{ {
LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true));
add_element(p->get_namespace(), std::move(p)); auto ns = p->get_namespace();
add_element(ns, std::move(p));
} }
void diagram::add_class(std::unique_ptr<class_> &&c) void diagram::add_class(std::unique_ptr<class_> &&c)
{ {
LOG_DBG("Adding class: {}, {}", c->name(), c->full_name()); LOG_DBG("Adding class: {}, {}", c->name(), c->full_name());
if (util::contains(c->name(), "::"))
throw std::runtime_error("Name cannot contain namespace: " + c->name());
if (util::contains(c->name(), "<"))
throw std::runtime_error("Name cannot contain <: " + c->name());
if (util::contains(c->name(), "*"))
throw std::runtime_error("Name cannot contain *: " + c->name());
if (!has_class(*c)) { if (!has_class(*c)) {
LOG_DBG("### ADDED CLASS WITH ADDRESS: {}", (void *)c.get());
classes_.emplace_back(*c); classes_.emplace_back(*c);
add_element(c->get_namespace(), std::move(c)); auto ns = c->get_relative_namespace();
auto name = c->name();
add_element(ns, std::move(c));
ns.push_back(name);
const auto ccc = get_element<class_>(ns);
assert(ccc.value().name() == name);
} }
else else
LOG_DBG( LOG_DBG(
@@ -77,9 +96,12 @@ void diagram::add_enum(std::unique_ptr<enum_> &&e)
{ {
LOG_DBG("Adding enum: {}", e->name()); LOG_DBG("Adding enum: {}", e->name());
assert(!util::contains(e->name(), "::"));
if (!has_enum(*e)) { if (!has_enum(*e)) {
enums_.emplace_back(*e); enums_.emplace_back(*e);
add_element(e->get_namespace(), std::move(e)); auto ns = e->get_relative_namespace();
add_element(ns, std::move(e));
} }
else else
LOG_DBG("Enum {} already in the model", e->name()); LOG_DBG("Enum {} already in the model", e->name());
@@ -90,13 +112,14 @@ std::string diagram::to_alias(const std::string &full_name) const
LOG_DBG("Looking for alias for {}", full_name); LOG_DBG("Looking for alias for {}", full_name);
for (const auto &c : classes_) { for (const auto &c : classes_) {
if (c->full_name() == full_name) { const auto &cc = c.get();
if (cc.full_name() == full_name) {
return c->alias(); return c->alias();
} }
} }
for (const auto &e : enums_) { for (const auto &e : enums_) {
if (e->full_name() == full_name) { if (e.get().full_name() == full_name) {
return e->alias(); return e->alias();
} }
} }
@@ -104,4 +127,5 @@ std::string diagram::to_alias(const std::string &full_name) const
throw error::uml_alias_missing( throw error::uml_alias_missing(
fmt::format("Missing alias for {}", full_name)); fmt::format("Missing alias for {}", full_name));
} }
} }

View File

@@ -40,9 +40,9 @@ public:
diagram &operator=(const diagram &) = delete; diagram &operator=(const diagram &) = delete;
diagram &operator=(diagram &&) = default; diagram &operator=(diagram &&) = default;
const std::vector<type_safe::object_ref<class_>> classes() const; const std::vector<type_safe::object_ref<const class_>> classes() const;
const std::vector<type_safe::object_ref<enum_>> enums() const; const std::vector<type_safe::object_ref<const enum_>> enums() const;
bool has_class(const class_ &c) const; bool has_class(const class_ &c) const;
@@ -58,9 +58,11 @@ public:
std::string to_alias(const std::string &full_name) const; std::string to_alias(const std::string &full_name) const;
friend void print_diagram_tree(const diagram &d, const int level);
private: private:
std::vector<type_safe::object_ref<class_>> classes_; std::vector<type_safe::object_ref<const class_, false>> classes_;
std::vector<type_safe::object_ref<enum_>> enums_; std::vector<type_safe::object_ref<const enum_, false>> enums_;
std::map<std::string, std::unique_ptr<type_alias>> type_aliases_; std::map<std::string, std::unique_ptr<type_alias>> type_aliases_;
}; };
} }

View File

@@ -106,9 +106,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
if (!util::starts_with(usn, package_path)) { if (!util::starts_with(usn, package_path)) {
auto p = std::make_unique<common::model::package>( auto p = std::make_unique<common::model::package>(
ctx.config().using_namespace()); util::split(
ctx.config().using_namespace()[0], "::"));
util::remove_prefix(package_path, usn); util::remove_prefix(package_path, usn);
util::remove_prefix(package_parent, usn);
p->set_name(e.name()); p->set_name(e.name());
p->set_namespace(package_parent); p->set_namespace(package_parent);
@@ -129,8 +129,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
} }
if (!p->skip()) { if (!p->skip()) {
ctx.diagram().add_element( ctx.diagram().add_package(std::move(p));
package_parent, std::move(p));
ctx.set_current_package( ctx.set_current_package(
ctx.diagram() ctx.diagram()
.get_element<common::model::package>( .get_element<common::model::package>(
@@ -190,7 +189,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
} }
if (ctx.config().should_include( if (ctx.config().should_include(
cx::util::fully_prefixed(ctx.get_namespace(), cls))) ctx.get_namespace(), cls.name()))
// cx::util::fully_prefixed(ctx.get_namespace(),
// cls)))
process_class_declaration(cls); process_class_declaration(cls);
} }
else if (e.kind() == cppast::cpp_entity_kind::enum_t) { else if (e.kind() == cppast::cpp_entity_kind::enum_t) {
@@ -201,7 +202,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
auto &enm = static_cast<const cppast::cpp_enum &>(e); auto &enm = static_cast<const cppast::cpp_enum &>(e);
if (ctx.config().should_include( if (ctx.config().should_include(
cx::util::fully_prefixed(ctx.get_namespace(), enm))) ctx.get_namespace(), enm.name()))
// cx::util::fully_prefixed(ctx.get_namespace(),
// enm)))
process_enum_declaration(enm); process_enum_declaration(enm);
} }
else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) { else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) {
@@ -210,7 +213,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
cppast::to_string(e.kind())); cppast::to_string(e.kind()));
auto &ta = static_cast<const cppast::cpp_type_alias &>(e); auto &ta = static_cast<const cppast::cpp_type_alias &>(e);
std::unique_ptr<type_alias> t; auto t = std::make_unique<type_alias>();
t->set_alias(cx::util::full_name(ctx.get_namespace(), ta)); t->set_alias(cx::util::full_name(ctx.get_namespace(), ta));
t->set_underlying_type(cx::util::full_name(ta.underlying_type(), t->set_underlying_type(cx::util::full_name(ta.underlying_type(),
ctx.entity_index(), cx::util::is_inside_class(e))); ctx.entity_index(), cx::util::is_inside_class(e)));
@@ -237,9 +240,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
else { else {
auto tinst = build_template_instantiation(static_cast< auto tinst = build_template_instantiation(static_cast<
const cppast::cpp_template_instantiation_type &>( const cppast::cpp_template_instantiation_type &>(
at.type_alias().underlying_type())); resolve_alias(at.type_alias().underlying_type())));
ctx.diagram().add_class(std::move(tinst)); ctx.add_type_alias_template(
cx::util::full_name(ctx.get_namespace(), at),
type_safe::ref(at.type_alias().underlying_type()));
if (ctx.config().should_include(
tinst->get_namespace(), tinst->name()))
ctx.diagram().add_class(std::move(tinst));
} }
} }
}); });
@@ -254,9 +263,10 @@ void translation_unit_visitor::process_enum_declaration(
return; return;
} }
auto e_ptr = std::make_unique<enum_>(ctx.config().using_namespace()); auto e_ptr = std::make_unique<enum_>(
util::split(ctx.config().using_namespace()[0], "::"));
auto &e = *e_ptr; auto &e = *e_ptr;
e.set_name(cx::util::full_name(ctx.get_namespace(), enm)); e.set_name(enm.name());
e.set_namespace(ctx.get_namespace()); e.set_namespace(ctx.get_namespace());
if (enm.comment().has_value()) if (enm.comment().has_value())
@@ -284,7 +294,9 @@ void translation_unit_visitor::process_enum_declaration(
e.add_relationship({relationship_t::kContainment, e.add_relationship({relationship_t::kContainment,
cx::util::full_name(ctx.get_namespace(), cur.value())}); cx::util::full_name(ctx.get_namespace(), cur.value())});
LOG_DBG("Added containment relationship {} +-- {}", e.name()); LOG_DBG("Added containment relationship {} +-- {}",
cx::util::full_name(ctx.get_namespace(), cur.value()),
e.name());
break; break;
} }
} }
@@ -296,10 +308,12 @@ void translation_unit_visitor::process_class_declaration(
const cppast::cpp_class &cls, const cppast::cpp_class &cls,
type_safe::optional_ref<const cppast::cpp_template_specialization> tspec) type_safe::optional_ref<const cppast::cpp_template_specialization> tspec)
{ {
auto c_ptr = std::make_unique<class_>(ctx.config().using_namespace()); auto c_ptr = std::make_unique<class_>(
util::split(ctx.config().using_namespace()[0], "::"));
auto &c = *c_ptr; auto &c = *c_ptr;
c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t); c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t);
c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); // c.set_name(cx::util::full_name(ctx.get_namespace(), cls));
c.set_name(cls.name());
c.set_namespace(ctx.get_namespace()); c.set_namespace(ctx.get_namespace());
if (cls.comment().has_value()) if (cls.comment().has_value())
@@ -576,6 +590,7 @@ void translation_unit_visitor::process_class_declaration(
static_cast<const char *>(cls.user_data()), static_cast<const char *>(cls.user_data()),
fmt::ptr(reinterpret_cast<const void *>(&cls))); fmt::ptr(reinterpret_cast<const void *>(&cls)));
assert(c_ptr);
ctx.diagram().add_class(std::move(c_ptr)); ctx.diagram().add_class(std::move(c_ptr));
} }
@@ -588,6 +603,8 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
bool res = false; bool res = false;
auto tr_declaration = cppast::to_string(tr);
const auto &template_instantiation_type = const auto &template_instantiation_type =
static_cast<const cppast::cpp_template_instantiation_type &>(tr); static_cast<const cppast::cpp_template_instantiation_type &>(tr);
@@ -595,7 +612,14 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
static_cast<const cppast::cpp_template_instantiation_type &>( static_cast<const cppast::cpp_template_instantiation_type &>(
resolve_alias(template_instantiation_type)); resolve_alias(template_instantiation_type));
auto tinst_ptr = build_template_instantiation(unaliased); auto tr_unaliased_declaration = cppast::to_string(unaliased);
std::unique_ptr<class_> tinst_ptr;
if (util::contains(tr_declaration, "<"))
tinst_ptr = build_template_instantiation(template_instantiation_type);
else
tinst_ptr = build_template_instantiation(unaliased);
auto &tinst = *tinst_ptr; auto &tinst = *tinst_ptr;
// Infer the relationship of this field to the template // Infer the relationship of this field to the template
@@ -604,7 +628,6 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
if (mv.type().kind() == cppast::cpp_type_kind::pointer_t || if (mv.type().kind() == cppast::cpp_type_kind::pointer_t ||
mv.type().kind() == cppast::cpp_type_kind::reference_t) mv.type().kind() == cppast::cpp_type_kind::reference_t)
relationship_type = relationship_t::kAssociation; relationship_type = relationship_t::kAssociation;
else else
relationship_type = relationship_t::kAggregation; relationship_type = relationship_t::kAggregation;
@@ -623,13 +646,19 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
} }
} }
if (ctx.config().should_include(tinst.name())) { if (ctx.config().should_include(tinst.get_namespace(), tinst.name())) {
LOG_DBG("Adding field instantiation relationship {} {} {} : {}", LOG_DBG("Adding field instantiation relationship {} {} {} : {}",
rr.destination(), clanguml::common::model::to_string(rr.type()), rr.destination(), clanguml::common::model::to_string(rr.type()),
c.full_name(), rr.label()); c.full_name(), rr.label());
c.add_relationship(std::move(rr)); c.add_relationship(std::move(rr));
if (tr_declaration != tr_unaliased_declaration) {
// Add template instantiation/specialization relationship;
tinst.add_relationship(
{relationship_t::kInstantiation, tr_unaliased_declaration});
}
res = true; res = true;
LOG_DBG("Created template instantiation: {}", tinst.full_name()); LOG_DBG("Created template instantiation: {}", tinst.full_name());
@@ -668,11 +697,14 @@ void translation_unit_visitor::process_field(
else if (tr.kind() == cppast::cpp_type_kind::user_defined_t) { else if (tr.kind() == cppast::cpp_type_kind::user_defined_t) {
LOG_DBG("Processing user defined type field {} {}", LOG_DBG("Processing user defined type field {} {}",
cppast::to_string(tr), mv.name()); cppast::to_string(tr), mv.name());
if (resolve_alias(tr).kind() ==
cppast::cpp_type_kind::template_instantiation_t)
template_instantiation_added_as_aggregation =
process_field_with_template_instantiation(mv, tr, c, m, as);
} }
else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) { else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) {
template_instantiation_added_as_aggregation = template_instantiation_added_as_aggregation =
process_field_with_template_instantiation( process_field_with_template_instantiation(mv, tr, c, m, as);
mv, resolve_alias(tr), c, m, as);
} }
else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) { else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) {
LOG_DBG( LOG_DBG(
@@ -680,6 +712,8 @@ void translation_unit_visitor::process_field(
// TODO // TODO
} }
auto tr_declaration = cppast::to_string(tr);
if (!m.skip_relationship() && if (!m.skip_relationship() &&
!template_instantiation_added_as_aggregation && !template_instantiation_added_as_aggregation &&
(tr.kind() != cppast::cpp_type_kind::builtin_t) && (tr.kind() != cppast::cpp_type_kind::builtin_t) &&
@@ -929,8 +963,10 @@ void translation_unit_visitor::process_function_parameter(
find_relationships(cppast::remove_cv(param.type()), relationships, find_relationships(cppast::remove_cv(param.type()), relationships,
relationship_t::kDependency); relationship_t::kDependency);
for (const auto &[type, relationship_type] : relationships) { for (const auto &[type, relationship_type] : relationships) {
if ((relationship_type != relationship_t::kNone) &&
(type != c.name())) { if (ctx.config().should_include(cx::util::split_ns(type)) &&
(relationship_type != relationship_t::kNone) &&
(type != c.name_and_ns())) {
relationship r{relationship_t::kDependency, type}; relationship r{relationship_t::kDependency, type};
LOG_DBG("Adding field relationship {} {} {} : {}", LOG_DBG("Adding field relationship {} {} {} : {}",
@@ -954,11 +990,11 @@ void translation_unit_visitor::process_function_parameter(
// Here we need the name of the primary template with full // Here we need the name of the primary template with full
// namespace prefix to apply config inclusion filters // namespace prefix to apply config inclusion filters
auto primary_template_name = // auto primary_template_name =
cx::util::full_name(ctx.get_namespace(), // cx::util::full_name(ctx.get_namespace(),
template_instantiation_type.primary_template() // template_instantiation_type.primary_template()
.get(ctx.entity_index())[0] // .get(ctx.entity_index())[0]
.get()); // .get());
// Now check if the template arguments of this function param // Now check if the template arguments of this function param
// are a subset of the method template params - if yes this is // are a subset of the method template params - if yes this is
// not an instantiation but just a reference to an existing // not an instantiation but just a reference to an existing
@@ -987,10 +1023,15 @@ void translation_unit_visitor::process_function_parameter(
// arguments string // arguments string
} }
LOG_DBG("Maybe building instantiation for: {}", // LOG_DBG("Maybe building instantiation for:
primary_template_name); // {}",
// primary_template_name);
if (ctx.config().should_include(primary_template_name)) { if (ctx.config().should_include(ctx.get_namespace(),
template_instantiation_type.primary_template()
.get(ctx.entity_index())[0]
.get()
.name())) {
if (template_is_not_instantiation) { if (template_is_not_instantiation) {
LOG_DBG("Template is not an instantiation - " LOG_DBG("Template is not an instantiation - "
@@ -1073,9 +1114,11 @@ void translation_unit_visitor::process_friend(
return; return;
if (f.type()) { if (f.type()) {
auto name = cppast::to_string(f.type().value()); auto name_with_ns =
util::split(cppast::to_string(f.type().value()), "::");
if (!ctx.config().should_include(name)) auto name = name_with_ns.back();
name_with_ns.pop_back();
if (!ctx.config().should_include(name_with_ns, name))
return; return;
LOG_DBG("Type friend declaration {}", name); LOG_DBG("Type friend declaration {}", name);
@@ -1113,7 +1156,7 @@ void translation_unit_visitor::process_friend(
name = cx::util::full_name(ctx.get_namespace(), f.entity().value()); name = cx::util::full_name(ctx.get_namespace(), f.entity().value());
} }
if (!ctx.config().should_include(name)) if (!ctx.config().should_include(ctx.get_namespace(), name))
return; return;
r.set_destination(name); r.set_destination(name);
@@ -1192,7 +1235,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) {
// class_relationship r; // class_relationship r;
auto &tinst = const auto &tinst =
static_cast<const cppast::cpp_template_instantiation_type &>(t); static_cast<const cppast::cpp_template_instantiation_type &>(t);
if (!tinst.arguments_exposed()) { if (!tinst.arguments_exposed()) {
@@ -1201,28 +1244,47 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
return found; return found;
} }
const auto &args = tinst.arguments().value(); assert(tinst.arguments().has_value());
assert(tinst.arguments().value().size() > 0u);
[[maybe_unused]] const auto args_count =
tinst.arguments().value().size();
const auto args = tinst.arguments().value();
const auto [ns, base_name] = cx::util::split_ns(fn);
auto ns_and_name = ns;
ns_and_name.push_back(base_name);
auto full_name = fmt::format("{}", fmt::join(ns_and_name, "::"));
// Try to match common containers // Try to match common containers
// TODO: Refactor to a separate class with configurable // TODO: Refactor to a separate class with configurable
// container list // container list
if (name.find("std::unique_ptr") == 0) { if (full_name.find("std::unique_ptr") == 0) {
found = find_relationships(args[0u].type().value(), relationships, found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kAggregation); relationship_t::kAggregation);
} }
else if (name.find("std::shared_ptr") == 0) { else if (full_name.find("std::shared_ptr") == 0) {
found = find_relationships(args[0u].type().value(), relationships, found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kAssociation); relationship_t::kAssociation);
} }
else if (name.find("std::weak_ptr") == 0) { else if (full_name.find("std::weak_ptr") == 0) {
found = find_relationships(args[0u].type().value(), relationships, found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kAssociation); relationship_t::kAssociation);
} }
else if (name.find("std::vector") == 0) { else if (full_name.find("std::vector") == 0) {
found = find_relationships(args[0u].type().value(), relationships, assert(args.size() == 1u);
relationship_t::kAggregation); if (args[0u].type().has_value())
found = find_relationships(args[0u].type().value(),
relationships, relationship_t::kAggregation);
else
LOG_WARN(
"Failed to process template argument of std::vector at: {}",
fn);
} }
else if (ctx.config().should_include(fn)) { else if (ctx.config().should_include(ns, name)) {
LOG_DBG("User defined template instantiation: {} | {}", LOG_DBG("User defined template instantiation: {} | {}",
cppast::to_string(t_), cppast::to_string(t_.canonical())); cppast::to_string(t_), cppast::to_string(t_.canonical()));
@@ -1245,9 +1307,12 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
return found; return found;
} }
// ???
else { else {
for (const auto &arg : args) { for (const auto &arg : args) {
if (arg.type()) { if (arg.type().has_value()) {
LOG_DBG("########## PROCESSING PARAMETER TYPE: {}",
cppast::to_string(arg.type().value()));
found = find_relationships( found = find_relationships(
arg.type().value(), relationships, relationship_type); arg.type().value(), relationships, relationship_type);
} }
@@ -1262,7 +1327,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
const cppast::cpp_template_instantiation_type &t, const cppast::cpp_template_instantiation_type &t,
std::optional<clanguml::class_diagram::model::class_ *> parent) std::optional<clanguml::class_diagram::model::class_ *> parent)
{ {
auto tinst_ptr = std::make_unique<class_>(ctx.config().using_namespace()); auto tinst_ptr = std::make_unique<class_>(
util::split(ctx.config().using_namespace()[0], "::"));
auto &tinst = *tinst_ptr; auto &tinst = *tinst_ptr;
std::string full_template_name; std::string full_template_name;
@@ -1270,6 +1336,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
tinst.set_namespace(ctx.get_namespace()); tinst.set_namespace(ctx.get_namespace());
auto tinst_full_name = cppast::to_string(t);
if (t.primary_template().get(ctx.entity_index()).size()) { if (t.primary_template().get(ctx.entity_index()).size()) {
const auto &primary_template_ref = const auto &primary_template_ref =
static_cast<const cppast::cpp_class_template &>( static_cast<const cppast::cpp_class_template &>(
@@ -1361,28 +1429,37 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
LOG_DBG("Building template instantiation for {}", full_template_name); LOG_DBG("Building template instantiation for {}", full_template_name);
// Extract namespace from base template name // Extract namespace from base template name
std::vector<std::string> ns_toks; // std::vector<std::string> ns_toks;
ns_toks = clanguml::util::split( // ns_toks = clanguml::util::split(
full_template_name.substr(0, full_template_name.find('<')), "::"); // full_template_name.substr(0, full_template_name.find('<')), "::");
//
// std::string ns;
// if (ns_toks.size() > 1) {
// ns = fmt::format(
// "{}::", fmt::join(ns_toks.begin(), ns_toks.end() - 1, "::"));
// }
std::string ns; // LOG_DBG("Template namespace is {}", ns);
if (ns_toks.size() > 1) {
ns = fmt::format( const auto [ns, name] = cx::util::split_ns(tinst_full_name);
"{}::", fmt::join(ns_toks.begin(), ns_toks.end() - 1, "::")); tinst.set_name(name);
if (ns.empty())
tinst.set_namespace(ctx.get_namespace());
else
tinst.set_namespace(ns);
if (tinst_full_name.find('<') != std::string::npos) {
tinst.set_name(tinst_full_name.substr(0, tinst_full_name.find('<')));
} }
LOG_DBG("Template namespace is {}", ns);
tinst.set_name(ns + util::split(cppast::to_string(t), "<")[0]);
tinst.is_template_instantiation(true); tinst.is_template_instantiation(true);
if (tinst.full_name().substr(0, tinst.full_name().find('<')).find("::") == if (tinst.full_name().substr(0, tinst.full_name().find('<')).find("::") ==
std::string::npos) { std::string::npos) {
tinst.set_name(ns + tinst.full_name()); tinst.set_name(name);
} }
// Process template argumetns // Process template arguments
int arg_index{0}; int arg_index{0};
bool variadic_params{false}; bool variadic_params{false};
if (t.arguments_exposed()) { if (t.arguments_exposed()) {
@@ -1398,6 +1475,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
cx::util::unreferenced(targ.type().value())), cx::util::unreferenced(targ.type().value())),
ctx.entity_index(), false); ctx.entity_index(), false);
auto [fn_ns, fn_name] = cx::util::split_ns(fn);
if (targ.type().value().kind() == if (targ.type().value().kind() ==
cppast::cpp_type_kind::template_instantiation_t) { cppast::cpp_type_kind::template_instantiation_t) {
@@ -1409,9 +1488,11 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
if (parent) if (parent)
nnn = (*parent)->name(); nnn = (*parent)->name();
auto [tinst_ns, tinst_name] =
cx::util::split_ns(tinst.full_name(false));
auto nested_tinst = auto nested_tinst =
build_template_instantiation(nested_template_parameter, build_template_instantiation(nested_template_parameter,
ctx.config().should_include(tinst.full_name(false)) ctx.config().should_include(tinst_ns, tinst_name)
? std::make_optional(&tinst) ? std::make_optional(&tinst)
: parent); : parent);
@@ -1420,11 +1501,13 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
auto nested_tinst_full_name = nested_tinst->full_name(); auto nested_tinst_full_name = nested_tinst->full_name();
if (ctx.config().should_include(fn)) { if (ctx.config().should_include(fn_ns, fn_name)) {
ctx.diagram().add_class(std::move(nested_tinst)); ctx.diagram().add_class(std::move(nested_tinst));
} }
if (ctx.config().should_include(tinst.full_name(false))) { if (ctx.config().should_include(tinst_ns, tinst_name) &&
ctx.config().should_include(cx::util::split_ns(
tinst_dependency.destination()))) {
LOG_DBG( LOG_DBG(
"Creating nested template dependency to template " "Creating nested template dependency to template "
"instantiation {}, {} -> {}", "instantiation {}, {} -> {}",
@@ -1463,7 +1546,7 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
"type {} -> {}", "type {} -> {}",
tinst.full_name(), tinst_dependency.destination()); tinst.full_name(), tinst_dependency.destination());
if (ctx.config().should_include(fn)) { if (ctx.config().should_include(fn_ns, fn_name)) {
tinst.add_relationship(std::move(tinst_dependency)); tinst.add_relationship(std::move(tinst_dependency));
} }
else if (parent) { else if (parent) {
@@ -1547,12 +1630,32 @@ const cppast::cpp_type &translation_unit_visitor::resolve_alias(
const cppast::cpp_type &type) const cppast::cpp_type &type)
{ {
const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type));
const auto type_full_name = auto type_full_name =
cx::util::full_name(raw_type, ctx.entity_index(), false); cx::util::full_name(raw_type, ctx.entity_index(), false);
if (ctx.has_type_alias(type_full_name)) {
if (util::contains(type_full_name, "<"))
type_full_name = util::split(type_full_name, "<")[0];
if (ctx.has_type_alias_template(type_full_name)) {
return ctx.get_type_alias(type_full_name).get();
}
else if (ctx.has_type_alias(type_full_name)) {
return ctx.get_type_alias_final(raw_type).get(); return ctx.get_type_alias_final(raw_type).get();
} }
return type; return type;
} }
const cppast::cpp_type &translation_unit_visitor::resolve_alias_template(
const cppast::cpp_type &type)
{
const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type));
const auto type_full_name =
cx::util::full_name(raw_type, ctx.entity_index(), false);
if (ctx.has_type_alias_template(type_full_name)) {
return ctx.get_type_alias_template(type_full_name).get();
}
return type;
}
} }

View File

@@ -130,6 +130,8 @@ private:
* If t does not represent an alias, returns t. * If t does not represent an alias, returns t.
*/ */
const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t); const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t);
const cppast::cpp_type &resolve_alias_template(
const cppast::cpp_type &type);
// ctx allows to track current visitor context, e.g. current namespace // ctx allows to track current visitor context, e.g. current namespace
translation_unit_context ctx; translation_unit_context ctx;

View File

@@ -20,6 +20,8 @@
#include "util/util.h" #include "util/util.h"
#include <ostream>
namespace clanguml::common::model { namespace clanguml::common::model {
std::atomic_uint64_t element::m_nextId = 1; std::atomic_uint64_t element::m_nextId = 1;
@@ -28,6 +30,8 @@ element::element(const std::vector<std::string> &using_namespaces)
: using_namespaces_{using_namespaces} : using_namespaces_{using_namespaces}
, m_id{m_nextId++} , m_id{m_nextId++}
{ {
for (const auto &n : using_namespaces_)
assert(!util::contains(n, "::"));
} }
std::string element::alias() const { return fmt::format("C_{:010}", m_id); } std::string element::alias() const { return fmt::format("C_{:010}", m_id); }
@@ -41,6 +45,13 @@ void element::add_relationship(relationship &&cr)
return; return;
} }
if ((cr.type() == relationship_t::kInstantiation) &&
(cr.destination() == full_name(true))) {
LOG_WARN("Skipping self instantiation relationship for {}",
cr.destination());
return;
}
LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(), LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(),
to_string(cr.type()), full_name(true)); to_string(cr.type()), full_name(true));
@@ -50,6 +61,9 @@ void element::add_relationship(relationship &&cr)
void element::set_using_namespaces(const std::vector<std::string> &un) void element::set_using_namespaces(const std::vector<std::string> &un)
{ {
for (const auto &n : un)
assert(!util::contains(n, "::"));
using_namespaces_ = un; using_namespaces_ = un;
} }
@@ -71,4 +85,14 @@ bool operator==(const element &l, const element &r)
{ {
return l.full_name(false) == r.full_name(false); return l.full_name(false) == r.full_name(false);
} }
std::ostream &operator<<(std::ostream &out, const element &rhs)
{
out << "(" << rhs.name() << ", ns=["
<< util::join(rhs.get_namespace(), "::") << "], full_name=["
<< rhs.full_name(true) << "])";
return out;
}
} }

View File

@@ -19,8 +19,10 @@
#include "decorated_element.h" #include "decorated_element.h"
#include "relationship.h" #include "relationship.h"
#include "util/util.h"
#include <atomic> #include <atomic>
#include <exception>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -38,10 +40,24 @@ public:
std::string name() const { return name_; } std::string name() const { return name_; }
std::string name_and_ns() const
{
auto ns = namespace_;
ns.push_back(name());
return util::join(ns, "::");
}
void set_namespace(const std::vector<std::string> &ns) { namespace_ = ns; } void set_namespace(const std::vector<std::string> &ns) { namespace_ = ns; }
std::vector<std::string> get_namespace() const { return namespace_; } std::vector<std::string> get_namespace() const { return namespace_; }
std::vector<std::string> get_relative_namespace() const
{
auto relative_ns = namespace_;
util::remove_prefix(relative_ns, using_namespaces_);
return relative_ns;
}
virtual std::string full_name(bool relative) const { return name(); } virtual std::string full_name(bool relative) const { return name(); }
void set_using_namespaces(const std::vector<std::string> &un); void set_using_namespaces(const std::vector<std::string> &un);
@@ -58,6 +74,8 @@ public:
friend bool operator==(const element &l, const element &r); friend bool operator==(const element &l, const element &r);
friend std::ostream &operator<<(std::ostream &out, const element &rhs);
protected: protected:
const uint64_t m_id{0}; const uint64_t m_id{0};

View File

@@ -21,6 +21,7 @@
#include <type_safe/optional_ref.hpp> #include <type_safe/optional_ref.hpp>
#include <iostream>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -55,7 +56,7 @@ public:
{ {
assert(p); assert(p);
LOG_DBG("Adding nested element {} at path '{}'", p->name(), LOG_DBG("Adding nested element {} at path {}", p->name(),
fmt::join(path, "::")); fmt::join(path, "::"));
if (path.empty()) { if (path.empty()) {
@@ -68,9 +69,11 @@ public:
if (parent && dynamic_cast<nested_trait<T> *>(&parent.value())) if (parent && dynamic_cast<nested_trait<T> *>(&parent.value()))
dynamic_cast<nested_trait<T> &>(parent.value()) dynamic_cast<nested_trait<T> &>(parent.value())
.template add_element<V>(std::move(p)); .template add_element<V>(std::move(p));
else else {
spdlog::error( spdlog::error(
"No parent element found at: {}", fmt::join(path, "::")); "No parent element found at: {}", fmt::join(path, "::"));
throw std::runtime_error("No parent element found");
}
} }
template <typename V = T> template <typename V = T>
@@ -105,7 +108,7 @@ public:
[&](const auto &p) { return name == p->name(); }); [&](const auto &p) { return name == p->name(); });
if (it == elements_.end()) if (it == elements_.end())
return type_safe::optional_ref<V>{}; return type_safe::optional_ref<V>{type_safe::nullopt};
assert(it->get() != nullptr); assert(it->get() != nullptr);
@@ -113,7 +116,7 @@ public:
return type_safe::optional_ref<V>{ return type_safe::optional_ref<V>{
type_safe::ref<V>(dynamic_cast<V &>(*it->get()))}; type_safe::ref<V>(dynamic_cast<V &>(*it->get()))};
return type_safe::optional_ref<V>{}; return type_safe::optional_ref<V>{type_safe::nullopt};
} }
bool has_element(const std::string &name) const bool has_element(const std::string &name) const
@@ -132,6 +135,24 @@ public:
auto begin() const { return elements_.begin(); } auto begin() const { return elements_.begin(); }
auto end() const { return elements_.end(); } auto end() const { return elements_.end(); }
void print_tree(const int level)
{
const auto &d = *this;
if (level == 0) {
std::cout << "--- Printing tree:\n";
}
for (const auto &e : d) {
if (dynamic_cast<nested_trait<T> *>(e.get())) {
std::cout << std::string(level, ' ') << "[" << *e << "]\n";
dynamic_cast<nested_trait<T> *>(e.get())->print_tree(level + 1);
}
else {
std::cout << std::string(level, ' ') << "- " << *e << "]\n";
}
}
}
private: private:
std::vector<std::unique_ptr<T>> elements_; std::vector<std::unique_ptr<T>> elements_;
}; };

View File

@@ -129,6 +129,20 @@ bool diagram::should_include_relationship(const std::string &rel)
return false; return false;
} }
bool diagram::should_include(
const std::pair<std::vector<std::string>, std::string> &name) const
{
return should_include(std::get<0>(name), std::get<1>(name));
}
bool diagram::should_include(
const std::vector<std::string> &ns, const std::string &name) const
{
auto ns_and_name = ns;
ns_and_name.push_back(name);
return should_include(util::join(ns_and_name, "::"));
}
bool diagram::should_include(const std::string &name_) const bool diagram::should_include(const std::string &name_) const
{ {
auto name = clanguml::util::unqualify(name_); auto name = clanguml::util::unqualify(name_);

View File

@@ -87,8 +87,7 @@ struct inheritable_diagram_options {
option<plantuml> puml{"plantuml", option_inherit_mode::append}; option<plantuml> puml{"plantuml", option_inherit_mode::append};
option<method_arguments> generate_method_arguments{ option<method_arguments> generate_method_arguments{
"generate_method_arguments", method_arguments::full}; "generate_method_arguments", method_arguments::full};
option<bool> generate_packages{ option<bool> generate_packages{"generate_packages", false};
"generate_packages", false};
void inherit(const inheritable_diagram_options &parent); void inherit(const inheritable_diagram_options &parent);
}; };
@@ -104,9 +103,16 @@ struct diagram : public inheritable_diagram_options {
bool should_include_relationship(const std::string &rel); bool should_include_relationship(const std::string &rel);
bool should_include(const std::string &name_) const; bool should_include(
const std::pair<std::vector<std::string>, std::string> &name) const;
bool should_include(
const std::vector<std::string> &ns, const std::string &name) const;
bool should_include(const common::model::scope_t scope) const; bool should_include(const common::model::scope_t scope) const;
bool should_include(const std::string &name_) const;
private:
}; };
struct source_location { struct source_location {

View File

@@ -126,6 +126,16 @@ bool is_inside_class(const cppast::cpp_entity &e)
return false; return false;
} }
std::pair<std::vector<std::string>, std::string> split_ns(
const std::string &full_name)
{
auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
auto ns = ::clanguml::util::split(name_before_template, "::");
auto name = ns.back();
ns.pop_back();
return {ns, name};
}
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx) std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx)
{ {
if (t.kind() == cppast::cpp_type_kind::user_defined_t && if (t.kind() == cppast::cpp_type_kind::user_defined_t &&

View File

@@ -58,6 +58,9 @@ type_safe::optional_ref<const cppast::cpp_namespace> entity_ns(
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx);
std::pair<std::vector<std::string>, std::string> split_ns(
const std::string &full_name);
bool is_inside_class(const cppast::cpp_entity &e); bool is_inside_class(const cppast::cpp_entity &e);
} // namespace util } // namespace util
} // namespace cx } // namespace cx

View File

@@ -353,6 +353,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
const auto fn = cx::util::full_name( const auto fn = cx::util::full_name(
resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false); resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false);
auto t_ns = util::split(fn, "::"); auto t_ns = util::split(fn, "::");
auto t_name = t_ns.back();
t_ns.pop_back(); t_ns.pop_back();
const auto &t_raw = resolve_alias(cppast::remove_cv(t_)); const auto &t_raw = resolve_alias(cppast::remove_cv(t_));
@@ -465,7 +466,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
found = find_relationships(args[0u].type().value(), relationships, found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kDependency); relationship_t::kDependency);
} }
else if (ctx.config().should_include(fn)) { else if (ctx.config().should_include(t_ns, t_name)) {
LOG_DBG("User defined template instantiation: {} | {}", LOG_DBG("User defined template instantiation: {} | {}",
cppast::to_string(t_), cppast::to_string(t_.canonical())); cppast::to_string(t_), cppast::to_string(t_.canonical()));

View File

@@ -80,7 +80,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e)
.value(); .value();
m.from = cx::util::ns(caller) + "::" + caller.name(); m.from = cx::util::ns(caller) + "::" + caller.name();
if (!ctx.config().should_include(m.from)) if (!ctx.config().should_include(
util::split(cx::util::ns(caller), "::"), caller.name()))
continue; continue;
if (caller.kind() == cpp_entity_kind::function_t) if (caller.kind() == cpp_entity_kind::function_t)
@@ -96,7 +97,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e)
if (callee.kind() == cpp_entity_kind::function_t) if (callee.kind() == cpp_entity_kind::function_t)
m.to += "()"; m.to += "()";
if (!ctx.config().should_include(m.to)) if (!ctx.config().should_include(
util::split(cx::util::ns(callee), "::"), callee.name()))
continue; continue;
m.to_usr = type_safe::get(function_call.get_callee_method_id()); m.to_usr = type_safe::get(function_call.get_callee_method_id());

View File

@@ -44,22 +44,21 @@ std::vector<std::string> split(std::string str, std::string delimiter)
{ {
std::vector<std::string> result; std::vector<std::string> result;
while (str.size()) { if (!contains(str, delimiter))
int index = str.find(delimiter);
if (index != std::string::npos) {
result.push_back(str.substr(0, index));
str = str.substr(index + delimiter.size());
if (str.size() == 0)
result.push_back(str);
}
else {
result.push_back(str);
str = "";
}
}
if (result.empty())
result.push_back(str); result.push_back(str);
else
while (str.size()) {
int index = str.find(delimiter);
if (index != std::string::npos) {
result.push_back(str.substr(0, index));
str = str.substr(index + delimiter.size());
}
else {
if (!str.empty())
result.push_back(str);
str = "";
}
}
return result; return result;
} }

View File

@@ -22,6 +22,7 @@
#include <algorithm> #include <algorithm>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <type_traits>
#include <vector> #include <vector>
namespace clanguml { namespace clanguml {
@@ -190,6 +191,9 @@ bool contains(const T &container, const E &element)
[&element](const auto &e) { return *e == *element; }) != [&element](const auto &e) { return *e == *element; }) !=
container.end(); container.end();
} }
else if constexpr (std::is_same_v<std::remove_cv_t<T>, std::string>) {
return container.find(element) != std::string::npos;
}
else { else {
return std::find(container.begin(), container.end(), element) != return std::find(container.begin(), container.end(), element) !=
container.end(); container.end();

View File

@@ -30,8 +30,8 @@ TEST_CASE("t00002", "[test-case][class]")
REQUIRE(diagram->exclude().namespaces.size() == 0); REQUIRE(diagram->exclude().namespaces.size() == 0);
REQUIRE(diagram->should_include("clanguml::t00002::A")); REQUIRE(diagram->should_include({"clanguml", "t00002"}, "A"));
REQUIRE(!diagram->should_include("std::vector")); REQUIRE(!diagram->should_include({"std"}, "vector"));
auto model = generate_class_diagram(db, diagram); auto model = generate_class_diagram(db, diagram);

View File

@@ -39,12 +39,23 @@ TEST_CASE("t00014", "[test-case][class]")
REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string"));
REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "float")); REQUIRE_THAT(puml, IsClassTemplate("AString", "float"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "int"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "std::string"));
REQUIRE_THAT( REQUIRE_THAT(
puml, !IsClassTemplate("std::std::function", "void(T...,int),int)")); puml, !IsClassTemplate("std::std::function", "void(T...,int),int)"));
REQUIRE_THAT(puml, IsInstantiation(_A("A<T,P>"), _A("A<T,std::string>"))); REQUIRE_THAT(puml, IsInstantiation(_A("A<T,P>"), _A("A<T,std::string>")));
REQUIRE_THAT( REQUIRE_THAT(
puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<float>"))); puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<float>")));
REQUIRE_THAT(
puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<int>")));
REQUIRE_THAT(
puml, !IsInstantiation(_A("AString<int>"), _A("AString<int>")));
REQUIRE_THAT(puml,
IsInstantiation(_A("A<T,std::string>"), _A("AString<std::string>")));
REQUIRE_THAT(puml,
!IsInstantiation(
_A("AString<std::string>"), _A("AString<std::string>")));
REQUIRE_THAT( REQUIRE_THAT(
puml, IsAggregation(_A("R"), _A("A<bool,std::string>"), "-boolstring")); puml, IsAggregation(_A("R"), _A("A<bool,std::string>"), "-boolstring"));
REQUIRE_THAT( REQUIRE_THAT(

View File

@@ -32,6 +32,8 @@ TEST_CASE("Test split", "[unit-test]")
CHECK(split("ABCD", " ") == C{"ABCD"}); CHECK(split("ABCD", " ") == C{"ABCD"});
CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"}); CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"});
CHECK(split("std::vector::detail::", "::") == C{"std", "vector", "detail"});
} }
TEST_CASE("Test ns_relative", "[unit-test]") TEST_CASE("Test ns_relative", "[unit-test]")