Added package diagram namespace alias test case

This commit is contained in:
Bartek Kryza
2022-01-28 21:58:54 +01:00
parent 4de6d2a3ac
commit 4c51268869
11 changed files with 247 additions and 32 deletions

View File

@@ -21,6 +21,7 @@
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_entity_kind.hpp>
#include <cppast/cpp_namespace.hpp>
#include <cppast/cpp_template.hpp>
#include <spdlog/spdlog.h>
@@ -92,6 +93,27 @@ std::string ns(const cppast::cpp_entity &e)
return fmt::format("{}", fmt::join(res, "::"));
}
type_safe::optional_ref<const cppast::cpp_namespace> entity_ns(
const cppast::cpp_entity &e)
{
std::vector<std::string> res{};
if (e.kind() == cppast::cpp_entity_kind::namespace_t)
return type_safe::optional_ref<const cppast::cpp_namespace>(
static_cast<const cppast::cpp_namespace &>(e));
auto it = e.parent();
while (it) {
if (it.value().kind() == cppast::cpp_entity_kind::namespace_t) {
return type_safe::optional_ref<const cppast::cpp_namespace>(
static_cast<const cppast::cpp_namespace &>(it.value()));
}
it = it.value().parent();
}
return {};
}
bool is_inside_class(const cppast::cpp_entity &e)
{
auto it = e.parent();

View File

@@ -53,6 +53,9 @@ const cppast::cpp_type &unreferenced(const cppast::cpp_type &t);
std::string ns(const cppast::cpp_entity &e);
type_safe::optional_ref<const cppast::cpp_namespace> entity_ns(
const cppast::cpp_entity &e);
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx);
bool is_inside_class(const cppast::cpp_entity &e);

View File

