Added generation of package diagrams from directory structure instead of namespaces

This commit is contained in:
Bartek Kryza
2023-05-26 21:05:28 +02:00
parent c3dcac72da
commit 467c021e17
11 changed files with 248 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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_*/) { }

View File

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

View File

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

View File

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

View File

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