Added package diagram generation from C++20 modules (#101)
This commit is contained in:
@@ -25,8 +25,9 @@
|
||||
|
||||
namespace clanguml::common::model {
|
||||
|
||||
element::element(namespace_ using_namespace)
|
||||
: using_namespace_{std::move(using_namespace)}
|
||||
element::element(namespace_ using_namespace, path_type pt)
|
||||
: ns_{pt}
|
||||
, using_namespace_{std::move(using_namespace)}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ namespace clanguml::common::model {
|
||||
*/
|
||||
class element : public diagram_element {
|
||||
public:
|
||||
element(namespace_ using_namespace);
|
||||
element(namespace_ using_namespace, path_type pt = path_type::kNamespace);
|
||||
|
||||
element(path_type pt);
|
||||
|
||||
~element() override = default;
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#include <sstream>
|
||||
|
||||
namespace clanguml::common::model {
|
||||
package::package(const common::model::namespace_ &using_namespace)
|
||||
: element{using_namespace}
|
||||
package::package(const common::model::namespace_ &using_namespace, path_type pt)
|
||||
: element{using_namespace, pt}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ class package : public element,
|
||||
public stylable_element,
|
||||
public nested_trait<element, path> {
|
||||
public:
|
||||
package(const common::model::path &using_namespace);
|
||||
package(const common::model::path &using_namespace,
|
||||
path_type pt = path_type::kNamespace);
|
||||
|
||||
package(const package &) = delete;
|
||||
package(package &&) = default;
|
||||
|
||||
@@ -386,6 +386,8 @@ public:
|
||||
*/
|
||||
path_type type() const { return path_type_; }
|
||||
|
||||
const container_type &tokens() const { return path_; }
|
||||
|
||||
private:
|
||||
path_type path_type_;
|
||||
container_type path_;
|
||||
|
||||
@@ -135,6 +135,9 @@ public:
|
||||
if (parent_path.type() == common::model::path_type::kNamespace) {
|
||||
return add_with_namespace_path(std::move(e));
|
||||
}
|
||||
else if (parent_path.type() == common::model::path_type::kModule) {
|
||||
return add_with_module_path(parent_path, std::move(e));
|
||||
}
|
||||
|
||||
return add_with_filesystem_path(parent_path, std::move(e));
|
||||
}
|
||||
@@ -155,6 +158,17 @@ public:
|
||||
inja::json context() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Add element using module as diagram path
|
||||
*
|
||||
* @tparam ElementT Element type
|
||||
* @param e Element to add
|
||||
* @return True, if the element was added
|
||||
*/
|
||||
template <typename ElementT>
|
||||
bool add_with_module_path(
|
||||
const common::model::path &parent_path, std::unique_ptr<ElementT> &&e);
|
||||
|
||||
/**
|
||||
* @brief Add element using namespace as diagram path
|
||||
*
|
||||
@@ -237,6 +251,55 @@ bool diagram::add_with_namespace_path(std::unique_ptr<ElementT> &&p)
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename ElementT>
|
||||
bool diagram::add_with_module_path(
|
||||
const common::model::path &parent_path, std::unique_ptr<ElementT> &&p)
|
||||
{
|
||||
LOG_DBG("Adding package: {}, {}, {}, [{}]", p->name(), p->full_name(false),
|
||||
parent_path.to_string(), p->id());
|
||||
|
||||
// Make sure all parent modules are already packages in the
|
||||
// model
|
||||
std::string module_path = p->using_namespace().to_string();
|
||||
for (auto it = parent_path.begin(); it != parent_path.end(); it++) {
|
||||
auto pkg = std::make_unique<common::model::package>(
|
||||
p->using_namespace(), common::model::path_type::kModule);
|
||||
pkg->set_name(*it);
|
||||
|
||||
auto ns = common::model::path(
|
||||
parent_path.begin(), it, common::model::path_type::kModule);
|
||||
pkg->set_module(module_path);
|
||||
pkg->set_namespace(ns);
|
||||
|
||||
std::string package_id_path;
|
||||
if (module_path.empty())
|
||||
package_id_path = pkg->name();
|
||||
else
|
||||
package_id_path = module_path + "." + pkg->name();
|
||||
|
||||
pkg->set_id(common::to_id(package_id_path));
|
||||
|
||||
auto p_ref = std::ref(*pkg);
|
||||
|
||||
auto res = add_element(ns, std::move(pkg));
|
||||
if (res)
|
||||
element_view<ElementT>::add(p_ref);
|
||||
|
||||
if (module_path.empty())
|
||||
module_path = *it;
|
||||
else
|
||||
module_path += fmt::format(".{}", *it);
|
||||
}
|
||||
|
||||
auto p_ref = std::ref(*p);
|
||||
|
||||
auto res = add_element(parent_path, std::move(p));
|
||||
if (res)
|
||||
element_view<ElementT>::add(p_ref);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename ElementT>
|
||||
bool diagram::add_with_filesystem_path(
|
||||
const common::model::path &parent_path, std::unique_ptr<ElementT> &&p)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "common/clang_utils.h"
|
||||
#include "common/model/namespace.h"
|
||||
|
||||
#include "clang/Basic/Module.h"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <deque>
|
||||
@@ -45,7 +47,7 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns)
|
||||
{
|
||||
assert(ns != nullptr);
|
||||
|
||||
if (config().package_type() == config::package_type_t::kDirectory)
|
||||
if (config().package_type() != config::package_type_t::kNamespace)
|
||||
return true;
|
||||
|
||||
if (ns->isAnonymousNamespace() || ns->isInline())
|
||||
@@ -237,6 +239,43 @@ void translation_unit_visitor::add_relationships(
|
||||
if (diagram().should_include(*pkg))
|
||||
diagram().add(parent_path, std::move(pkg));
|
||||
}
|
||||
else if (config().package_type() == config::package_type_t::kModule) {
|
||||
const auto *module = cls->getOwningModule();
|
||||
|
||||
if (module == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string module_path_str = module->Name;
|
||||
if (module->isPrivateModule())
|
||||
module_path_str = module->getTopLevelModule()->Name;
|
||||
|
||||
common::model::path module_path{
|
||||
module_path_str, common::model::path_type::kModule};
|
||||
module_path.pop_back();
|
||||
|
||||
auto relative_module =
|
||||
config().make_module_relative(std::optional{module_path_str});
|
||||
|
||||
common::model::path parent_path{
|
||||
relative_module, common::model::path_type::kModule};
|
||||
auto pkg_name = parent_path.name();
|
||||
parent_path.pop_back();
|
||||
|
||||
auto pkg = std::make_unique<common::model::package>(
|
||||
config().using_module(), common::model::path_type::kModule);
|
||||
|
||||
pkg->set_name(pkg_name);
|
||||
pkg->set_id(get_package_id(cls));
|
||||
// This is for diagram filters
|
||||
pkg->set_module(module_path.to_string());
|
||||
// This is for rendering nested package structure
|
||||
pkg->set_namespace(parent_path);
|
||||
set_source_location(*cls, *pkg);
|
||||
|
||||
if (diagram().should_include(*pkg))
|
||||
diagram().add(parent_path, std::move(pkg));
|
||||
}
|
||||
|
||||
auto current_package_id = get_package_id(cls);
|
||||
|
||||
@@ -284,6 +323,18 @@ common::model::diagram_element::id_t translation_unit_visitor::get_package_id(
|
||||
|
||||
return {};
|
||||
}
|
||||
else if (config().package_type() == config::package_type_t::kModule) {
|
||||
const auto *module = cls->getOwningModule();
|
||||
if (module != nullptr) {
|
||||
std::string module_path = module->Name;
|
||||
if (module->isPrivateModule()) {
|
||||
module_path = module->getTopLevelModule()->Name;
|
||||
}
|
||||
return common::to_id(module_path);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto file =
|
||||
source_manager().getFilename(cls->getSourceRange().getBegin()).str();
|
||||
@@ -578,6 +629,15 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type,
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (config().package_type() ==
|
||||
config::package_type_t::kModule) {
|
||||
const auto *module = cxxrecord_decl->getOwningModule();
|
||||
if (module != nullptr) {
|
||||
const auto target_id = get_package_id(cxxrecord_decl);
|
||||
relationships.emplace_back(target_id, relationship_hint);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (diagram().should_include(
|
||||
namespace_{common::get_qualified_name(
|
||||
@@ -591,8 +651,8 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type,
|
||||
}
|
||||
else if (const auto *record_decl = type->getAsRecordDecl();
|
||||
record_decl != nullptr) {
|
||||
// This is only possible for plain C translation unit, so we don't
|
||||
// need to consider namespaces here
|
||||
// This is only possible for plain C translation unit, so we
|
||||
// don't need to consider namespaces or modules here
|
||||
if (config().package_type() == config::package_type_t::kDirectory) {
|
||||
if (diagram().should_include(
|
||||
namespace_{common::get_qualified_name(*record_decl)})) {
|
||||
|
||||
@@ -7,7 +7,7 @@ file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml
|
||||
test_compilation_database_data/*.json)
|
||||
|
||||
set(TEST_CASES_REQUIRING_CXX20 t00056 t00058 t00059 t00065 t00069)
|
||||
set(TEST_CASES_REQUIRING_CXX20_MODULES t00070 t00071)
|
||||
set(TEST_CASES_REQUIRING_CXX20_MODULES t00070 t00071 t30012)
|
||||
|
||||
if(ENABLE_CXX_MODULES_TEST_CASES)
|
||||
foreach(CXX20_MOD_TC ${TEST_CASES_REQUIRING_CXX20_MODULES})
|
||||
|
||||
10
tests/t30012/.clang-uml
Normal file
10
tests/t30012/.clang-uml
Normal file
@@ -0,0 +1,10 @@
|
||||
diagrams:
|
||||
t30012_package:
|
||||
type: package
|
||||
glob:
|
||||
- t30012.cc
|
||||
package_type: module
|
||||
include:
|
||||
modules:
|
||||
- t30012
|
||||
using_module: t30012
|
||||
13
tests/t30012/src/lib1.cppm
Normal file
13
tests/t30012/src/lib1.cppm
Normal file
@@ -0,0 +1,13 @@
|
||||
export module t30012.app.lib1;
|
||||
|
||||
export namespace clanguml::t30012 {
|
||||
class B { };
|
||||
|
||||
template <typename T> class BB {
|
||||
T t;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
enum class BBB { bbb1, bbb2 };
|
||||
} // namespace detail
|
||||
}
|
||||
5
tests/t30012/src/lib1mod1.cppm
Normal file
5
tests/t30012/src/lib1mod1.cppm
Normal file
@@ -0,0 +1,5 @@
|
||||
export module t30012.app.lib1.mod1;
|
||||
|
||||
export namespace clanguml::t30012 {
|
||||
class D { };
|
||||
}
|
||||
5
tests/t30012/src/lib1mod2.cppm
Normal file
5
tests/t30012/src/lib1mod2.cppm
Normal file
@@ -0,0 +1,5 @@
|
||||
export module t30012.app.lib1.mod2;
|
||||
|
||||
export namespace clanguml::t30012 {
|
||||
class E { };
|
||||
}
|
||||
13
tests/t30012/src/lib2.cppm
Normal file
13
tests/t30012/src/lib2.cppm
Normal file
@@ -0,0 +1,13 @@
|
||||
export module t30012.app.lib2;
|
||||
|
||||
export namespace clanguml::t30012 {
|
||||
class C { };
|
||||
|
||||
template <typename T> class CC {
|
||||
T t;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
enum class CCC { ccc1, ccc2 };
|
||||
}
|
||||
}
|
||||
11
tests/t30012/src/t30012_mod.cppm
Normal file
11
tests/t30012/src/t30012_mod.cppm
Normal file
@@ -0,0 +1,11 @@
|
||||
export module t30012.app;
|
||||
export import t30012.app.lib1;
|
||||
export import t30012.app.lib2;
|
||||
|
||||
export namespace clanguml::t30012 {
|
||||
class A {
|
||||
int get() { return a; }
|
||||
|
||||
int a;
|
||||
};
|
||||
}
|
||||
15
tests/t30012/t30012.cc
Normal file
15
tests/t30012/t30012.cc
Normal file
@@ -0,0 +1,15 @@
|
||||
import t30012.app;
|
||||
import t30012.app.lib1;
|
||||
import t30012.app.lib1.mod1;
|
||||
import t30012.app.lib1.mod2;
|
||||
import t30012.app.lib2;
|
||||
|
||||
namespace clanguml {
|
||||
namespace t30012 {
|
||||
class R {
|
||||
A *a;
|
||||
B *b;
|
||||
C *c;
|
||||
};
|
||||
}
|
||||
}
|
||||
70
tests/t30012/test_case.h
Normal file
70
tests/t30012/test_case.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* tests/t30012/test_case.h
|
||||
*
|
||||
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
TEST_CASE("t30012", "[test-case][package]")
|
||||
{
|
||||
auto [config, db] = load_config("t30012");
|
||||
|
||||
auto diagram = config.diagrams["t30012_package"];
|
||||
|
||||
REQUIRE(diagram->name == "t30012_package");
|
||||
|
||||
auto model = generate_package_diagram(*db, diagram);
|
||||
|
||||
REQUIRE(model->name() == "t30012_package");
|
||||
|
||||
{
|
||||
auto src = generate_package_puml(diagram, *model);
|
||||
AliasMatcher _A(src);
|
||||
|
||||
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||
|
||||
// Check if all packages exist
|
||||
REQUIRE_THAT(src, IsPackage("app"));
|
||||
REQUIRE_THAT(src, IsPackage("lib1"));
|
||||
REQUIRE_THAT(src, IsPackage("lib2"));
|
||||
REQUIRE_THAT(src, IsPackage("mod1"));
|
||||
REQUIRE_THAT(src, IsPackage("mod2"));
|
||||
|
||||
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||
}
|
||||
|
||||
{
|
||||
auto j = generate_package_json(diagram, *model);
|
||||
|
||||
using namespace json;
|
||||
|
||||
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||
}
|
||||
|
||||
{
|
||||
auto src = generate_package_mermaid(diagram, *model);
|
||||
|
||||
mermaid::AliasMatcher _A(src);
|
||||
using mermaid::IsPackage;
|
||||
|
||||
REQUIRE_THAT(src, IsPackage(_A("app")));
|
||||
REQUIRE_THAT(src, IsPackage(_A("lib1")));
|
||||
REQUIRE_THAT(src, IsPackage(_A("lib2")));
|
||||
REQUIRE_THAT(src, IsPackage(_A("mod1")));
|
||||
REQUIRE_THAT(src, IsPackage(_A("mod2")));
|
||||
|
||||
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||
}
|
||||
}
|
||||
@@ -472,7 +472,9 @@ using namespace clanguml::test::matchers;
|
||||
#include "t30009/test_case.h"
|
||||
#include "t30010/test_case.h"
|
||||
#include "t30011/test_case.h"
|
||||
|
||||
#if defined(ENABLE_CXX_MODULES_TEST_CASES)
|
||||
#include "t30012/test_case.h"
|
||||
#endif
|
||||
///
|
||||
/// Include diagram tests
|
||||
///
|
||||
|
||||
@@ -50,7 +50,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]")
|
||||
}
|
||||
|
||||
{
|
||||
auto src = generate_class_mermaid(diagram, *model);
|
||||
auto src = generate_{{ type }}_mermaid(diagram, *model);
|
||||
|
||||
mermaid::AliasMatcher _A(src);
|
||||
using mermaid::IsClass;
|
||||
|
||||
Reference in New Issue
Block a user