@@ -32,6 +32,52 @@ translation_unit_context::translation_unit_context(
{
}
bool translation_unit_context::has_namespace_alias(
const std::string &full_name) const
{
bool res =
namespace_alias_index_.find(full_name) != namespace_alias_index_.end();
LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not");
return res;
}
void translation_unit_context::add_namespace_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_namespace> ref)
{
if (!has_namespace_alias(full_name)) {
LOG_DBG("Stored type alias: {} -> {} ", full_name, ref.get().name());
namespace_alias_index_.emplace(full_name, std::move(ref));
}
}
type_safe::object_ref<const cppast::cpp_namespace>
translation_unit_context::get_namespace_alias(
const std::string &full_name) const
{
assert(has_namespace_alias(full_name));
return namespace_alias_index_.at(full_name);
}
type_safe::object_ref<const cppast::cpp_namespace>
translation_unit_context::get_namespace_alias_final(
const cppast::cpp_namespace &ns) const
{
auto ns_full_name = cx::util::full_name({}, ns);
ns_full_name = cx::util::ns(ns) + "::" + ns_full_name;
if (has_namespace_alias(ns_full_name)) {
return get_namespace_alias_final(
namespace_alias_index_.at(ns_full_name).get());
}
return type_safe::ref(ns);
}
bool translation_unit_context::has_type_alias(
const std::string &full_name) const
{

View File

@@ -21,6 +21,7 @@
#include "package_diagram/model/diagram.h"
#include <cppast/cpp_entity_index.hpp>
#include <cppast/cpp_namespace.hpp>
#include <cppast/cpp_type.hpp>
#include <type_safe/reference.hpp>
@@ -32,6 +33,17 @@ public:
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config);
bool has_namespace_alias(const std::string &full_name) const;
void add_namespace_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_namespace> ref);
type_safe::object_ref<const cppast::cpp_namespace> get_namespace_alias(
const std::string &full_name) const;
type_safe::object_ref<const cppast::cpp_namespace>
get_namespace_alias_final(const cppast::cpp_namespace &t) const;
bool has_type_alias(const std::string &full_name) const;
void add_type_alias(const std::string &full_name,
@@ -80,7 +92,11 @@ private:
// Reference to class diagram config
const clanguml::config::package_diagram &config_;
// Map of discovered aliases (declared with 'using' keyword)
// Map of discovered aliases (declared with 'namespace' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_namespace>>
namespace_alias_index_;
// Map of discovered type aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_index_;

View File

@@ -143,6 +143,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
ctx.pop_namespace();
}
}
else if (e.kind() == cppast::cpp_entity_kind::namespace_alias_t) {
auto &na = static_cast<const cppast::cpp_namespace_alias &>(e);
for (const auto &alias_target :
na.target().get(ctx.entity_index())) {
auto full_ns = cx::util::full_name(ctx.get_namespace(), na);
ctx.add_namespace_alias(full_ns, alias_target);
}
}
else if (e.kind() ==
cppast::cpp_entity_kind::class_template_specialization_t) {
LOG_DBG("========== Visiting '{}' - {}",
@@ -207,8 +216,6 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta),
type_safe::ref(ta.underlying_type()));
// ctx.diagram().add_type_alias(std::move(t));
}
else if (e.kind() == cppast::cpp_entity_kind::alias_template_t) {
LOG_DBG("========== Visiting '{}' - {}",
@@ -294,21 +301,15 @@ void translation_unit_visitor::process_class_declaration(
for (auto &base : cls.bases()) {
find_relationships(
base.type(), relationships, relationship_t::kDependency);
clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base);
}
for (const auto &dependency : relationships) {
auto type_ns = util::split(std::get<0>(dependency), "::");
type_ns.pop_back();
auto destination = util::split(std::get<0>(dependency), "::");
relationship r{relationship_t::kDependency,
fmt::format("{}", fmt::join(type_ns, "::"))};
if (!starts_with(ctx.get_namespace(), type_ns) &&
!starts_with(type_ns, ctx.get_namespace())) {
relationship r{relationship_t::kDependency,
fmt::format("{}", fmt::join(type_ns, "::"))};
if (!starts_with(ctx.get_namespace(), destination) &&
!starts_with(destination, ctx.get_namespace())) {
relationship r{
relationship_t::kDependency, std::get<0>(dependency)};
current_package.value().add_relationship(std::move(r));
}
}
@@ -330,16 +331,12 @@ void translation_unit_visitor::process_function(const cppast::cpp_function &f)
f.return_type(), relationships, relationship_t::kDependency);
for (const auto &dependency : relationships) {
auto type_ns = util::split(std::get<0>(dependency), "::");
type_ns.pop_back();
auto destination = util::split(std::get<0>(dependency), "::");
relationship r{relationship_t::kDependency,
fmt::format("{}", fmt::join(type_ns, "::"))};
if (!starts_with(ctx.get_namespace(), type_ns) &&
!starts_with(type_ns, ctx.get_namespace())) {
relationship r{relationship_t::kDependency,
fmt::format("{}", fmt::join(type_ns, "::"))};
if (!starts_with(ctx.get_namespace(), destination) &&
!starts_with(destination, ctx.get_namespace())) {
relationship r{
relationship_t::kDependency, std::get<0>(dependency)};
current_package.value().add_relationship(std::move(r));
}
}
@@ -352,8 +349,37 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
{
bool found{false};
const auto fn =
cx::util::full_name(cppast::remove_cv(t_), ctx.entity_index(), false);
const auto fn = cx::util::full_name(
resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false);
auto t_ns = util::split(fn, "::");
t_ns.pop_back();
const auto &t_raw = resolve_alias(cppast::remove_cv(t_));
if (t_raw.kind() == cppast::cpp_type_kind::user_defined_t) {
auto t_raw_ns = cx::util::ns(t_raw, ctx.entity_index());
const auto &type_entities =
static_cast<const cppast::cpp_user_defined_type &>(t_raw)
.entity()
.get(ctx.entity_index());
if (type_entities.size() > 0) {
const auto &type_entity = type_entities[0];
const auto &t_raw_ns = cx::util::entity_ns(type_entity.get());
const auto &t_raw_ns_final = cx::util::ns(t_raw_ns.value()) +
"::" + cx::util::full_name({}, t_raw_ns.value());
t_ns = util::split(t_raw_ns_final, "::");
}
}
std::vector<std::string> possible_matches;
possible_matches.push_back(util::join(t_ns, "::"));
const auto fn_ns = cx::util::ns(cppast::remove_cv(t_), ctx.entity_index());
LOG_DBG("Finding relationships for type {}, {}, {}", cppast::to_string(t_),
t_.kind(), fn);
@@ -391,21 +417,19 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
LOG_DBG("User defined type: {} | {}", cppast::to_string(t_),
cppast::to_string(t_.canonical()));
if (relationship_type != relationship_t::kNone)
relationships.emplace_back(cppast::to_string(t), relationship_type);
else
relationships.emplace_back(
cppast::to_string(t), relationship_t::kDependency);
// Check if t_ has an alias in the alias index
if (ctx.has_type_alias(fn)) {
LOG_DBG("Find relationship in alias of {} | {}", fn,
LOG_DBG("Found relationship in alias of {} | {}", fn,
cppast::to_string(ctx.get_type_alias(fn).get()));
found = find_relationships(
ctx.get_type_alias(fn).get(), relationships, relationship_type);
if (found)
return found;
}
for (const auto &pm : possible_matches) {
relationships.emplace_back(pm, relationship_t::kDependency);
}
}
else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) {
auto &tinst =

View File

@@ -64,6 +64,11 @@ std::vector<std::string> split(std::string str, std::string delimiter)
return result;
}
std::string join(const std::vector<std::string> &toks, std::string delimiter)
{
return fmt::format("{}", fmt::join(toks, delimiter));
}
std::string ns_relative(
const std::vector<std::string> &namespaces, const std::string &n)
{

View File

@@ -64,6 +64,8 @@ std::string trim(const std::string &s);
*/
std::vector<std::string> split(std::string str, std::string delimiter);
std::string join(const std::vector<std::string> &toks, std::string delimiter);
/**
* @brief Get name of the identifier relative to a set of namespaces
*

15
tests/t30005/.clang-uml Normal file
View File

@@ -0,0 +1,15 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t30005_package:
type: package
glob:
- ../../tests/t30005/t30005.cc
include:
namespaces:
- clanguml::t30005
using_namespace:
- clanguml::t30005
plantuml:
before:
- "' t30005 test package diagram"

27
tests/t30005/t30005.cc Normal file
View File

@@ -0,0 +1,27 @@
namespace clanguml {
namespace t30005 {
namespace A::AA::AAA {
struct C1 {
};
}
namespace B::BB::BBB {
namespace A6 = A::AA::AAA;
namespace ASix = A6;
struct C2 {
ASix::C1 *cb;
};
}
namespace C::CC::CCC {
namespace A6 = A::AA::AAA;
namespace ASix = A6;
using ADSix = ASix::C1;
struct C2 {
ADSix *cc;
};
}
}
}

54
tests/t30005/test_case.h Normal file
View File

@@ -0,0 +1,54 @@
/**
* tests/t30005/test_case.cc
*
* Copyright (c) 2021-2022 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("t30005", "[test-case][package]")
{
auto [config, db] = load_config("t30005");
auto diagram = config.diagrams["t30005_package"];
REQUIRE(diagram->include.namespaces.size() == 1);
REQUIRE_THAT(diagram->include.namespaces,
VectorContains(std::string{"clanguml::t30005"}));
REQUIRE(diagram->should_include("clanguml::t30005::A"));
REQUIRE(diagram->should_include("clanguml::t30005::C"));
REQUIRE(!diagram->should_include("std::vector"));
REQUIRE(diagram->name == "t30005_package");
auto model = generate_package_diagram(db, diagram);
REQUIRE(model.name() == "t30005_package");
auto puml = generate_package_puml(diagram, model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, IsPackage("AAA"));
REQUIRE_THAT(puml, IsPackage("BBB"));
REQUIRE_THAT(puml, IsPackage("CCC"));
REQUIRE_THAT(puml, IsDependency(_A("BBB"), _A("AAA")));
REQUIRE_THAT(puml, IsDependency(_A("CCC"), _A("AAA")));
save_puml(
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
}

View File

@@ -180,6 +180,7 @@ using namespace clanguml::test::matchers;
#include "t30002/test_case.h"
#include "t30003/test_case.h"
#include "t30004/test_case.h"
#include "t30005/test_case.h"
//
// Other tests (e.g. configuration file)