Fixed template instantiation matching
This commit is contained in:
@@ -105,7 +105,7 @@ std::string class_::full_name_no_ns() const
|
||||
|
||||
ostr << name();
|
||||
|
||||
render_template_params(ostr);
|
||||
render_template_params(ostr, false);
|
||||
|
||||
return ostr.str();
|
||||
}
|
||||
@@ -118,7 +118,7 @@ std::string class_::full_name(bool relative) const
|
||||
std::ostringstream ostr;
|
||||
|
||||
ostr << name_and_ns();
|
||||
render_template_params(ostr);
|
||||
render_template_params(ostr, relative);
|
||||
|
||||
std::string res;
|
||||
|
||||
@@ -134,7 +134,7 @@ std::string class_::full_name(bool relative) const
|
||||
}
|
||||
|
||||
std::ostringstream &class_::render_template_params(
|
||||
std::ostringstream &ostr) const
|
||||
std::ostringstream &ostr, bool relative) const
|
||||
{
|
||||
using clanguml::common::model::namespace_;
|
||||
|
||||
@@ -144,8 +144,8 @@ std::ostringstream &class_::render_template_params(
|
||||
|
||||
std::transform(templates_.cbegin(), templates_.cend(),
|
||||
std::back_inserter(tnames),
|
||||
[ns = using_namespace()](
|
||||
const auto &tmplt) { return tmplt.to_string(ns); });
|
||||
[ns = using_namespace(), relative](
|
||||
const auto &tmplt) { return tmplt.to_string(ns, relative); });
|
||||
|
||||
ostr << fmt::format("<{}>", fmt::join(tnames, ","));
|
||||
}
|
||||
@@ -160,4 +160,5 @@ bool class_::is_abstract() const
|
||||
return std::any_of(methods_.begin(), methods_.end(),
|
||||
[](const auto &method) { return method.is_pure_virtual(); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -79,8 +79,13 @@ public:
|
||||
|
||||
void is_alias(bool alias) { is_alias_ = alias; }
|
||||
|
||||
void find_relationships(
|
||||
std::vector<std::pair<std::string, common::model::relationship_t>>
|
||||
&nested_relationships);
|
||||
|
||||
private:
|
||||
std::ostringstream &render_template_params(std::ostringstream &ostr) const;
|
||||
std::ostringstream &render_template_params(
|
||||
std::ostringstream &ostr, bool relative) const;
|
||||
|
||||
bool is_struct_{false};
|
||||
bool is_template_{false};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "template_parameter.h"
|
||||
#include "common/model/enums.h"
|
||||
#include <common/model/namespace.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -81,6 +82,11 @@ void template_parameter::add_template_param(template_parameter &&ct)
|
||||
template_params_.emplace_back(std::move(ct));
|
||||
}
|
||||
|
||||
void template_parameter::add_template_param(const template_parameter &ct)
|
||||
{
|
||||
template_params_.push_back(ct);
|
||||
}
|
||||
|
||||
const std::vector<template_parameter> &
|
||||
template_parameter::template_params() const
|
||||
{
|
||||
@@ -113,28 +119,37 @@ bool operator!=(const template_parameter &l, const template_parameter &r)
|
||||
}
|
||||
|
||||
std::string template_parameter::to_string(
|
||||
const clanguml::common::model::namespace_ &using_namespace) const
|
||||
const clanguml::common::model::namespace_ &using_namespace,
|
||||
bool relative) const
|
||||
{
|
||||
using clanguml::common::model::namespace_;
|
||||
|
||||
std::string res;
|
||||
if (!type().empty())
|
||||
res += namespace_{type()}.relative_to(using_namespace).to_string();
|
||||
|
||||
// Render nested template params
|
||||
if (!template_params_.empty()) {
|
||||
std::vector<std::string> params;
|
||||
for (const auto &template_param : template_params_) {
|
||||
params.push_back(template_param.to_string(using_namespace));
|
||||
}
|
||||
|
||||
res += fmt::format("<{}>", fmt::join(params, ","));
|
||||
if (!type().empty()) {
|
||||
if (!relative)
|
||||
res += namespace_{type()}.to_string();
|
||||
else
|
||||
res += namespace_{type()}.relative_to(using_namespace).to_string();
|
||||
}
|
||||
|
||||
if (!name().empty()) {
|
||||
if (!type().empty())
|
||||
res += " ";
|
||||
res += namespace_{name()}.relative_to(using_namespace).to_string();
|
||||
if (!relative)
|
||||
res += namespace_{name()}.to_string();
|
||||
else
|
||||
res += namespace_{name()}.relative_to(using_namespace).to_string();
|
||||
}
|
||||
|
||||
// Render nested template params
|
||||
if (!template_params_.empty()) {
|
||||
std::vector<std::string> params;
|
||||
for (const auto &template_param : template_params_) {
|
||||
params.push_back(
|
||||
template_param.to_string(using_namespace, relative));
|
||||
}
|
||||
|
||||
res += fmt::format("<{}>", fmt::join(params, ","));
|
||||
}
|
||||
|
||||
if (!default_value().empty()) {
|
||||
@@ -142,7 +157,38 @@ std::string template_parameter::to_string(
|
||||
res += default_value();
|
||||
}
|
||||
|
||||
// TODO: Refactor this to external configurable class
|
||||
util::replace_all(res, "std::basic_string<char>", "std::string");
|
||||
util::replace_all(res, "std::basic_string<wchar_t>", "std::wstring");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void template_parameter::find_nested_relationships(
|
||||
std::vector<std::pair<std::string, common::model::relationship_t>>
|
||||
&nested_relationships,
|
||||
common::model::relationship_t hint,
|
||||
std::function<bool(const std::string &full_name)> condition) const
|
||||
{
|
||||
// If this type argument should be included in the relationship
|
||||
// just add it and skip recursion (e.g. this is a user defined type)
|
||||
if (condition(name())) {
|
||||
nested_relationships.push_back({to_string({}, false), hint});
|
||||
}
|
||||
// Otherwise (e.g. this is a std::shared_ptr) and we're actually
|
||||
// interested what is stored inside it
|
||||
else {
|
||||
for (const auto &template_argument : template_params()) {
|
||||
if (condition(template_argument.name())) {
|
||||
nested_relationships.push_back(
|
||||
{template_argument.to_string({}, false), hint});
|
||||
}
|
||||
else {
|
||||
template_argument.find_nested_relationships(
|
||||
nested_relationships, hint, condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "common/model/enums.h"
|
||||
#include "common/model/namespace.h"
|
||||
|
||||
#include <string>
|
||||
@@ -24,10 +25,10 @@
|
||||
|
||||
namespace clanguml::class_diagram::model {
|
||||
|
||||
/// @brief Represents template parameter or template parameter instantiation
|
||||
/// @brief Represents template parameter or template argument
|
||||
///
|
||||
/// This class can represent both template parameter and template parameter
|
||||
/// instantiation, including variadic parameters and instantiations with
|
||||
/// This class can represent both template parameter and template arguments,
|
||||
/// including variadic parameters and instantiations with
|
||||
/// nested templates
|
||||
class template_parameter {
|
||||
public:
|
||||
@@ -35,6 +36,8 @@ public:
|
||||
const std::string &name = "", const std::string &default_value = "",
|
||||
bool is_variadic = false);
|
||||
|
||||
template_parameter(const template_parameter &right) = default;
|
||||
|
||||
void set_type(const std::string &type);
|
||||
std::string type() const;
|
||||
|
||||
@@ -72,12 +75,21 @@ public:
|
||||
}
|
||||
|
||||
std::string to_string(
|
||||
const clanguml::common::model::namespace_ &using_namespace) const;
|
||||
const clanguml::common::model::namespace_ &using_namespace,
|
||||
bool relative) const;
|
||||
|
||||
void add_template_param(template_parameter &&ct);
|
||||
|
||||
void add_template_param(const template_parameter &ct);
|
||||
|
||||
const std::vector<template_parameter> &template_params() const;
|
||||
|
||||
void find_nested_relationships(
|
||||
std::vector<std::pair<std::string, common::model::relationship_t>>
|
||||
&nested_relationships,
|
||||
common::model::relationship_t hint,
|
||||
std::function<bool(const std::string &full_name)> condition) const;
|
||||
|
||||
private:
|
||||
/// Represents the type of non-type template parameters
|
||||
/// e.g. 'int'
|
||||
|
||||
@@ -746,7 +746,7 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
|
||||
mv.type().kind() == cppast::cpp_type_kind::reference_t)
|
||||
relationship_type = relationship_t::kAssociation;
|
||||
else
|
||||
relationship_type = relationship_t::kAggregation;
|
||||
relationship_type = nested_relationship_hint;
|
||||
|
||||
relationship rr{relationship_type, tinst.full_name(),
|
||||
detail::cpp_access_specifier_to_access(as), mv.name()};
|
||||
@@ -780,8 +780,20 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
|
||||
|
||||
ctx.diagram().add_class(std::move(tinst_ptr));
|
||||
}
|
||||
else if (!nested_relationships.empty()) {
|
||||
for (const auto &rel : nested_relationships) {
|
||||
|
||||
found_relationships_t nested_relationships2;
|
||||
if (!ctx.diagram().should_include(tinst.get_namespace(), tinst.name()))
|
||||
for (const auto &template_argument : tinst.templates()) {
|
||||
template_argument.find_nested_relationships(nested_relationships2,
|
||||
relationship_type,
|
||||
[&d = ctx.diagram()](const std::string &full_name) {
|
||||
auto [ns, name] = cx::util::split_ns(full_name);
|
||||
return d.should_include(ns, name);
|
||||
});
|
||||
}
|
||||
|
||||
if (!nested_relationships2.empty()) {
|
||||
for (const auto &rel : nested_relationships2) {
|
||||
relationship nested_relationship{std::get<1>(rel), std::get<0>(rel),
|
||||
detail::cpp_access_specifier_to_access(as), mv.name()};
|
||||
nested_relationship.set_style(m.style_spec());
|
||||
@@ -1581,7 +1593,7 @@ bool translation_unit_visitor::find_relationships_in_unexposed_template_params(
|
||||
{
|
||||
bool found{false};
|
||||
LOG_DBG("Finding relationships in user defined type: {}",
|
||||
ct.to_string(ctx.config().using_namespace()));
|
||||
ct.to_string(ctx.config().using_namespace(), false));
|
||||
|
||||
auto type_with_namespace = ctx.get_name_with_namespace(ct.type());
|
||||
|
||||
@@ -1610,14 +1622,18 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
relationship_t nested_relationship_hint,
|
||||
std::optional<clanguml::class_diagram::model::class_ *> parent)
|
||||
{
|
||||
//
|
||||
// Create class_ instance to hold the template instantiation
|
||||
//
|
||||
auto tinst_ptr = std::make_unique<class_>(ctx.config().using_namespace());
|
||||
auto &tinst = *tinst_ptr;
|
||||
std::string full_template_name;
|
||||
|
||||
auto tr_declaration = cppast::to_string(t);
|
||||
|
||||
//
|
||||
// If this is an alias - resolve the alias target
|
||||
//
|
||||
const auto &unaliased =
|
||||
static_cast<const cppast::cpp_template_instantiation_type &>(
|
||||
resolve_alias(t));
|
||||
@@ -1625,16 +1641,20 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
|
||||
bool t_is_alias = t_unaliased_declaration != tr_declaration;
|
||||
|
||||
//
|
||||
// Here we'll hold the template base params to replace with the instantiated
|
||||
// values
|
||||
//
|
||||
std::deque<std::tuple<std::string, int, bool>> template_base_params{};
|
||||
|
||||
tinst.set_namespace(ctx.get_namespace());
|
||||
|
||||
auto tinst_full_name = cppast::to_string(t);
|
||||
|
||||
//
|
||||
// Typically, every template instantiation should have a primary_template()
|
||||
// which should also be generated here if it doesn't exist yet in the model
|
||||
//
|
||||
if (t.primary_template().get(ctx.entity_index()).size()) {
|
||||
auto size = t.primary_template().get(ctx.entity_index()).size();
|
||||
(void)size;
|
||||
@@ -1650,7 +1670,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
|
||||
LOG_DBG("Building template instantiation for {}", full_template_name);
|
||||
|
||||
//
|
||||
// Extract namespace from base template name
|
||||
//
|
||||
const auto [ns, name] = cx::util::split_ns(tinst_full_name);
|
||||
tinst.set_name(name);
|
||||
if (ns.is_empty())
|
||||
@@ -1661,6 +1683,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
tinst.is_template_instantiation(true);
|
||||
tinst.is_alias(t_is_alias);
|
||||
|
||||
//
|
||||
// Process exposed template arguments - if any
|
||||
//
|
||||
if (t.arguments_exposed()) {
|
||||
auto arg_index = 0U;
|
||||
// We can figure this only when we encounter variadic param in
|
||||
@@ -1690,7 +1715,10 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
ct);
|
||||
}
|
||||
|
||||
LOG_DBG("Adding template argument '{}'", ct.name());
|
||||
LOG_DBG("Adding template argument '{}'",
|
||||
ct.to_string(ctx.config().using_namespace(), false));
|
||||
|
||||
// assert(!ct.name().empty() || !ct.type().empty());
|
||||
|
||||
tinst.add_template(std::move(ct));
|
||||
}
|
||||
@@ -1702,6 +1730,7 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
auto fn = cx::util::full_name(tt, ctx.entity_index(), false);
|
||||
fn = util::split(fn, "<")[0];
|
||||
|
||||
// TODO: Refactor template instantiation search to a separate method
|
||||
std::string destination;
|
||||
if (ctx.has_type_alias(fn)) {
|
||||
// If this is a template alias - set the instantiation
|
||||
@@ -1715,9 +1744,12 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
// First try to find the best match for this template in partially
|
||||
// specialized templates
|
||||
for (const auto c : ctx.diagram().classes()) {
|
||||
if (c->name_and_ns() == full_template_name &&
|
||||
std::string left = c->name_and_ns();
|
||||
auto left_count = c->templates().size();
|
||||
auto right_count = tinst.templates().size();
|
||||
if (c != tinst && left == full_template_name &&
|
||||
// TODO: handle variadic templates
|
||||
c->templates().size() == tinst.templates().size()) {
|
||||
left_count == right_count) {
|
||||
int tmp_match = 0;
|
||||
for (int i = 0; i < tinst.templates().size(); i++) {
|
||||
const auto &tinst_i = tinst.templates().at(i);
|
||||
@@ -1771,11 +1803,12 @@ bool translation_unit_visitor::build_template_instantiation_add_base_classes(
|
||||
}
|
||||
|
||||
if (add_template_argument_as_base_class) {
|
||||
LOG_DBG("Adding template argument as base class '{}'", ct.name());
|
||||
LOG_DBG("Adding template argument as base class '{}'",
|
||||
ct.to_string({}, false));
|
||||
|
||||
class_parent cp;
|
||||
cp.set_access(access_t::kPublic);
|
||||
cp.set_name(ct.name());
|
||||
cp.set_name(ct.to_string({}, false));
|
||||
|
||||
tinst.add_parent(std::move(cp));
|
||||
}
|
||||
@@ -1806,31 +1839,42 @@ void translation_unit_visitor::
|
||||
template_parameter &ct, found_relationships_t &nested_relationships,
|
||||
common::model::relationship_t relationship_hint)
|
||||
{
|
||||
ct.set_name(cppast::to_string(targ.type().value()));
|
||||
|
||||
LOG_DBG("Template argument is a type {}", ct.name());
|
||||
auto fn = cx::util::full_name(
|
||||
auto full_name = cx::util::full_name(
|
||||
cppast::remove_cv(cx::util::unreferenced(targ.type().value())),
|
||||
ctx.entity_index(), false);
|
||||
|
||||
auto [fn_ns, fn_name] = cx::util::split_ns(fn);
|
||||
auto [fn_ns, fn_name] = cx::util::split_ns(full_name);
|
||||
auto template_argument_kind = targ.type().value().kind();
|
||||
|
||||
if (template_argument_kind == cppast::cpp_type_kind::unexposed_t) {
|
||||
// Here we're on our own - just make a best guess
|
||||
if (!fn.empty() && !util::contains(fn, "<") &&
|
||||
!util::contains(fn, ":") && std::isupper(fn.at(0)))
|
||||
if (!full_name.empty() && !util::contains(full_name, "<") &&
|
||||
!util::contains(full_name, ":") && std::isupper(full_name.at(0)))
|
||||
ct.is_template_parameter(true);
|
||||
else
|
||||
ct.is_template_parameter(false);
|
||||
|
||||
ct.set_name(full_name);
|
||||
}
|
||||
else if (template_argument_kind ==
|
||||
cppast::cpp_type_kind::template_parameter_t) {
|
||||
ct.is_template_parameter(true);
|
||||
ct.set_name(full_name);
|
||||
}
|
||||
else if (template_argument_kind == cppast::cpp_type_kind::builtin_t) {
|
||||
ct.is_template_parameter(false);
|
||||
ct.set_type(full_name);
|
||||
}
|
||||
else if (template_argument_kind ==
|
||||
cppast::cpp_type_kind::template_instantiation_t) {
|
||||
|
||||
// Check if this template should be simplified (e.g. system
|
||||
// template aliases such as std:basic_string<char> should be simply
|
||||
// std::string
|
||||
if (simplify_system_template(ct, full_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &nested_template_parameter =
|
||||
static_cast<const cppast::cpp_template_instantiation_type &>(
|
||||
targ.type().value());
|
||||
@@ -1842,14 +1886,23 @@ void translation_unit_visitor::
|
||||
auto [tinst_ns, tinst_name] =
|
||||
cx::util::split_ns(tinst.full_name(false));
|
||||
|
||||
auto nested_tinst = build_template_instantiation(
|
||||
nested_template_parameter, nested_relationships, relationship_hint,
|
||||
ctx.diagram().should_include(tinst_ns, tinst_name)
|
||||
? std::make_optional(&tinst)
|
||||
: parent);
|
||||
ct.set_name(full_name.substr(0, full_name.find('<')));
|
||||
|
||||
assert(!ct.name().empty());
|
||||
|
||||
found_relationships_t sub_nested_relationships;
|
||||
auto nested_tinst =
|
||||
build_template_instantiation(nested_template_parameter,
|
||||
sub_nested_relationships, relationship_hint,
|
||||
ctx.diagram().should_include(tinst_ns, tinst_name)
|
||||
? std::make_optional(&tinst)
|
||||
: parent);
|
||||
|
||||
assert(nested_tinst);
|
||||
|
||||
for (const auto &t : nested_tinst->templates())
|
||||
ct.add_template_param(t);
|
||||
|
||||
relationship tinst_dependency{
|
||||
relationship_t::kDependency, nested_tinst->full_name()};
|
||||
|
||||
@@ -1863,6 +1916,10 @@ void translation_unit_visitor::
|
||||
{nested_tinst->full_name(false), relationship_hint});
|
||||
ctx.diagram().add_class(std::move(nested_tinst));
|
||||
}
|
||||
else {
|
||||
if (!sub_nested_relationships.empty())
|
||||
nested_relationships.push_back(sub_nested_relationships[0]);
|
||||
}
|
||||
|
||||
if (ctx.diagram().should_include(tinst_ns, tinst_name)
|
||||
// TODO: check why this breaks t00033:
|
||||
@@ -1871,7 +1928,7 @@ void translation_unit_visitor::
|
||||
) {
|
||||
LOG_DBG("Creating nested template dependency to template "
|
||||
"instantiation {}, {} -> {}",
|
||||
fn, tinst.full_name(), tinst_dependency.destination());
|
||||
full_name, tinst.full_name(), tinst_dependency.destination());
|
||||
|
||||
tinst.add_relationship(std::move(tinst_dependency));
|
||||
}
|
||||
@@ -1879,14 +1936,15 @@ void translation_unit_visitor::
|
||||
LOG_DBG("Creating nested template dependency to parent "
|
||||
"template "
|
||||
"instantiation {}, {} -> {}",
|
||||
fn, (*parent)->full_name(), tinst_dependency.destination());
|
||||
full_name, (*parent)->full_name(),
|
||||
tinst_dependency.destination());
|
||||
|
||||
(*parent)->add_relationship(std::move(tinst_dependency));
|
||||
}
|
||||
else {
|
||||
LOG_DBG("No nested template dependency to template "
|
||||
"instantiation: {}, {} -> {}",
|
||||
fn, tinst.full_name(), tinst_dependency.destination());
|
||||
full_name, tinst.full_name(), tinst_dependency.destination());
|
||||
}
|
||||
}
|
||||
else if (template_argument_kind == cppast::cpp_type_kind::user_defined_t) {
|
||||
@@ -1899,6 +1957,8 @@ void translation_unit_visitor::
|
||||
"type {} -> {}",
|
||||
tinst.full_name(), tinst_dependency.destination());
|
||||
|
||||
ct.set_name(full_name);
|
||||
|
||||
if (ctx.diagram().should_include(fn_ns, fn_name)) {
|
||||
tinst.add_relationship(std::move(tinst_dependency));
|
||||
nested_relationships.push_back(
|
||||
@@ -2034,4 +2094,27 @@ const cppast::cpp_type &translation_unit_visitor::resolve_alias_template(
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::simplify_system_template(
|
||||
template_parameter &ct, const std::string &full_name)
|
||||
{
|
||||
if (full_name == "std::basic_string<char>") {
|
||||
ct.set_name("std::string");
|
||||
return true;
|
||||
}
|
||||
else if (full_name == "std::basic_string<wchar_t>") {
|
||||
ct.set_name("std::wstring");
|
||||
return true;
|
||||
}
|
||||
else if (full_name == "std::basic_string<char16_t>") {
|
||||
ct.set_name("std::u16string");
|
||||
return true;
|
||||
}
|
||||
else if (full_name == "std::basic_string<char32_t>") {
|
||||
ct.set_name("std::u32string");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ namespace clanguml::class_diagram::visitor {
|
||||
using found_relationships_t =
|
||||
std::vector<std::pair<std::string, common::model::relationship_t>>;
|
||||
|
||||
// class nested_template_relationships {
|
||||
//
|
||||
// std::vector<std::unique_ptr<nested_template_relationships>> children;
|
||||
//};
|
||||
|
||||
class translation_unit_visitor {
|
||||
public:
|
||||
translation_unit_visitor(cppast::cpp_entity_index &idx,
|
||||
@@ -232,5 +237,7 @@ private:
|
||||
|
||||
// ctx allows to track current visitor context, e.g. current namespace
|
||||
translation_unit_context ctx;
|
||||
bool simplify_system_template(
|
||||
model::template_parameter ¶meter, const std::string &basicString);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <algorithm>
|
||||
//#include <algorithm>
|
||||
#include <functional>
|
||||
#include <ios>
|
||||
//#include <ios>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
//#include <numeric>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
//#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -44,6 +44,16 @@ TEST_CASE("t00019", "[test-case][class]")
|
||||
IsBaseClass(
|
||||
_A("Layer2<Layer3<Base>>"), _A("Layer1<Layer2<Layer3<Base>>>")));
|
||||
|
||||
REQUIRE_THAT(puml,
|
||||
IsAggregation(_A("A"), _A("Layer1<Layer2<Layer3<Base>>>"), "+layers"));
|
||||
|
||||
REQUIRE_THAT(
|
||||
puml, !IsAggregation(_A("A"), _A("Layer2<Layer3<Base>>"), "+layers"));
|
||||
|
||||
REQUIRE_THAT(puml, !IsAggregation(_A("A"), _A("Layer3<Base>"), "+layers"));
|
||||
|
||||
REQUIRE_THAT(puml, !IsAggregation(_A("A"), _A("Base"), "+layers"));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user