Added sequence diagram model cleanup step to remove empty block statements

This commit is contained in:
Bartek Kryza 2023-07-02 17:56:15 +02:00
parent 9ada158828
commit ed88fcd39d
No known key found for this signature in database
GPG Key ID: 6CDA4566635E93B1
15 changed files with 160 additions and 46 deletions

View File

@ -359,6 +359,8 @@ std::unique_ptr<DiagramModel> generate(const common::compilation_database &db,
diagram->set_complete(true);
diagram->finalize();
return diagram;
}

View File

@ -59,6 +59,8 @@ void diagram::set_complete(bool complete) { complete_ = complete; }
bool diagram::complete() const { return complete_; }
void diagram::finalize() { }
bool diagram::should_include(const element &e) const
{
if (filter_.get() == nullptr)

View File

@ -124,12 +124,24 @@ public:
void set_complete(bool complete);
/**
* Whether the diagram is complete.
* @brief Whether the diagram is complete.
*
* This flag is set to true, when all translation units for this diagram
* have been visited.
*
* @return Diagram completion status.
*/
bool complete() const;
/**
* @brief Once the diagram is complete, run any final processing.
*
* This method should be overriden by specific diagram models to do some
* final tasks like cleaning up the model (e.g. some filters only work
* on completed diagrams).
*/
virtual void finalize();
// TODO: refactor to a template method
bool should_include(const element &e) const;
bool should_include(const namespace_ &ns) const;

View File

@ -376,6 +376,7 @@ 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::function;
using sequence_diagram::model::method;
using sequence_diagram::model::participant;
@ -391,6 +392,10 @@ tvl::value_t callee_filter::match(
tvl::value_t res = tvl::any_of(
callee_types_.begin(), callee_types_.end(), [&p, is_lambda](auto ct) {
auto is_function = [](const participant *p) {
return dynamic_cast<const function *>(p) != nullptr;
};
switch (ct) {
case config::callee_type::method:
return p.type_name() == "method";
@ -401,12 +406,12 @@ tvl::value_t callee_filter::match(
return p.type_name() == "method" &&
((method &)p).is_assignment();
case config::callee_type::operator_:
return p.type_name() == "method" && ((method &)p).is_operator();
return is_function(&p) && ((function &)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();
return is_function(&p) && ((function &)p).is_static();
case config::callee_type::function:
return p.type_name() == "function";
case config::callee_type::function_template:

View File

@ -252,16 +252,6 @@ nlohmann::json &generator::current_block_statement() const
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

@ -139,14 +139,6 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
if (m.type() == message_t::kCall) {
const auto &to =
m_model.get_participant<model::participant>(m.to());
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());

View File

@ -248,6 +248,81 @@ void diagram::fold_or_end_block_statement(message &&m,
current_messages.emplace_back(std::move(m));
}
}
void diagram::finalize()
{
// Apply diagram filters and remove any empty block statements
using common::model::message_t;
// First in each sequence (activity) filter out any remaining
// uninteresting calls
for (auto &[id, act] : sequences_) {
util::erase_if(act.messages(), [this](auto &m) {
if (m.type() != message_t::kCall)
return false;
const auto &to = get_participant<model::participant>(m.to());
if (!to || to.value().skip())
return true;
if (!should_include(to.value())) {
LOG_DBG("Excluding call from [{}] to {} [{}]", m.from(),
to.value().full_name(false), m.to());
return true;
}
return false;
});
}
// Now remove any empty block statements, e.g. if/endif
for (auto &[id, act] : sequences_) {
int64_t block_nest_level{0};
std::vector<std::vector<message>> block_message_stack;
// Add first stack level - this level will contain the filtered
// message sequence
block_message_stack.emplace_back();
// First create a recursive stack from the messages and
// message blocks (e.g. if statements)
for (auto &m : act.messages()) {
if (is_begin_block_message(m.type())) {
block_nest_level++;
block_message_stack.push_back({m});
}
else if (is_end_block_message(m.type())) {
block_nest_level--;
block_message_stack.back().push_back(m);
// Check the last stack for any calls, if yes, collapse it
// on the previous stack
if (std::count_if(block_message_stack.back().begin(),
block_message_stack.back().end(), [](auto &m) {
return m.type() == message_t::kCall;
}) > 0) {
std::copy(block_message_stack.back().begin(),
block_message_stack.back().end(),
std::back_inserter(
block_message_stack.at(block_nest_level)));
}
block_message_stack.pop_back();
assert(block_nest_level >= 0);
}
else {
block_message_stack.back().push_back(m);
}
}
act.messages().clear();
for (auto &m : block_message_stack[0]) {
act.add_message(m);
}
}
}
} // namespace clanguml::sequence_diagram::model
namespace clanguml::common::model {

View File

@ -212,6 +212,15 @@ public:
*/
bool should_include(const sequence_diagram::model::participant &p) const;
/**
* @brief Once the diagram is complete, run any final processing.
*
* This method should be overriden by specific diagram models to do some
* final tasks like cleaning up the model (e.g. some filters only work
* on completed diagrams).
*/
void finalize() override;
private:
/**
* This method checks the last messages in sequence (current_messages),
@ -233,6 +242,27 @@ private:
common::model::message_t statement_begin,
std::vector<message> &current_messages) const;
bool is_begin_block_message(common::model::message_t mt)
{
using common::model::message_t;
static std::set<message_t> block_begin_types{message_t::kIf,
message_t::kWhile, message_t::kDo, message_t::kFor, message_t::kTry,
message_t::kSwitch, message_t::kConditional};
return block_begin_types.count(mt) > 0;
};
bool is_end_block_message(common::model::message_t mt)
{
using common::model::message_t;
static std::set<message_t> block_end_types{message_t::kIfEnd,
message_t::kWhileEnd, message_t::kDoEnd, message_t::kForEnd,
message_t::kTryEnd, message_t::kSwitchEnd,
message_t::kConditionalEnd};
return block_end_types.count(mt) > 0;
};
bool started_{false};
std::map<common::model::diagram_element::id_t, activity> sequences_;

View File

@ -143,6 +143,10 @@ bool function::is_static() const { return is_static_; }
void function::is_static(bool s) { is_static_ = s; }
bool function::is_operator() const { return is_operator_; }
void function::is_operator(bool o) { is_operator_ = o; }
void function::add_parameter(const std::string &a) { parameters_.push_back(a); }
const std::vector<std::string> &function::parameters() const
@ -176,10 +180,6 @@ 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

@ -277,6 +277,20 @@ struct function : public participant {
*/
void is_static(bool s);
/**
* @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);
/**
* @brief Add a function parameter
*
@ -298,6 +312,7 @@ private:
bool is_const_{false};
bool is_void_{false};
bool is_static_{false};
bool is_operator_{false};
std::vector<std::string> parameters_;
};
@ -429,20 +444,6 @@ struct method : public function {
*/
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_;
@ -450,7 +451,6 @@ private:
bool is_constructor_{false};
bool is_defaulted_{false};
bool is_assignment_{false};
bool is_operator_{false};
};
/**

View File

@ -330,6 +330,8 @@ bool translation_unit_visitor::VisitFunctionDecl(
function_model_ptr->is_void(declaration->getReturnType()->isVoidType());
function_model_ptr->is_operator(declaration->isOverloadedOperator());
context().update(declaration);
context().set_caller_id(function_model_ptr->id());
@ -373,6 +375,9 @@ bool translation_unit_visitor::VisitFunctionTemplateDecl(
function_template_model->set_id(
common::to_id(function_template_model->full_name(false)));
function_template_model->is_operator(
declaration->getAsFunction()->isOverloadedOperator());
context().update(declaration);
context().set_caller_id(function_template_model->id());
@ -2260,6 +2265,7 @@ void translation_unit_visitor::finalize()
{
std::set<common::model::diagram_element::id_t> active_participants_unique;
// Change all active participants AST local ids to diagram global ids
for (auto id : diagram().active_participants()) {
if (local_ast_id_map_.find(id) != local_ast_id_map_.end()) {
active_participants_unique.emplace(local_ast_id_map_.at(id));
@ -2271,6 +2277,7 @@ void translation_unit_visitor::finalize()
diagram().active_participants() = std::move(active_participants_unique);
// Change all message callees AST local ids to diagram global ids
for (auto &[id, activity] : diagram().sequences()) {
for (auto &m : activity.messages()) {
if (local_ast_id_map_.find(m.to()) != local_ast_id_map_.end()) {

View File

@ -252,7 +252,7 @@ public:
int64_t local_id) const;
/**
* @brief Finalize diagram model
* @brief Finalize diagram model for this translation unit
*/
void finalize();

View File

@ -10,4 +10,4 @@ plantuml:
before:
- 'title clang-uml clanguml::class_diagram::generators::plantuml::generator sequence diagram'
start_from:
- function: "clanguml::class_diagram::generators::plantuml::generator::generate(std::ostream &)"
- function: "clanguml::class_diagram::generators::plantuml::generator::generate(std::ostream &) const"

View File

@ -1,6 +1,7 @@
type: sequence
combine_free_functions_into_file_participants: true
generate_method_arguments: none
debug_mode: true
glob:
- src/cli/cli_handler.cc
- src/config/config.cc
@ -14,6 +15,8 @@ exclude:
- r: "clanguml::config::option.*"
paths:
- src/util/util.h
callee_types:
- operator
using_namespace:
- clanguml
start_from:

View File

@ -7,12 +7,8 @@ using_namespace:
- clanguml
include:
namespaces:
- clang
- clanguml::common::generators
exclude:
namespaces:
- clanguml::model::tvl
- clanguml::decorators
paths:
- src/common/model/source_location.h
start_from: