Added combine_free_functions_into_file_participants sequence diagram option

This commit is contained in:
Bartek Kryza
2022-12-09 00:10:38 +01:00
parent 6478cffa27
commit caf0ae7928
14 changed files with 212 additions and 12 deletions

View File

@@ -101,6 +101,8 @@ void inheritable_diagram_options::inherit(
base_directory.override(parent.base_directory);
relative_to.override(parent.relative_to);
comment_parser.override(parent.comment_parser);
combine_free_functions_into_file_participants.override(
combine_free_functions_into_file_participants);
}
std::string inheritable_diagram_options::simplify_template_type(
@@ -589,6 +591,8 @@ template <> struct convert<sequence_diagram> {
return false;
get_option(node, rhs.start_from);
get_option(node, rhs.combine_free_functions_into_file_participants);
get_option(node, rhs.relative_to);
rhs.initialize_type_aliases();

View File

@@ -142,6 +142,8 @@ struct inheritable_diagram_options {
option<type_aliases_t> type_aliases{"type_aliases"};
option<comment_parser_t> comment_parser{
"comment_parser", comment_parser_t::plain};
option<bool> combine_free_functions_into_file_participants{
"combine_free_functions_into_file_participants", false};
void inherit(const inheritable_diagram_options &parent);

View File

@@ -62,10 +62,25 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
message = dynamic_cast<const model::function &>(to.value())
.message_name(model::function::message_render_mode::full);
}
else if (m_config.combine_free_functions_into_file_participants()) {
if (to.value().type_name() == "function") {
message =
dynamic_cast<const model::function &>(to.value())
.message_name(model::function::message_render_mode::full);
}
else if (to.value().type_name() == "function_template") {
message =
dynamic_cast<const model::function_template &>(to.value())
.message_name(model::function::message_render_mode::full);
}
}
ostr << from.value().alias() << " "
const std::string from_alias = generate_alias(from.value());
const std::string to_alias = generate_alias(to.value());
ostr << from_alias << " "
<< common::generators::plantuml::to_plantuml(message_t::kCall) << " "
<< to.value().alias() << " : " << message << std::endl;
<< to_alias << " : " << message << std::endl;
LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from,
m.from, to, m.to);
@@ -79,9 +94,13 @@ void generator::generate_return(const message &m, std::ostream &ostr) const
const auto &from = m_model.get_participant<model::participant>(m.from);
const auto &to = m_model.get_participant<model::participant>(m.to);
ostr << to.value().alias() << " "
const std::string from_alias = generate_alias(from.value());
const std::string to_alias = generate_alias(to.value());
ostr << to_alias << " "
<< common::generators::plantuml::to_plantuml(message_t::kReturn)
<< " " << from.value().alias() << '\n';
<< " " << from_alias << '\n';
}
}
@@ -99,7 +118,9 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
generate_call(m, ostr);
ostr << "activate " << to.value().alias() << std::endl;
std::string to_alias = generate_alias(to.value());
ostr << "activate " << to_alias << std::endl;
if (m_model.sequences.find(m.to) != m_model.sequences.end()) {
if (std::find(visited.begin(), visited.end(), m.to) ==
@@ -117,7 +138,7 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
visited.pop_back();
ostr << "deactivate " << to.value().alias() << std::endl;
ostr << "deactivate " << to_alias << std::endl;
}
}
@@ -152,6 +173,35 @@ void generator::generate_participant(std::ostream &ostr, common::id_t id) const
generated_participants_.emplace(class_id);
}
else if ((participant.type_name() == "function" ||
participant.type_name() == "function_template") &&
m_config.combine_free_functions_into_file_participants()) {
// Create a single participant for all functions declared in a
// single file
const auto &file_path =
m_model.get_participant<model::function>(participant_id)
.value()
.file();
assert(!file_path.empty());
const auto file_id = common::to_id(file_path);
if (is_participant_generated(file_id))
return;
[[maybe_unused]] const auto &relative_to =
std::filesystem::canonical(m_config.relative_to());
auto participant_name = std::filesystem::relative(
std::filesystem::path{file_path}, relative_to)
.string();
ostr << "participant \"" << render_name(participant_name)
<< "\" as " << fmt::format("C_{:022}", file_id) << '\n';
generated_participants_.emplace(file_id);
}
else {
ostr << "participant \""
<< m_config.using_namespace().relative(
@@ -192,6 +242,8 @@ void generator::generate(std::ostream &ostr) const
break;
}
}
// Use this to break out of recurrent loops
std::vector<common::model::diagram_element::id_t>
visited_participants;
@@ -199,20 +251,22 @@ void generator::generate(std::ostream &ostr) const
m_model.get_participant<model::participant>(start_from);
if (!from.has_value()) {
LOG_WARN(
"Failed to find participant {} for start_from condition",
LOG_WARN("Failed to find participant {} for start_from "
"condition",
sf.location);
continue;
}
generate_participant(ostr, start_from);
ostr << "activate " << from.value().alias() << std::endl;
std::string from_alias = generate_alias(from.value());
ostr << "activate " << from_alias << std::endl;
generate_activity(
m_model.sequences[start_from], ostr, visited_participants);
ostr << "deactivate " << from.value().alias() << std::endl;
ostr << "deactivate " << from_alias << std::endl;
}
else {
// TODO: Add support for other sequence start location types
@@ -225,4 +279,17 @@ void generator::generate(std::ostream &ostr) const
ostr << "@enduml" << std::endl;
}
std::string generator::generate_alias(
const model::participant &participant) const
{
if ((participant.type_name() == "function" ||
participant.type_name() == "function_template") &&
m_config.combine_free_functions_into_file_participants()) {
const auto file_id = common::to_id(participant.file());
return fmt::format("C_{:022}", file_id);
}
return participant.alias();
}
}

