Added support for class diagram filtering based on C++20 modules (#195)

This commit is contained in:
Bartek Kryza
2023-12-17 20:49:41 +01:00
parent f2fe1ca2cf
commit ea6892f754
21 changed files with 310 additions and 21 deletions

View File

@@ -159,6 +159,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
process_comment(*enm, e);
set_source_location(*enm, e);
set_owning_module(*enm, e);
if (e.skip())
return true;
@@ -265,6 +266,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl(
LOG_DBG("Adding class {} with id {}", name, id);
set_source_location(*cls, *template_specialization_ptr);
set_owning_module(*cls, *template_specialization_ptr);
add_class(std::move(template_specialization_ptr));
}
@@ -677,6 +679,9 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
LOG_DBG(
"== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString());
if (cls->getOwningModule() != nullptr)
LOG_DBG(
"== getOwningModule()->Name = {}", cls->getOwningModule()->Name);
LOG_DBG("== getID() = {}", cls->getID());
LOG_DBG("== isTemplateDecl() = {}", cls->isTemplateDecl());
LOG_DBG("== isTemplated() = {}", cls->isTemplated());
@@ -762,6 +767,7 @@ translation_unit_visitor::create_concept_declaration(clang::ConceptDecl *cpt)
process_comment(*cpt, concept_model);
set_source_location(*cpt, concept_model);
set_owning_module(*cpt, concept_model);
if (concept_model.skip())
return {};
@@ -802,6 +808,7 @@ std::unique_ptr<class_> translation_unit_visitor::create_record_declaration(
process_comment(*rec, record);
set_source_location(*rec, record);
set_owning_module(*rec, record);
const auto record_full_name = record_ptr->full_name(false);
@@ -841,6 +848,7 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
process_comment(*cls, c);
set_source_location(*cls, c);
set_owning_module(*cls, c);
if (c.skip())
return {};
@@ -1835,6 +1843,7 @@ translation_unit_visitor::process_template_specialization(
process_comment(*cls, template_instantiation);
set_source_location(*cls, template_instantiation);
set_owning_module(*cls, template_instantiation);
if (template_instantiation.skip())
return {};

View File

@@ -285,6 +285,39 @@ tvl::value_t namespace_filter::match(const diagram &d, const element &e) const
return result;
}
modules_filter::modules_filter(
filter_t type, std::vector<common::string_or_regex> modules)
: filter_visitor{type}
, modules_{std::move(modules)}
{
}
tvl::value_t modules_filter::match(const diagram &d, const element &e) const
{
if (modules_.empty())
return {};
if (!e.module().has_value())
return {false};
const auto module_toks = util::split(e.module().value(), ".");
auto result = tvl::any_of(modules_.begin(), modules_.end(),
[&e, &module_toks](const auto &modit) {
if (std::holds_alternative<std::string>(modit.value())) {
const auto &modit_str = std::get<std::string>(modit.value());
const auto modit_toks = util::split(modit_str, ".");
return e.module() == modit_str ||
util::starts_with(module_toks, modit_toks);
}
return std::get<common::regex>(modit.value()) %= e.module().value();
});
return result;
}
element_filter::element_filter(
filter_t type, std::vector<common::string_or_regex> elements)
: filter_visitor{type}
@@ -887,6 +920,9 @@ void diagram_filter::init_filters(const config::diagram &c)
add_inclusive_filter(std::make_unique<namespace_filter>(
filter_t::kInclusive, c.include().namespaces));
add_inclusive_filter(std::make_unique<modules_filter>(
filter_t::kInclusive, c.include().modules));
add_inclusive_filter(std::make_unique<relationship_filter>(
filter_t::kInclusive, c.include().relationships));
@@ -997,6 +1033,9 @@ void diagram_filter::init_filters(const config::diagram &c)
add_exclusive_filter(std::make_unique<namespace_filter>(
filter_t::kExclusive, c.exclude().namespaces));
add_exclusive_filter(std::make_unique<modules_filter>(
filter_t::kExclusive, c.exclude().modules));
add_exclusive_filter(std::make_unique<paths_filter>(
filter_t::kExclusive, c.root_directory(), c.exclude().paths));

View File

@@ -154,6 +154,21 @@ private:
std::vector<common::namespace_or_regex> namespaces_;
};
/**
* Match diagram elements to a set of specified modules or
* module regex patterns.
*/
struct modules_filter : public filter_visitor {
modules_filter(filter_t type, std::vector<common::string_or_regex> modules);
~modules_filter() override = default;
tvl::value_t match(const diagram &d, const element &e) const override;
private:
std::vector<common::string_or_regex> modules_;
};
/**
* Match element's name to a set of names or regex patterns.
*/

View File

@@ -87,6 +87,20 @@ public:
*/
const namespace_ &path() const { return ns_; }
/**
* Set elements owning module.
*
* @param module C++20 module.
*/
void set_module(const std::string &module) { module_ = module; }
/**
* Return elements owning module, if any.
*
* @return C++20 module.
*/
std::optional<std::string> module() const { return module_; }
/**
* Return elements full name.
*
@@ -120,5 +134,6 @@ public:
private:
namespace_ ns_;
namespace_ using_namespace_;
std::optional<std::string> module_;
};
} // namespace clanguml::common::model

View File

@@ -21,6 +21,8 @@
#include "comment/clang_visitor.h"
#include "comment/plain_visitor.h"
#include "clang/Basic/Module.h"
namespace clanguml::common::visitor {
translation_unit_visitor::translation_unit_visitor(
@@ -161,4 +163,12 @@ void translation_unit_visitor::set_source_location(
element.set_location_id(location.getHashValue());
}
void translation_unit_visitor::set_owning_module(
const clang::Decl &decl, clanguml::common::model::element &element)
{
if (const clang::Module *module = decl.getOwningModule();
module != nullptr) {
element.set_module(module->Name);
}
}
} // namespace clanguml::common::visitor

View File

@@ -100,6 +100,9 @@ public:
void set_source_location(const clang::SourceLocation &location,
clanguml::common::model::source_location &element);
void set_owning_module(
const clang::Decl &decl, clanguml::common::model::element &element);
protected:
/**
* @brief Process comment directives in comment attached to a declaration

View File

@@ -180,6 +180,19 @@ struct filter {
*/
std::vector<common::namespace_or_regex> namespaces;
/*! @brief Modules filter
*
* Example:
*
* ```yaml
* include
* modules:
* - app.module1
* - r: ".*internal.*"
* ```
*/
std::vector<common::string_or_regex> modules;
/*! @brief Elements filter
*
* Example:

View File

@@ -64,10 +64,6 @@ types:
regex_t:
r: string
regex_or_string_t: [string, regex_t]
namespaces_filter_t:
namespaces: [regex_or_string_t]
elements_filter_t:
elements: [regex_or_string_t]
element_types_filter_t: !variant
- class
- enum
@@ -121,6 +117,7 @@ types:
- context_filter_match_t
filter_t:
namespaces: !optional [regex_or_string_t]
modules: !optional [regex_or_string_t]
elements: !optional [regex_or_string_t]
element_types: !optional [element_types_filter_t]
relationships: !optional [relationship_filter_t]

View File

@@ -475,6 +475,12 @@ template <> struct convert<filter> {
rhs.namespaces.push_back({ns});
}
if (node["modules"]) {
auto module_list = node["modules"].as<decltype(rhs.modules)>();
for (const auto &ns : module_list)
rhs.modules.push_back({ns});
}
if (node["relationships"])
rhs.relationships =
node["relationships"].as<decltype(rhs.relationships)>();

View File

@@ -122,6 +122,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f)
out << YAML::BeginMap;
if (!f.namespaces.empty())
out << YAML::Key << "namespaces" << YAML::Value << f.namespaces;
if (!f.modules.empty())
out << YAML::Key << "modules" << YAML::Value << f.modules;
if (!f.access.empty())
out << YAML::Key << "access" << YAML::Value << f.access;
if (!f.context.empty())