Added combine_free_functions_into_file_participants sequence diagram option
This commit is contained in:
@@ -101,6 +101,8 @@ void inheritable_diagram_options::inherit(
|
|||||||
base_directory.override(parent.base_directory);
|
base_directory.override(parent.base_directory);
|
||||||
relative_to.override(parent.relative_to);
|
relative_to.override(parent.relative_to);
|
||||||
comment_parser.override(parent.comment_parser);
|
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(
|
std::string inheritable_diagram_options::simplify_template_type(
|
||||||
@@ -589,6 +591,8 @@ template <> struct convert<sequence_diagram> {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
get_option(node, rhs.start_from);
|
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();
|
rhs.initialize_type_aliases();
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ struct inheritable_diagram_options {
|
|||||||
option<type_aliases_t> type_aliases{"type_aliases"};
|
option<type_aliases_t> type_aliases{"type_aliases"};
|
||||||
option<comment_parser_t> comment_parser{
|
option<comment_parser_t> comment_parser{
|
||||||
"comment_parser", comment_parser_t::plain};
|
"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);
|
void inherit(const inheritable_diagram_options &parent);
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,25 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
|||||||
message = dynamic_cast<const model::function &>(to.value())
|
message = dynamic_cast<const model::function &>(to.value())
|
||||||
.message_name(model::function::message_render_mode::full);
|
.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) << " "
|
<< 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,
|
LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from,
|
||||||
m.from, to, m.to);
|
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 &from = m_model.get_participant<model::participant>(m.from);
|
||||||
const auto &to = m_model.get_participant<model::participant>(m.to);
|
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)
|
<< 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);
|
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 (m_model.sequences.find(m.to) != m_model.sequences.end()) {
|
||||||
if (std::find(visited.begin(), visited.end(), m.to) ==
|
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();
|
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);
|
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 {
|
else {
|
||||||
ostr << "participant \""
|
ostr << "participant \""
|
||||||
<< m_config.using_namespace().relative(
|
<< m_config.using_namespace().relative(
|
||||||
@@ -192,6 +242,8 @@ void generator::generate(std::ostream &ostr) const
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use this to break out of recurrent loops
|
||||||
std::vector<common::model::diagram_element::id_t>
|
std::vector<common::model::diagram_element::id_t>
|
||||||
visited_participants;
|
visited_participants;
|
||||||
|
|
||||||
@@ -199,20 +251,22 @@ void generator::generate(std::ostream &ostr) const
|
|||||||
m_model.get_participant<model::participant>(start_from);
|
m_model.get_participant<model::participant>(start_from);
|
||||||
|
|
||||||
if (!from.has_value()) {
|
if (!from.has_value()) {
|
||||||
LOG_WARN(
|
LOG_WARN("Failed to find participant {} for start_from "
|
||||||
"Failed to find participant {} for start_from condition",
|
"condition",
|
||||||
sf.location);
|
sf.location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_participant(ostr, start_from);
|
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(
|
generate_activity(
|
||||||
m_model.sequences[start_from], ostr, visited_participants);
|
m_model.sequences[start_from], ostr, visited_participants);
|
||||||
|
|
||||||
ostr << "deactivate " << from.value().alias() << std::endl;
|
ostr << "deactivate " << from_alias << std::endl;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO: Add support for other sequence start location types
|
// TODO: Add support for other sequence start location types
|
||||||
@@ -225,4 +279,17 @@ void generator::generate(std::ostream &ostr) const
|
|||||||
ostr << "@enduml" << std::endl;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ private:
|
|||||||
std::string render_name(std::string name) const;
|
std::string render_name(std::string name) const;
|
||||||
|
|
||||||
mutable std::set<common::id_t> generated_participants_;
|
mutable std::set<common::id_t> generated_participants_;
|
||||||
|
std::string generate_alias(const model::participant &participant) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(bool relative = true) const override;
|
||||||
|
|
||||||
std::string full_name_no_ns() 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" : "");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,6 +376,8 @@ bool translation_unit_visitor::VisitFunctionDecl(clang::FunctionDecl *f)
|
|||||||
|
|
||||||
set_unique_id(f->getID(), f_ptr->id());
|
set_unique_id(f->getID(), f_ptr->id());
|
||||||
|
|
||||||
|
set_source_location(*f, *f_ptr);
|
||||||
|
|
||||||
// TODO: Handle overloaded functions with different arguments
|
// TODO: Handle overloaded functions with different arguments
|
||||||
diagram().add_participant(std::move(f_ptr));
|
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_unique_id(f->getID(), f_ptr->id());
|
||||||
|
|
||||||
|
set_source_location(*f, *f_ptr);
|
||||||
|
|
||||||
// TODO: Handle overloaded functions with different arguments
|
// TODO: Handle overloaded functions with different arguments
|
||||||
diagram().add_participant(std::move(f_ptr));
|
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)));
|
f_ptr->set_id(common::to_id(f_ptr->full_name(false)));
|
||||||
|
|
||||||
|
set_source_location(*function_template, *f_ptr);
|
||||||
|
|
||||||
context().update(function_template);
|
context().update(function_template);
|
||||||
|
|
||||||
context().set_caller_id(f_ptr->id());
|
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());
|
get_unique_id(template_declaration->getID()).value());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LOG_WARN("Cannot generate call due to unresolvable "
|
LOG_DBG("Skipping call due to unresolvable "
|
||||||
"CXXDependentScopeMemberExpr");
|
"CXXDependentScopeMemberExpr at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
16
tests/t20017/.clang-uml
Normal file
16
tests/t20017/.clang-uml
Normal 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()"
|
||||||
9
tests/t20017/include/t20017_a.h
Normal file
9
tests/t20017/include/t20017_a.h
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
tests/t20017/include/t20017_b.h
Normal file
9
tests/t20017/include/t20017_b.h
Normal 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
8
tests/t20017/t20017.cc
Normal 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
7
tests/t20017/t20017_b.cc
Normal 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
51
tests/t20017/test_case.h
Normal 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);
|
||||||
|
}
|
||||||
@@ -263,6 +263,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20014/test_case.h"
|
#include "t20014/test_case.h"
|
||||||
#include "t20015/test_case.h"
|
#include "t20015/test_case.h"
|
||||||
#include "t20016/test_case.h"
|
#include "t20016/test_case.h"
|
||||||
|
#include "t20017/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ test_cases:
|
|||||||
- name: t20016
|
- name: t20016
|
||||||
title: Template method specialization sequence diagram test case
|
title: Template method specialization sequence diagram test case
|
||||||
description:
|
description:
|
||||||
|
- name: t20017
|
||||||
|
title: Test case for combine_free_functions_into_file_participants option
|
||||||
|
description:
|
||||||
Package diagrams:
|
Package diagrams:
|
||||||
- name: t30001
|
- name: t30001
|
||||||
title: Basic package diagram test case
|
title: Basic package diagram test case
|
||||||
|
|||||||
Reference in New Issue
Block a user