View File

@@ -66,6 +66,7 @@ private:
std::string render_name(std::string name) const;
mutable std::set<common::id_t> generated_participants_;
std::string generate_alias(const model::participant &participant) const;
};
}

View File

@@ -246,6 +246,21 @@ struct function_template : public function, public template_trait {
std::string full_name(bool relative = true) const override;
std::string full_name_no_ns() const override;
std::string message_name(message_render_mode mode) const override
{
std::ostringstream s;
render_template_params(s, using_namespace(), true);
std::string template_params = s.str();
if (mode == message_render_mode::no_arguments) {
return fmt::format("{}{}(){}", name(), template_params,
is_const() ? " const" : "");
}
return fmt::format("{}{}({}){}", name(), template_params,
fmt::join(parameters(), ","), is_const() ? " const" : "");
}
};
}

View File

@@ -376,6 +376,8 @@ bool translation_unit_visitor::VisitFunctionDecl(clang::FunctionDecl *f)
set_unique_id(f->getID(), f_ptr->id());
set_source_location(*f, *f_ptr);
// TODO: Handle overloaded functions with different arguments
diagram().add_participant(std::move(f_ptr));
}
@@ -401,6 +403,8 @@ bool translation_unit_visitor::VisitFunctionDecl(clang::FunctionDecl *f)
set_unique_id(f->getID(), f_ptr->id());
set_source_location(*f, *f_ptr);
// TODO: Handle overloaded functions with different arguments
diagram().add_participant(std::move(f_ptr));
}
@@ -437,6 +441,8 @@ bool translation_unit_visitor::VisitFunctionTemplateDecl(
f_ptr->set_id(common::to_id(f_ptr->full_name(false)));
set_source_location(*function_template, *f_ptr);
context().update(function_template);
context().set_caller_id(f_ptr->id());
@@ -784,8 +790,9 @@ bool translation_unit_visitor::process_class_template_method_call_expression(
get_unique_id(template_declaration->getID()).value());
}
else {
LOG_WARN("Cannot generate call due to unresolvable "
"CXXDependentScopeMemberExpr");
LOG_DBG("Skipping call due to unresolvable "
"CXXDependentScopeMemberExpr at {}",
expr->getBeginLoc().printToString(source_manager()));
}
return true;

16
tests/t20017/.clang-uml Normal file
View File

@@ -0,0 +1,16 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t20017_sequence:
type: sequence
combine_free_functions_into_file_participants: true
relative_to: ../../tests/t20017
glob:
- ../../tests/t20017/t20017.cc
include:
namespaces:
- clanguml::t20017
using_namespace:
- clanguml::t20017
start_from:
- function: "clanguml::t20017::tmain()"

View File

@@ -0,0 +1,9 @@
#pragma once
namespace clanguml {
namespace t20017 {
int a1(int x, int y) { return x + y; }
int a2(int x, int y) { return x - y; }
int a3(int x, int y) { return x * y; }
}
}

View File

@@ -0,0 +1,9 @@
#pragma once
namespace clanguml {
namespace t20017 {
int b1(int x, int y);
template <typename T> T b2(T x, T y) { return x / y; }
}
}

8
tests/t20017/t20017.cc Normal file
View File

@@ -0,0 +1,8 @@
#include "include/t20017_a.h"
#include "include/t20017_b.h"
namespace clanguml {
namespace t20017 {
int tmain() { return b2(a1(a2(a3(1, 2), b1(3, 4)), 5), 6); }
}
}

7
tests/t20017/t20017_b.cc Normal file
View File

@@ -0,0 +1,7 @@
#include "include/t20017_b.h"
namespace clanguml {
namespace t20017 {
int b1(int x, int y) { return x - y; }
}
}

51
tests/t20017/test_case.h Normal file
View File

@@ -0,0 +1,51 @@
/**
* tests/t20017/test_case.h
*
* 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("t20017", "[test-case][sequence]")
{
auto [config, db] = load_config("t20017");
auto diagram = config.diagrams["t20017_sequence"];
REQUIRE(diagram->name == "t20017_sequence");
auto model = generate_sequence_diagram(*db, diagram);
REQUIRE(model->name() == "t20017_sequence");
auto puml = generate_sequence_puml(diagram, *model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
// Check if all calls exist
REQUIRE_THAT(puml,
HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a1(int,int)"));
REQUIRE_THAT(
puml, HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a2(int,int)"));
REQUIRE_THAT(puml,
HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a3(int,int)"));
REQUIRE_THAT(puml,
HasCall(_A("t20017.cc"), _A("include/t20017_b.h"), "b1(int,int)"));
REQUIRE_THAT(puml,
HasCall(_A("t20017.cc"), _A("include/t20017_b.h"), "b2<int>(int,int)"));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
}

View File

@@ -263,6 +263,7 @@ using namespace clanguml::test::matchers;
#include "t20014/test_case.h"
#include "t20015/test_case.h"
#include "t20016/test_case.h"
#include "t20017/test_case.h"
///
/// Package diagram tests

View File

@@ -196,6 +196,9 @@ test_cases:
- name: t20016
title: Template method specialization sequence diagram test case
description:
- name: t20017
title: Test case for combine_free_functions_into_file_participants option
description:
Package diagrams:
- name: t30001
title: Basic package diagram test case