Fixed handling of nested and anonymous classes
This commit is contained in:
@@ -52,6 +52,13 @@ void generator::generate_link(
|
|||||||
ostr << "]]]";
|
ostr << "]]]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string generator::render_name(std::string name) const
|
||||||
|
{
|
||||||
|
util::replace_all(name, "##", "::");
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
void generator::generate_alias(const class_ &c, std::ostream &ostr) const
|
void generator::generate_alias(const class_ &c, std::ostream &ostr) const
|
||||||
{
|
{
|
||||||
std::string class_type{"class"};
|
std::string class_type{"class"};
|
||||||
@@ -64,10 +71,9 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const
|
|||||||
else
|
else
|
||||||
full_name = c.full_name();
|
full_name = c.full_name();
|
||||||
|
|
||||||
if (full_name.empty())
|
assert(!full_name.empty());
|
||||||
full_name = "<<anonymous>>";
|
|
||||||
|
|
||||||
ostr << class_type << " \"" << full_name;
|
ostr << class_type << " \"" << render_name(full_name);
|
||||||
|
|
||||||
ostr << "\" as " << c.alias() << '\n';
|
ostr << "\" as " << c.alias() << '\n';
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ void generator::generate_alias(const enum_ &e, std::ostream &ostr) const
|
|||||||
<< " \"" << e.name();
|
<< " \"" << e.name();
|
||||||
else
|
else
|
||||||
ostr << "enum"
|
ostr << "enum"
|
||||||
<< " \"" << e.full_name();
|
<< " \"" << render_name(e.full_name());
|
||||||
|
|
||||||
ostr << "\" as " << e.alias() << '\n';
|
ostr << "\" as " << e.alias() << '\n';
|
||||||
|
|
||||||
@@ -225,7 +231,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
|
|||||||
ostr << "{static} ";
|
ostr << "{static} ";
|
||||||
|
|
||||||
ostr << plantuml_common::to_plantuml(m.access()) << m.name() << " : "
|
ostr << plantuml_common::to_plantuml(m.access()) << m.name() << " : "
|
||||||
<< uns.relative(m.type());
|
<< render_name(uns.relative(m.type()));
|
||||||
|
|
||||||
if (m_config.generate_links) {
|
if (m_config.generate_links) {
|
||||||
generate_link(ostr, m);
|
generate_link(ostr, m);
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ public:
|
|||||||
void generate_relationships(const package &p, std::ostream &ostr) const;
|
void generate_relationships(const package &p, std::ostream &ostr) const;
|
||||||
|
|
||||||
void generate(std::ostream &ostr) const override;
|
void generate(std::ostream &ostr) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string render_name(std::string name) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
|
|||||||
std::string qualified_name = common::get_qualified_name(*enm);
|
std::string qualified_name = common::get_qualified_name(*enm);
|
||||||
namespace_ ns{qualified_name};
|
namespace_ ns{qualified_name};
|
||||||
ns.pop_back();
|
ns.pop_back();
|
||||||
e.set_name(enm->getNameAsString());
|
|
||||||
|
e.set_name(common::get_tag_name(*enm));
|
||||||
e.set_namespace(ns);
|
e.set_namespace(ns);
|
||||||
e.set_id(common::to_id(*enm));
|
e.set_id(common::to_id(*enm));
|
||||||
set_ast_local_id(enm->getID(), e.id());
|
set_ast_local_id(enm->getID(), e.id());
|
||||||
@@ -358,7 +359,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
|
|||||||
forward_declarations_.erase(id);
|
forward_declarations_.erase(id);
|
||||||
|
|
||||||
if (diagram_.should_include(class_model)) {
|
if (diagram_.should_include(class_model)) {
|
||||||
LOG_DBG("Adding class {} with id {}", class_model.full_name(),
|
LOG_DBG("Adding class {} with id {}", class_model.full_name(false),
|
||||||
class_model.id());
|
class_model.id());
|
||||||
|
|
||||||
diagram_.add_class(std::move(c_ptr));
|
diagram_.add_class(std::move(c_ptr));
|
||||||
@@ -387,7 +388,8 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
|
|||||||
|
|
||||||
namespace_ ns{qualified_name};
|
namespace_ ns{qualified_name};
|
||||||
ns.pop_back();
|
ns.pop_back();
|
||||||
c.set_name(cls->getNameAsString());
|
|
||||||
|
c.set_name(common::get_tag_name(*cls));
|
||||||
c.set_namespace(ns);
|
c.set_namespace(ns);
|
||||||
c.set_id(common::to_id(*cls));
|
c.set_id(common::to_id(*cls));
|
||||||
|
|
||||||
@@ -415,6 +417,7 @@ void translation_unit_visitor::process_class_declaration(
|
|||||||
|
|
||||||
if (cls.getParent()->isRecord()) {
|
if (cls.getParent()->isRecord()) {
|
||||||
process_record_containment(cls, c);
|
process_record_containment(cls, c);
|
||||||
|
c.nested(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.complete(true);
|
c.complete(true);
|
||||||
@@ -1809,10 +1812,6 @@ void translation_unit_visitor::process_field(
|
|||||||
common::to_string(field_type, field_declaration.getASTContext());
|
common::to_string(field_type, field_declaration.getASTContext());
|
||||||
// The field name
|
// The field name
|
||||||
const auto field_name = field_declaration.getNameAsString();
|
const auto field_name = field_declaration.getNameAsString();
|
||||||
// If for any reason clang reports the type as empty string, make sure
|
|
||||||
// it has some default name
|
|
||||||
if (type_name.empty())
|
|
||||||
type_name = "<<anonymous>>";
|
|
||||||
|
|
||||||
class_member field{
|
class_member field{
|
||||||
detail::access_specifier_to_access_t(field_declaration.getAccess()),
|
detail::access_specifier_to_access_t(field_declaration.getAccess()),
|
||||||
|
|||||||
@@ -39,6 +39,35 @@ std::optional<clanguml::common::model::namespace_> get_enclosing_namespace(
|
|||||||
common::get_qualified_name(*namespace_declaration)};
|
common::get_qualified_name(*namespace_declaration)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_tag_name(const clang::TagDecl &declaration)
|
||||||
|
{
|
||||||
|
auto base_name = declaration.getNameAsString();
|
||||||
|
if (base_name.empty()) {
|
||||||
|
base_name =
|
||||||
|
fmt::format("(anonymous_{})", std::to_string(declaration.getID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declaration.getParent() && declaration.getParent()->isRecord()) {
|
||||||
|
// If the record is nested within another record (e.g. class or struct)
|
||||||
|
// we have to maintain a containment namespace in order to ensure
|
||||||
|
// unique names within the diagram
|
||||||
|
std::deque<std::string> record_parent_names;
|
||||||
|
record_parent_names.push_front(base_name);
|
||||||
|
|
||||||
|
auto *cls_parent{declaration.getParent()};
|
||||||
|
while (cls_parent->isRecord()) {
|
||||||
|
auto parent_name =
|
||||||
|
static_cast<const clang::RecordDecl *>(cls_parent)
|
||||||
|
->getNameAsString();
|
||||||
|
record_parent_names.push_front(parent_name);
|
||||||
|
cls_parent = cls_parent->getParent();
|
||||||
|
}
|
||||||
|
return fmt::format("{}", fmt::join(record_parent_names, "##"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base_name;
|
||||||
|
}
|
||||||
|
|
||||||
std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
|
std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
|
||||||
bool try_canonical)
|
bool try_canonical)
|
||||||
{
|
{
|
||||||
@@ -66,6 +95,14 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If for any reason clang reports the type as empty string, make sure
|
||||||
|
// it has some default name
|
||||||
|
if (result.empty())
|
||||||
|
result = "(anonymous)";
|
||||||
|
else if (util::contains(result, "unnamed struct")) {
|
||||||
|
result = common::get_tag_name(*type->getAsTagDecl());
|
||||||
|
}
|
||||||
|
|
||||||
// Remove trailing spaces after commas in template arguments
|
// Remove trailing spaces after commas in template arguments
|
||||||
clanguml::util::replace_all(result, ", ", ",");
|
clanguml::util::replace_all(result, ", ", ",");
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <clang/AST/RecursiveASTVisitor.h>
|
#include <clang/AST/RecursiveASTVisitor.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -30,12 +31,23 @@ class NamespaceDecl;
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace clanguml::common {
|
namespace clanguml::common {
|
||||||
|
std::string get_tag_name(const clang::TagDecl &declaration);
|
||||||
|
|
||||||
template <typename T> std::string get_qualified_name(const T &declaration)
|
template <typename T> std::string get_qualified_name(const T &declaration)
|
||||||
{
|
{
|
||||||
auto qualified_name = declaration.getQualifiedNameAsString();
|
auto qualified_name = declaration.getQualifiedNameAsString();
|
||||||
util::replace_all(qualified_name, "(anonymous namespace)", "");
|
util::replace_all(qualified_name, "(anonymous namespace)", "");
|
||||||
util::replace_all(qualified_name, "::::", "::");
|
util::replace_all(qualified_name, "::::", "::");
|
||||||
|
|
||||||
|
if constexpr (std::is_base_of_v<clang::TagDecl, T>) {
|
||||||
|
auto base_name = get_tag_name(declaration);
|
||||||
|
model::namespace_ ns{qualified_name};
|
||||||
|
ns.pop_back();
|
||||||
|
ns = ns | base_name;
|
||||||
|
|
||||||
|
return ns.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
return qualified_name;
|
return qualified_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ std::atomic_uint64_t diagram_element::m_nextId = 1;
|
|||||||
|
|
||||||
diagram_element::diagram_element()
|
diagram_element::diagram_element()
|
||||||
: id_{0}
|
: id_{0}
|
||||||
|
, nested_{false}
|
||||||
, complete_{false}
|
, complete_{false}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -84,6 +85,10 @@ inja::json diagram_element::context() const
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool diagram_element::is_nested() const { return nested_; }
|
||||||
|
|
||||||
|
void diagram_element::nested(bool nested) { nested_ = nested; }
|
||||||
|
|
||||||
bool diagram_element::complete() const { return complete_; }
|
bool diagram_element::complete() const { return complete_; }
|
||||||
|
|
||||||
void diagram_element::complete(bool completed) { complete_ = completed; }
|
void diagram_element::complete(bool completed) { complete_ = completed; }
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ public:
|
|||||||
|
|
||||||
virtual inja::json context() const;
|
virtual inja::json context() const;
|
||||||
|
|
||||||
|
bool is_nested() const;
|
||||||
|
|
||||||
|
void nested(bool nested);
|
||||||
|
|
||||||
bool complete() const;
|
bool complete() const;
|
||||||
|
|
||||||
void complete(bool completed);
|
void complete(bool completed);
|
||||||
@@ -73,7 +77,7 @@ private:
|
|||||||
id_t id_;
|
id_t id_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::vector<relationship> relationships_;
|
std::vector<relationship> relationships_;
|
||||||
|
bool nested_;
|
||||||
bool complete_;
|
bool complete_;
|
||||||
|
|
||||||
static std::atomic_uint64_t m_nextId;
|
static std::atomic_uint64_t m_nextId;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
namespace clanguml {
|
namespace clanguml {
|
||||||
namespace t00004 {
|
namespace t00004 {
|
||||||
|
|
||||||
|
class B {
|
||||||
|
public:
|
||||||
|
enum AA { AA_1, AA_2, AA_3 };
|
||||||
|
};
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
public:
|
public:
|
||||||
void foo() const { }
|
void foo() const { }
|
||||||
@@ -15,5 +20,6 @@ public:
|
|||||||
|
|
||||||
void foo2() const { }
|
void foo2() const { }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ TEST_CASE("t00004", "[test-case][class]")
|
|||||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||||
REQUIRE_THAT(puml, IsClass(_A("AA")));
|
REQUIRE_THAT(puml, IsClass(_A("A::AA")));
|
||||||
REQUIRE_THAT(puml, IsClass(_A("AAA")));
|
REQUIRE_THAT(puml, IsClass(_A("A::AA::AAA")));
|
||||||
REQUIRE_THAT(puml, IsEnum(_A("Lights")));
|
REQUIRE_THAT(puml, IsEnum(_A("B::AA")));
|
||||||
REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA")));
|
REQUIRE_THAT(puml, IsEnum(_A("A::AA::Lights")));
|
||||||
REQUIRE_THAT(puml, IsInnerClass(_A("AA"), _A("AAA")));
|
REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("A::AA")));
|
||||||
REQUIRE_THAT(puml, IsInnerClass(_A("AA"), _A("Lights")));
|
REQUIRE_THAT(puml, IsInnerClass(_A("A::AA"), _A("A::AA::AAA")));
|
||||||
|
REQUIRE_THAT(puml, IsInnerClass(_A("A::AA"), _A("A::AA::Lights")));
|
||||||
|
|
||||||
REQUIRE_THAT(puml, (IsMethod<Public, Const>("foo")));
|
REQUIRE_THAT(puml, (IsMethod<Public, Const>("foo")));
|
||||||
REQUIRE_THAT(puml, (IsMethod<Public, Const>("foo2")));
|
REQUIRE_THAT(puml, (IsMethod<Public, Const>("foo2")));
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ struct ST {
|
|||||||
double y;
|
double y;
|
||||||
double z;
|
double z;
|
||||||
} dimensions;
|
} dimensions;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
double c;
|
||||||
|
double h;
|
||||||
|
} units;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct A {
|
struct A {
|
||||||
@@ -17,6 +22,9 @@ struct A {
|
|||||||
st.dimensions.x = 1;
|
st.dimensions.x = 1;
|
||||||
st.dimensions.y = 1;
|
st.dimensions.y = 1;
|
||||||
st.dimensions.z = 1;
|
st.dimensions.z = 1;
|
||||||
|
|
||||||
|
st.units.c = 1;
|
||||||
|
st.units.h = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ST st;
|
ST st;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ TEST_CASE("t00037", "[test-case][class]")
|
|||||||
|
|
||||||
REQUIRE_THAT(puml, IsClass(_A("ST")));
|
REQUIRE_THAT(puml, IsClass(_A("ST")));
|
||||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||||
REQUIRE_THAT(puml, IsInnerClass(_A("ST"), _A("<<anonymous>>")));
|
REQUIRE_THAT(puml, IsClass(_A("ST::\\(anonymous_\\d+\\)")));
|
||||||
|
|
||||||
save_puml(
|
save_puml(
|
||||||
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
|
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
|
|||||||
Reference in New Issue
Block a user