Added generation of package diagrams from directory structure instead of namespaces
This commit is contained in:
@@ -251,10 +251,10 @@ bool diagram::add_class_fs(
|
||||
const auto base_name = c->name();
|
||||
const auto full_name = c->full_name(false);
|
||||
const auto id = c->id();
|
||||
auto &cc = *c;
|
||||
auto cc = std::ref(*c);
|
||||
|
||||
if (add_element(parent_path, std::move(c))) {
|
||||
classes_.push_back(std::ref(cc));
|
||||
classes_.push_back(cc);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -469,6 +469,13 @@ template <> struct convert<package_diagram> {
|
||||
return false;
|
||||
|
||||
get_option(node, rhs.layout);
|
||||
get_option(node, rhs.relative_to);
|
||||
get_option(node, rhs.package_type);
|
||||
|
||||
// Ensure relative_to has a value
|
||||
if (!rhs.relative_to.has_value)
|
||||
rhs.relative_to.set(
|
||||
std::filesystem::current_path().lexically_normal());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ diagram::packages() const
|
||||
|
||||
void diagram::add_package(std::unique_ptr<common::model::package> &&p)
|
||||
{
|
||||
LOG_DBG("Adding package: {}, {}", p->name(), p->full_name(true));
|
||||
LOG_DBG(
|
||||
"Adding package: {}, {}, [{}]", p->name(), p->full_name(true), p->id());
|
||||
|
||||
auto ns = p->get_relative_namespace();
|
||||
|
||||
@@ -45,6 +46,31 @@ void diagram::add_package(std::unique_ptr<common::model::package> &&p)
|
||||
add_element(ns, std::move(p));
|
||||
}
|
||||
|
||||
void diagram::add_package_fs(const common::model::path &parent_path,
|
||||
std::unique_ptr<common::model::package> &&p)
|
||||
{
|
||||
LOG_DBG("Adding package: {}, {}", p->name(), p->full_name(true));
|
||||
|
||||
// Make sure all parent directories are already packages in the
|
||||
// model
|
||||
for (auto it = parent_path.begin(); it != parent_path.end(); it++) {
|
||||
auto pkg =
|
||||
std::make_unique<common::model::package>(p->using_namespace());
|
||||
pkg->set_name(*it);
|
||||
auto ns = common::model::path(parent_path.begin(), it);
|
||||
// ns.pop_back();
|
||||
pkg->set_namespace(ns);
|
||||
pkg->set_id(common::to_id(pkg->full_name(false)));
|
||||
|
||||
add_package_fs(ns, std::move(pkg));
|
||||
}
|
||||
|
||||
auto pp = std::ref(*p);
|
||||
if (add_element(parent_path, std::move(p))) {
|
||||
packages_.emplace_back(pp);
|
||||
}
|
||||
}
|
||||
|
||||
common::optional_ref<common::model::package> diagram::get_package(
|
||||
const std::string &name) const
|
||||
{
|
||||
@@ -79,6 +105,8 @@ common::optional_ref<clanguml::common::model::diagram_element> diagram::get(
|
||||
common::optional_ref<clanguml::common::model::diagram_element> diagram::get(
|
||||
const clanguml::common::model::diagram_element::id_t id) const
|
||||
{
|
||||
LOG_DBG("Looking for package with id {}", id);
|
||||
|
||||
return get_package(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,14 +48,17 @@ public:
|
||||
common::optional_ref<clanguml::common::model::diagram_element> get(
|
||||
clanguml::common::model::diagram_element::id_t id) const override;
|
||||
|
||||
void add_package(std::unique_ptr<common::model::package> &&p);
|
||||
|
||||
common::optional_ref<clanguml::common::model::package> get_package(
|
||||
const std::string &name) const;
|
||||
|
||||
common::optional_ref<common::model::package> get_package(
|
||||
clanguml::common::model::diagram_element::id_t id) const;
|
||||
|
||||
void add_package(std::unique_ptr<common::model::package> &&p);
|
||||
|
||||
void add_package_fs(const common::model::path &parent_path,
|
||||
std::unique_ptr<common::model::package> &&p);
|
||||
|
||||
std::string to_alias(
|
||||
clanguml::common::model::diagram_element::id_t /*id*/) const;
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns)
|
||||
{
|
||||
assert(ns != nullptr);
|
||||
|
||||
if (config().package_type() == config::package_type_t::kDirectory)
|
||||
return true;
|
||||
|
||||
if (ns->isAnonymousNamespace() || ns->isInline())
|
||||
return true;
|
||||
|
||||
@@ -144,24 +147,91 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
|
||||
|
||||
return true;
|
||||
}
|
||||
void translation_unit_visitor::add_relationships(
|
||||
clang::DeclContext *cls, found_relationships_t &relationships)
|
||||
{
|
||||
int64_t current_package_id{0};
|
||||
|
||||
const auto *namespace_context = cls->getEnclosingNamespaceContext();
|
||||
if (namespace_context != nullptr && namespace_context->isNamespace()) {
|
||||
current_package_id =
|
||||
common::to_id(*llvm::cast<clang::NamespaceDecl>(namespace_context));
|
||||
bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *decl)
|
||||
{
|
||||
assert(decl != nullptr);
|
||||
|
||||
// Skip system headers
|
||||
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
|
||||
return true;
|
||||
|
||||
found_relationships_t relationships;
|
||||
|
||||
if (decl->isCompleteDefinition()) {
|
||||
process_record_children(*decl, relationships);
|
||||
add_relationships(decl, relationships);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *decl)
|
||||
{
|
||||
assert(decl != nullptr);
|
||||
|
||||
// Skip system headers
|
||||
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
|
||||
return true;
|
||||
|
||||
found_relationships_t relationships;
|
||||
|
||||
if (decl->isCompleteDefinition()) {
|
||||
add_relationships(decl, relationships);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::VisitClassTemplateDecl(
|
||||
clang::ClassTemplateDecl *decl)
|
||||
{
|
||||
assert(decl != nullptr);
|
||||
|
||||
// Skip system headers
|
||||
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
|
||||
return true;
|
||||
|
||||
found_relationships_t relationships;
|
||||
|
||||
process_class_declaration(*decl->getTemplatedDecl(), relationships);
|
||||
add_relationships(decl, relationships);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void translation_unit_visitor::add_relationships(
|
||||
clang::Decl *cls, found_relationships_t &relationships)
|
||||
{
|
||||
// If this diagram has directory packages, first make sure that the
|
||||
// package for current directory is already in the model
|
||||
if (config().package_type() == config::package_type_t::kDirectory) {
|
||||
auto file = source_manager().getFilename(cls->getLocation()).str();
|
||||
auto relative_file =
|
||||
util::path_to_url(config().make_path_relative(file));
|
||||
|
||||
common::model::path parent_path{
|
||||
relative_file, common::model::path_type::kFilesystem};
|
||||
parent_path.pop_back();
|
||||
auto pkg_name = parent_path.name();
|
||||
parent_path.pop_back();
|
||||
|
||||
auto pkg = std::make_unique<common::model::package>(
|
||||
config().using_namespace());
|
||||
|
||||
pkg->set_name(pkg_name);
|
||||
pkg->set_id(get_package_id(cls));
|
||||
|
||||
diagram().add_package_fs(parent_path, std::move(pkg));
|
||||
}
|
||||
|
||||
auto current_package_id = get_package_id(cls);
|
||||
|
||||
if (current_package_id == 0)
|
||||
// These are relationships to a global namespace, and we don't care
|
||||
// about those
|
||||
return;
|
||||
|
||||
assert(current_package_id != 0);
|
||||
|
||||
auto current_package = diagram().get(current_package_id);
|
||||
|
||||
if (current_package) {
|
||||
@@ -175,6 +245,33 @@ void translation_unit_visitor::add_relationships(
|
||||
}
|
||||
}
|
||||
|
||||
common::model::diagram_element::id_t translation_unit_visitor::get_package_id(
|
||||
const clang::Decl *cls)
|
||||
{
|
||||
if (config().package_type() == config::package_type_t::kNamespace) {
|
||||
const auto *namespace_context =
|
||||
cls->getDeclContext()->getEnclosingNamespaceContext();
|
||||
if (namespace_context != nullptr && namespace_context->isNamespace()) {
|
||||
return common::to_id(
|
||||
*llvm::cast<clang::NamespaceDecl>(namespace_context));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
auto file = source_manager()
|
||||
.getFilename(cls->getSourceRange().getBegin())
|
||||
.str();
|
||||
auto relative_file =
|
||||
util::path_to_url(config().make_path_relative(file));
|
||||
common::model::path parent_path{
|
||||
relative_file, common::model::path_type::kFilesystem};
|
||||
parent_path.pop_back();
|
||||
|
||||
return common::to_id(parent_path.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
void translation_unit_visitor::process_class_declaration(
|
||||
const clang::CXXRecordDecl &cls, found_relationships_t &relationships)
|
||||
{
|
||||
@@ -255,6 +352,44 @@ void translation_unit_visitor::process_method(
|
||||
}
|
||||
}
|
||||
|
||||
void translation_unit_visitor::process_record_children(
|
||||
const clang::RecordDecl &cls, found_relationships_t &relationships)
|
||||
{
|
||||
if (const auto *decl_context =
|
||||
clang::dyn_cast_or_null<clang::DeclContext>(&cls);
|
||||
decl_context != nullptr) {
|
||||
// Iterate over class template methods
|
||||
for (auto const *decl_iterator : decl_context->decls()) {
|
||||
auto const *method_template =
|
||||
llvm::dyn_cast_or_null<clang::FunctionTemplateDecl>(
|
||||
decl_iterator);
|
||||
if (method_template == nullptr)
|
||||
continue;
|
||||
|
||||
process_template_method(*method_template, relationships);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over regular class fields
|
||||
for (const auto *field : cls.fields()) {
|
||||
if (field != nullptr)
|
||||
process_field(*field, relationships);
|
||||
}
|
||||
|
||||
// Static fields have to be processed by iterating over variable
|
||||
// declarations
|
||||
for (const auto *decl : cls.decls()) {
|
||||
if (decl->getKind() == clang::Decl::Var) {
|
||||
const clang::VarDecl *variable_declaration{
|
||||
clang::dyn_cast_or_null<clang::VarDecl>(decl)};
|
||||
if ((variable_declaration != nullptr) &&
|
||||
variable_declaration->isStaticDataMember()) {
|
||||
process_static_field(*variable_declaration, relationships);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void translation_unit_visitor::process_template_method(
|
||||
const clang::FunctionTemplateDecl &method,
|
||||
found_relationships_t &relationships)
|
||||
@@ -334,15 +469,22 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type,
|
||||
relationships, relationship_t::kAggregation);
|
||||
}
|
||||
else if (type->isEnumeralType()) {
|
||||
if (const auto *enum_type = type->getAs<clang::EnumType>();
|
||||
enum_type != nullptr) {
|
||||
if (const auto *enum_decl = type->getAs<clang::EnumType>()->getDecl();
|
||||
enum_decl != nullptr) {
|
||||
relationships.emplace_back(
|
||||
common::to_id(*enum_type), relationship_hint);
|
||||
get_package_id(enum_decl), relationship_hint);
|
||||
}
|
||||
}
|
||||
else if (const auto *template_specialization_type =
|
||||
type->getAs<clang::TemplateSpecializationType>()) {
|
||||
if (template_specialization_type != nullptr) {
|
||||
// Add dependency to template declaration
|
||||
relationships.emplace_back(
|
||||
get_package_id(template_specialization_type->getTemplateName()
|
||||
.getAsTemplateDecl()),
|
||||
relationship_hint);
|
||||
|
||||
// Add dependencies to template arguments
|
||||
for (const auto &template_argument :
|
||||
template_specialization_type->template_arguments()) {
|
||||
const auto template_argument_kind = template_argument.getKind();
|
||||
@@ -389,17 +531,29 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type,
|
||||
}
|
||||
}
|
||||
else if (type->isRecordType() && type->getAsCXXRecordDecl()) {
|
||||
if (config().package_type() == config::package_type_t::kNamespace) {
|
||||
const auto *namespace_context =
|
||||
type->getAsCXXRecordDecl()->getEnclosingNamespaceContext();
|
||||
if (namespace_context != nullptr && namespace_context->isNamespace()) {
|
||||
if (namespace_context != nullptr &&
|
||||
namespace_context->isNamespace()) {
|
||||
const auto *namespace_declaration =
|
||||
clang::cast<clang::NamespaceDecl>(namespace_context);
|
||||
|
||||
if (namespace_declaration != nullptr &&
|
||||
diagram().should_include(
|
||||
common::get_qualified_name(*namespace_declaration))) {
|
||||
const auto target_id = common::to_id(
|
||||
*clang::cast<clang::NamespaceDecl>(namespace_context));
|
||||
const auto target_id =
|
||||
get_package_id(type->getAsCXXRecordDecl());
|
||||
relationships.emplace_back(target_id, relationship_hint);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (diagram().should_include(
|
||||
common::get_qualified_name(*type->getAsCXXRecordDecl()))) {
|
||||
const auto target_id =
|
||||
get_package_id(type->getAsCXXRecordDecl());
|
||||
relationships.emplace_back(target_id, relationship_hint);
|
||||
result = true;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,14 @@ public:
|
||||
|
||||
virtual bool VisitNamespaceDecl(clang::NamespaceDecl *ns);
|
||||
|
||||
virtual bool VisitEnumDecl(clang::EnumDecl *decl);
|
||||
|
||||
virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *cls);
|
||||
|
||||
virtual bool VisitRecordDecl(clang::RecordDecl *cls);
|
||||
|
||||
virtual bool VisitClassTemplateDecl(clang::ClassTemplateDecl *decl);
|
||||
|
||||
virtual bool VisitFunctionDecl(clang::FunctionDecl *function_declaration);
|
||||
|
||||
clanguml::package_diagram::model::diagram &diagram() { return diagram_; }
|
||||
@@ -60,12 +66,17 @@ public:
|
||||
void finalize() { }
|
||||
|
||||
private:
|
||||
common::model::diagram_element::id_t get_package_id(const clang::Decl *cls);
|
||||
|
||||
void process_class_declaration(
|
||||
const clang::CXXRecordDecl &cls, found_relationships_t &relationships);
|
||||
|
||||
void process_class_children(
|
||||
const clang::CXXRecordDecl &cls, found_relationships_t &relationships);
|
||||
|
||||
void process_record_children(
|
||||
const clang::RecordDecl &cls, found_relationships_t &relationships);
|
||||
|
||||
void process_class_bases(
|
||||
const clang::CXXRecordDecl &cls, found_relationships_t &relationships);
|
||||
|
||||
@@ -90,7 +101,7 @@ private:
|
||||
common::model::relationship_t::kDependency);
|
||||
|
||||
void add_relationships(
|
||||
clang::DeclContext *cls, found_relationships_t &relationships);
|
||||
clang::Decl *cls, found_relationships_t &relationships);
|
||||
|
||||
// Reference to the output diagram model
|
||||
clanguml::package_diagram::model::diagram &diagram_;
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace A1 {
|
||||
struct CA { };
|
||||
}
|
||||
namespace A2 {
|
||||
struct CB { };
|
||||
template <typename T> struct CB {
|
||||
T cb;
|
||||
};
|
||||
}
|
||||
namespace A3 {
|
||||
struct CC { };
|
||||
@@ -58,12 +60,15 @@ struct CP { };
|
||||
namespace A17 {
|
||||
struct CR { };
|
||||
}
|
||||
namespace A18 {
|
||||
enum class S { s1, s2, s3 };
|
||||
}
|
||||
}
|
||||
namespace B::BB::BBB {
|
||||
class CBA : public A::AA::A6::CF {
|
||||
public:
|
||||
A::AA::A1::CA *ca_;
|
||||
A::AA::A2::CB cb_;
|
||||
A::AA::A2::CB<int> cb_;
|
||||
std::shared_ptr<A::AA::A3::CC> cc_;
|
||||
std::map<std::string, std::unique_ptr<A::AA::A4::CD>> *cd_;
|
||||
std::array<A::AA::A15::CO, 5> co_;
|
||||
@@ -91,6 +96,8 @@ public:
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
A::AA::A18::S s;
|
||||
};
|
||||
|
||||
void cj(std::unique_ptr<A::AA::A10::CJ> /*cj_*/) { }
|
||||
|
||||
@@ -51,6 +51,7 @@ TEST_CASE("t30002", "[test-case][package]")
|
||||
REQUIRE_THAT(puml, IsPackage("A15"));
|
||||
REQUIRE_THAT(puml, IsPackage("A16"));
|
||||
REQUIRE_THAT(puml, IsPackage("A17"));
|
||||
REQUIRE_THAT(puml, IsPackage("A18"));
|
||||
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A1")));
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A2")));
|
||||
@@ -69,6 +70,7 @@ TEST_CASE("t30002", "[test-case][package]")
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A15")));
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A16")));
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A17")));
|
||||
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("A18")));
|
||||
|
||||
save_puml(
|
||||
config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||
|
||||
@@ -354,6 +354,7 @@ using namespace clanguml::test::matchers;
|
||||
#include "t30007/test_case.h"
|
||||
#include "t30008/test_case.h"
|
||||
#include "t30009/test_case.h"
|
||||
#include "t30010/test_case.h"
|
||||
|
||||
///
|
||||
/// Include diagram tests
|
||||
|
||||
@@ -308,6 +308,9 @@ test_cases:
|
||||
- name: t30009
|
||||
title: Together layout hint test
|
||||
description:
|
||||
- name: t30010
|
||||
title: Package diagram with packages from directory structure
|
||||
description:
|
||||
Include diagrams:
|
||||
- name: t40001
|
||||
title: Basic include graph diagram test case
|
||||
|
||||
@@ -42,7 +42,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]")
|
||||
}
|
||||
|
||||
{
|
||||
auto j = generate_class_json(diagram, *model);
|
||||
auto j = generate_{{ type }}_json(diagram, *model);
|
||||
|
||||
using namespace json;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user