Added callee_filter for including/excluding messages based on receiver type (#152)

This commit is contained in:
Bartek Kryza
2023-07-01 21:19:51 +02:00
parent 213483dd3b
commit e50a7b1846
24 changed files with 590 additions and 43 deletions

View File

@@ -25,6 +25,7 @@
#include "glob/glob.hpp"
#include "include_diagram/model/diagram.h"
#include "package_diagram/model/diagram.h"
#include "sequence_diagram/model/diagram.h"
namespace clanguml::common::model {
@@ -140,6 +141,12 @@ tvl::value_t filter_visitor::match(
return match(d, m.access());
}
tvl::value_t filter_visitor::match(const diagram & /*d*/,
const sequence_diagram::model::participant & /*p*/) const
{
return {};
}
bool filter_visitor::is_inclusive() const
{
return type_ == filter_t::kInclusive;
@@ -166,6 +173,13 @@ tvl::value_t anyof_filter::match(
[&d, &e](const auto &f) { return f->match(d, e); });
}
tvl::value_t anyof_filter::match(
const diagram &d, const sequence_diagram::model::participant &p) const
{
return tvl::any_of(filters_.begin(), filters_.end(),
[&d, &p](const auto &f) { return f->match(d, p); });
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::source_file &e) const
{
@@ -351,6 +365,62 @@ tvl::value_t method_type_filter::match(
});
}
callee_filter::callee_filter(
filter_t type, std::vector<config::callee_type> callee_types)
: filter_visitor{type}
, callee_types_{std::move(callee_types)}
{
}
tvl::value_t callee_filter::match(
const diagram &d, const sequence_diagram::model::participant &p) const
{
using sequence_diagram::model::class_;
using sequence_diagram::model::method;
using sequence_diagram::model::participant;
auto is_lambda = [&d](const method &m) {
auto class_participant =
dynamic_cast<const sequence_diagram::model::diagram &>(d)
.get_participant<class_>(m.class_id());
if (!class_participant)
return false;
return class_participant.value().is_lambda();
};
tvl::value_t res = tvl::any_of(
callee_types_.begin(), callee_types_.end(), [&p, is_lambda](auto ct) {
switch (ct) {
case config::callee_type::method:
return p.type_name() == "method";
case config::callee_type::constructor:
return p.type_name() == "method" &&
((method &)p).is_constructor();
case config::callee_type::assignment:
return p.type_name() == "method" &&
((method &)p).is_assignment();
case config::callee_type::operator_:
return p.type_name() == "method" && ((method &)p).is_operator();
case config::callee_type::defaulted:
return p.type_name() == "method" &&
((method &)p).is_defaulted();
case config::callee_type::static_:
return p.type_name() == "method" && ((method &)p).is_static();
case config::callee_type::function:
return p.type_name() == "function";
case config::callee_type::function_template:
return p.type_name() == "function_template";
case config::callee_type::lambda:
return p.type_name() == "method" && is_lambda((method &)p);
}
return false;
});
return res;
}
subclass_filter::subclass_filter(
filter_t type, std::vector<common::string_or_regex> roots)
: filter_visitor{type}
@@ -771,6 +841,10 @@ void diagram_filter::init_filters(const config::diagram &c)
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependencies, true));
}
else if (c.type() == diagram_t::kSequence) {
element_filters.emplace_back(std::make_unique<callee_filter>(
filter_t::kInclusive, c.include().callee_types));
}
else if (c.type() == diagram_t::kPackage) {
element_filters.emplace_back(
std::make_unique<package_dependants_filter_t>(
@@ -878,7 +952,11 @@ void diagram_filter::init_filters(const config::diagram &c)
filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependencies, true));
if (c.type() == diagram_t::kInclude) {
if (c.type() == diagram_t::kSequence) {
add_exclusive_filter(std::make_unique<callee_filter>(
filter_t::kExclusive, c.exclude().callee_types));
}
else if (c.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> dependencies;

View File

@@ -28,6 +28,7 @@
#include "config/config.h"
#include "diagram.h"
#include "include_diagram/model/diagram.h"
#include "sequence_diagram/model/participant.h"
#include "source_file.h"
#include "tvl.h"
@@ -104,6 +105,9 @@ public:
virtual tvl::value_t match(
const diagram &d, const class_diagram::model::class_member &m) const;
virtual tvl::value_t match(
const diagram &d, const sequence_diagram::model::participant &p) const;
bool is_inclusive() const;
bool is_exclusive() const;
@@ -122,6 +126,9 @@ struct anyof_filter : public filter_visitor {
tvl::value_t match(
const diagram &d, const common::model::element &e) const override;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
tvl::value_t match(
const diagram &d, const common::model::source_file &e) const override;
@@ -192,6 +199,21 @@ private:
std::vector<config::method_type> method_types_;
};
/**
* Sequence diagram callee type filter.
*/
struct callee_filter : public filter_visitor {
callee_filter(filter_t type, std::vector<config::callee_type> callee_types);
~callee_filter() override = default;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
private:
std::vector<config::callee_type> callee_types_;
};
/**
* Match element based on whether it is a subclass of a set of base classes,
* or one of them.

View File

@@ -79,10 +79,37 @@ std::string to_string(method_type mt)
return "deleted";
case method_type::static_:
return "static";
default:
assert(false);
return "";
}
assert(false);
return "";
}
std::string to_string(callee_type mt)
{
switch (mt) {
case callee_type::constructor:
return "constructor";
case callee_type::assignment:
return "assignment";
case callee_type::operator_:
return "operator";
case callee_type::defaulted:
return "defaulted";
case callee_type::static_:
return "static";
case callee_type::method:
return "method";
case callee_type::function:
return "function";
case callee_type::function_template:
return "function_template";
case callee_type::lambda:
return "lambda";
}
assert(false);
return "";
}
std::string to_string(const comment_parser_t cp)

View File

@@ -63,6 +63,21 @@ enum class method_type {
std::string to_string(method_type mt);
/*! Types of call expressions, which can be used in sequence diagram filters */
enum class callee_type {
constructor,
assignment,
operator_,
defaulted,
static_,
method,
function,
function_template,
lambda
};
std::string to_string(callee_type mt);
/*! How packages in diagrams should be generated */
enum class package_type_t {
kNamespace, /*!< From namespaces */
@@ -313,6 +328,23 @@ struct filter {
* ```
*/
std::vector<method_type> method_types;
/*! @brief Callee types filter
*
* This filter allows to filter sequence diagram calls by callee types.
*
* @see method_type
*
* Example:
*
* ```yaml
* exclude:
* callee_types:
* - constructor
* - operator
* ```
*/
std::vector<callee_type> callee_types;
};
enum class hint_t { up, down, left, right, together, row, column };

View File

@@ -24,6 +24,7 @@ using clanguml::common::namespace_or_regex;
using clanguml::common::string_or_regex;
using clanguml::common::model::access_t;
using clanguml::common::model::relationship_t;
using clanguml::config::callee_type;
using clanguml::config::class_diagram;
using clanguml::config::config;
using clanguml::config::diagram_template;
@@ -252,6 +253,38 @@ template <> struct convert<method_type> {
}
};
//
// config callee_type decoder
//
template <> struct convert<callee_type> {
static bool decode(const Node &node, callee_type &rhs)
{
const auto &val = node.as<std::string>();
if (val == to_string(callee_type::constructor))
rhs = callee_type::constructor;
else if (val == to_string(callee_type::assignment))
rhs = callee_type::assignment;
else if (val == to_string(callee_type::operator_))
rhs = callee_type::operator_;
else if (val == to_string(callee_type::defaulted))
rhs = callee_type::defaulted;
else if (val == to_string(callee_type::static_))
rhs = callee_type::static_;
else if (val == to_string(callee_type::function))
rhs = callee_type::function;
else if (val == to_string(callee_type::function_template))
rhs = callee_type::function_template;
else if (val == to_string(callee_type::method))
rhs = callee_type::method;
else if (val == to_string(callee_type::lambda))
rhs = callee_type::lambda;
else
return false;
return true;
}
};
//
// config relationship_t decoder
//
@@ -432,6 +465,10 @@ template <> struct convert<filter> {
if (node["paths"])
rhs.paths = node["paths"].as<decltype(rhs.paths)>();
if (node["callee_types"])
rhs.callee_types =
node["callee_types"].as<decltype(rhs.callee_types)>();
return true;
}
};

View File

@@ -82,6 +82,12 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const method_type &m)
return out;
}
YAML::Emitter &operator<<(YAML::Emitter &out, const callee_type &m)
{
out << to_string(m);
return out;
}
YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f)
{
out << YAML::BeginMap;
@@ -112,7 +118,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f)
out << YAML::Key << "subclasses" << YAML::Value << f.subclasses;
if (!f.parents.empty())
out << YAML::Key << "parents" << YAML::Value << f.parents;
if (!f.method_types.empty())
out << YAML::Key << "callee_types" << YAML::Value << f.callee_types;
out << YAML::EndMap;
return out;
}

View File

@@ -253,9 +253,15 @@ void generator::process_call_message(const model::message &m,
std::vector<common::model::diagram_element::id_t> &visited) const
{
const auto &to = m_model.get_participant<model::participant>(m.to());
if (!to || to.value().skip())
return;
if (!m_model.should_include(to.value())) {
LOG_DBG("Excluding call from '{}' to '{}'", m.from(), m.to());
return;
}
visited.push_back(m.from());
LOG_DBG("Generating message {} --> {}", m.from(), m.to());

View File

@@ -142,9 +142,15 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
if (!to || to.value().skip())
continue;
if (!m_model.should_include(to.value())) {
LOG_DBG("Excluding call from [{}] to {} [{}]", m.from(),
to.value().full_name(false), m.to());
continue;
}
visited.push_back(m.from());
LOG_DBG("Generating message {} --> {}", m.from(), m.to());
LOG_DBG("Generating message [{}] --> [{}]", m.from(), m.to());
generate_call(m, ostr);

View File

@@ -18,6 +18,8 @@
#include "diagram.h"
#include "common/model/diagram_filter.h"
#include <functional>
#include <memory>
@@ -171,6 +173,14 @@ std::set<common::model::diagram_element::id_t> &diagram::active_participants()
return active_participants_;
}
bool diagram::should_include(
const sequence_diagram::model::participant &p) const
{
return filter().should_include(p) &&
filter().should_include(
dynamic_cast<const common::model::source_location &>(p));
}
void diagram::print() const
{
LOG_TRACE(" --- Participants ---");

View File

@@ -74,7 +74,7 @@ public:
*/
template <typename T>
common::optional_ref<T> get_participant(
common::model::diagram_element::id_t id)
common::model::diagram_element::id_t id) const
{
if (participants_.find(id) == participants_.end()) {
return {};
@@ -202,6 +202,16 @@ public:
*/
void print() const;
// Implicitly import should_include overloads from base class
using common::model::diagram::should_include;
/**
* @brief Convenience `should_include` overload for participant
* @param p Participant model
* @return True, if the participant should be included in the diagram
*/
bool should_include(const sequence_diagram::model::participant &p) const;
private:
/**
* This method checks the last messages in sequence (current_messages),

View File

@@ -164,6 +164,22 @@ std::string method::alias() const
return fmt::format("C_{:022}", class_id_);
}
bool method::is_constructor() const { return is_constructor_; }
void method::is_constructor(bool c) { is_constructor_ = c; }
bool method::is_defaulted() const { return is_defaulted_; }
void method::is_defaulted(bool d) { is_defaulted_ = d; }
bool method::is_assignment() const { return is_assignment_; }
void method::is_assignment(bool a) { is_assignment_ = a; }
bool method::is_operator() const { return is_operator_; }
void method::is_operator(bool o) { is_operator_ = o; }
void method::set_method_name(const std::string &name) { method_name_ = name; }
void method::set_class_id(diagram_element::id_t id) { class_id_ = id; }

View File

@@ -401,10 +401,70 @@ struct method : public function {
*/
std::string to_string() const override;
/**
* @brief Check, if the method is a constructor
*
* @return True, if the method is a constructor
*/
bool is_constructor() const;
/**
* @brief Set whether the method is a constructor
*
* @param v True, if the method is a constructor
*/
void is_constructor(bool c);
/**
* @brief Check, if the method is defaulted
*
* @return True, if the method is defaulted
*/
bool is_defaulted() const;
/**
* @brief Set whether the method is defaulted
*
* @param v True, if the method is defaulted
*/
void is_defaulted(bool c);
/**
* @brief Check, if the method is an assignment operator
*
* @return True, if the method is an assignment operator
*/
bool is_assignment() const;
/**
* @brief Set whether the method is an assignment operator
*
* @param v True, if the method is an assignment operator
*/
void is_assignment(bool a);
/**
* @brief Check, if the method is an operator
*
* @return True, if the method is an operator
*/
bool is_operator() const;
/**
* @brief Set whether the method is an operator
*
* @param v True, if the method is an operator
*/
void is_operator(bool o);
private:
diagram_element::id_t class_id_{};
std::string method_name_;
std::string class_full_name_;
bool is_constructor_{false};
bool is_defaulted_{false};
bool is_assignment_{false};
bool is_operator_{false};
};
/**

View File

@@ -897,14 +897,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
// message source rather then enclosing context
// Unless the lambda is declared in a function or method call
if (context().lambda_caller_id() != 0) {
if (!std::holds_alternative<clang::CallExpr *>(
context().current_callexpr())) {
m.set_from(context().lambda_caller_id());
}
else {
LOG_DBG("Current lambda declaration is passed to a method or "
"function - keep the original caller id");
}
m.set_from(context().lambda_caller_id());
}
if (context().is_expr_in_current_control_statement_condition(expr)) {
@@ -1009,7 +1002,11 @@ bool translation_unit_visitor::VisitCXXConstructExpr(
using clanguml::sequence_diagram::model::activity;
using clanguml::sequence_diagram::model::message;
if (!should_include(expr->getConstructor()))
if (expr == nullptr)
return true;
if (const auto *ctor = expr->getConstructor();
ctor != nullptr && !should_include(ctor))
return true;
LOG_TRACE("Visiting cxx construct expression at {} [caller_id = {}]",
@@ -1021,14 +1018,7 @@ bool translation_unit_visitor::VisitCXXConstructExpr(
set_source_location(*expr, m);
if (context().lambda_caller_id() != 0) {
if (!std::holds_alternative<clang::CallExpr *>(
context().current_callexpr())) {
m.set_from(context().lambda_caller_id());
}
else {
LOG_DBG("Current lambda declaration is passed to a method or "
"function - keep the original caller id");
}
m.set_from(context().lambda_caller_id());
}
if (context().is_expr_in_current_control_statement_condition(expr)) {
@@ -1427,7 +1417,7 @@ translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls)
c.set_id(common::to_id(c.full_name(false)));
// TODO: Check if lambda is declared as an argument passed to a
// function/method call
// function/method call
}
else {
LOG_WARN("Cannot find parent declaration for lambda {}",
@@ -2303,6 +2293,16 @@ translation_unit_visitor::create_method_model(clang::CXXMethodDecl *declaration)
method_model_ptr->set_name(ns.name());
ns.pop_back();
method_model_ptr->is_defaulted(declaration->isDefaulted());
method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
declaration->isMoveAssignmentOperator());
method_model_ptr->is_const(declaration->isConst());
method_model_ptr->is_static(declaration->isStatic());
method_model_ptr->is_static(declaration->isStatic());
method_model_ptr->is_operator(declaration->isOverloadedOperator());
method_model_ptr->is_constructor(
clang::dyn_cast<clang::CXXConstructorDecl>(declaration) != nullptr);
clang::Decl *parent_decl = declaration->getParent();
if (context().current_class_template_decl_ != nullptr)