Make sure sequence diagram messages generated during static variable initialization are rendered only once
This commit is contained in:
@@ -256,6 +256,13 @@ void generator::process_call_message(const model::message &m,
|
|||||||
{
|
{
|
||||||
visited.push_back(m.from());
|
visited.push_back(m.from());
|
||||||
|
|
||||||
|
if (m.in_static_declaration_context()) {
|
||||||
|
if (util::contains(already_generated_in_static_context_, m))
|
||||||
|
return;
|
||||||
|
|
||||||
|
already_generated_in_static_context_.push_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
LOG_DBG("Generating message {} --> {}", m.from(), m.to());
|
LOG_DBG("Generating message {} --> {}", m.from(), m.to());
|
||||||
|
|
||||||
generate_call(m, current_block_statement());
|
generate_call(m, current_block_statement());
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ private:
|
|||||||
|
|
||||||
mutable std::vector<std::reference_wrapper<nlohmann::json>>
|
mutable std::vector<std::reference_wrapper<nlohmann::json>>
|
||||||
block_statements_stack_;
|
block_statements_stack_;
|
||||||
|
|
||||||
|
mutable std::vector<model::message> already_generated_in_static_context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clanguml::sequence_diagram::generators::json
|
} // namespace clanguml::sequence_diagram::generators::json
|
||||||
|
|||||||
@@ -139,6 +139,13 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
|
|||||||
std::vector<common::model::diagram_element::id_t> &visited) const
|
std::vector<common::model::diagram_element::id_t> &visited) const
|
||||||
{
|
{
|
||||||
for (const auto &m : a.messages()) {
|
for (const auto &m : a.messages()) {
|
||||||
|
if (m.in_static_declaration_context()) {
|
||||||
|
if (util::contains(already_generated_in_static_context_, m))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
already_generated_in_static_context_.push_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
if (m.type() == message_t::kCall) {
|
if (m.type() == message_t::kCall) {
|
||||||
const auto &to =
|
const auto &to =
|
||||||
model().get_participant<model::participant>(m.to());
|
model().get_participant<model::participant>(m.to());
|
||||||
|
|||||||
@@ -137,9 +137,17 @@ private:
|
|||||||
*/
|
*/
|
||||||
std::string generate_alias(const model::participant &participant) const;
|
std::string generate_alias(const model::participant &participant) const;
|
||||||
|
|
||||||
mutable std::set<common::id_t> generated_participants_;
|
/**
|
||||||
|
* @brief Convert config to model message render mode.
|
||||||
|
*
|
||||||
|
* @return Method render mode.
|
||||||
|
*/
|
||||||
model::function::message_render_mode
|
model::function::message_render_mode
|
||||||
select_method_arguments_render_mode() const;
|
select_method_arguments_render_mode() const;
|
||||||
|
|
||||||
|
mutable std::set<common::id_t> generated_participants_;
|
||||||
|
|
||||||
|
mutable std::vector<model::message> already_generated_in_static_context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mermaid
|
} // namespace mermaid
|
||||||
|
|||||||
@@ -135,6 +135,13 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
|
|||||||
std::vector<common::model::diagram_element::id_t> &visited) const
|
std::vector<common::model::diagram_element::id_t> &visited) const
|
||||||
{
|
{
|
||||||
for (const auto &m : a.messages()) {
|
for (const auto &m : a.messages()) {
|
||||||
|
if (m.in_static_declaration_context()) {
|
||||||
|
if (util::contains(already_generated_in_static_context_, m))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
already_generated_in_static_context_.push_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
if (m.type() == message_t::kCall) {
|
if (m.type() == message_t::kCall) {
|
||||||
const auto &to =
|
const auto &to =
|
||||||
model().get_participant<model::participant>(m.to());
|
model().get_participant<model::participant>(m.to());
|
||||||
|
|||||||
@@ -139,9 +139,17 @@ 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_;
|
/**
|
||||||
|
* @brief Convert config to model message render mode.
|
||||||
|
*
|
||||||
|
* @return Method render mode.
|
||||||
|
*/
|
||||||
model::function::message_render_mode
|
model::function::message_render_mode
|
||||||
select_method_arguments_render_mode() const;
|
select_method_arguments_render_mode() const;
|
||||||
|
|
||||||
|
mutable std::set<common::id_t> generated_participants_;
|
||||||
|
|
||||||
|
mutable std::vector<model::message> already_generated_in_static_context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace plantuml
|
} // namespace plantuml
|
||||||
|
|||||||
@@ -78,4 +78,14 @@ std::optional<std::string> message::condition_text() const
|
|||||||
return condition_text_;
|
return condition_text_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool message::in_static_declaration_context() const
|
||||||
|
{
|
||||||
|
return in_static_declaration_context_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void message::in_static_declaration_context(bool v)
|
||||||
|
{
|
||||||
|
in_static_declaration_context_ = v;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace clanguml::sequence_diagram::model
|
} // namespace clanguml::sequence_diagram::model
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::optional<std::string> condition_text() const;
|
std::optional<std::string> condition_text() const;
|
||||||
|
|
||||||
|
bool in_static_declaration_context() const;
|
||||||
|
|
||||||
|
void in_static_declaration_context(bool v);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
common::model::message_t type_{common::model::message_t::kNone};
|
common::model::message_t type_{common::model::message_t::kNone};
|
||||||
|
|
||||||
@@ -167,6 +171,8 @@ private:
|
|||||||
std::string return_type_{};
|
std::string return_type_{};
|
||||||
|
|
||||||
std::optional<std::string> condition_text_;
|
std::optional<std::string> condition_text_;
|
||||||
|
|
||||||
|
bool in_static_declaration_context_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clanguml::sequence_diagram::model
|
} // namespace clanguml::sequence_diagram::model
|
||||||
|
|||||||
@@ -936,6 +936,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
|
|
||||||
message m{message_t::kCall, context().caller_id()};
|
message m{message_t::kCall, context().caller_id()};
|
||||||
|
|
||||||
|
m.in_static_declaration_context(within_static_variable_declaration_ > 0);
|
||||||
|
|
||||||
set_source_location(*expr, m);
|
set_source_location(*expr, m);
|
||||||
|
|
||||||
// If we're currently inside a lambda expression, set it's id as
|
// If we're currently inside a lambda expression, set it's id as
|
||||||
@@ -1031,6 +1033,27 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::TraverseVarDecl(clang::VarDecl *decl)
|
||||||
|
{
|
||||||
|
LOG_TRACE("Traversing cxx variable declaration at {} [caller_id = {}]",
|
||||||
|
decl->getBeginLoc().printToString(source_manager()),
|
||||||
|
context().caller_id());
|
||||||
|
|
||||||
|
decl->dump();
|
||||||
|
|
||||||
|
decl->getInit()->dump();
|
||||||
|
|
||||||
|
if (decl->isStaticLocal())
|
||||||
|
within_static_variable_declaration_++;
|
||||||
|
|
||||||
|
RecursiveASTVisitor::TraverseVarDecl(decl);
|
||||||
|
|
||||||
|
if (decl->isStaticLocal())
|
||||||
|
within_static_variable_declaration_--;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::VisitCXXConstructExpr(
|
bool translation_unit_visitor::VisitCXXConstructExpr(
|
||||||
clang::CXXConstructExpr *expr)
|
clang::CXXConstructExpr *expr)
|
||||||
{
|
{
|
||||||
@@ -1051,8 +1074,12 @@ bool translation_unit_visitor::VisitCXXConstructExpr(
|
|||||||
expr->getBeginLoc().printToString(source_manager()),
|
expr->getBeginLoc().printToString(source_manager()),
|
||||||
context().caller_id());
|
context().caller_id());
|
||||||
|
|
||||||
|
expr->dump();
|
||||||
|
|
||||||
message m{message_t::kCall, context().caller_id()};
|
message m{message_t::kCall, context().caller_id()};
|
||||||
|
|
||||||
|
m.in_static_declaration_context(within_static_variable_declaration_ > 0);
|
||||||
|
|
||||||
set_source_location(*expr, m);
|
set_source_location(*expr, m);
|
||||||
|
|
||||||
if (context().lambda_caller_id() != 0) {
|
if (context().lambda_caller_id() != 0) {
|
||||||
@@ -2427,6 +2454,7 @@ bool translation_unit_visitor::should_include(const clang::CallExpr *expr) const
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
||||||
|
|
||||||
return diagram().should_include(common::model::source_file{expr_file});
|
return diagram().should_include(common::model::source_file{expr_file});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ public:
|
|||||||
|
|
||||||
bool TraverseCallExpr(clang::CallExpr *expr);
|
bool TraverseCallExpr(clang::CallExpr *expr);
|
||||||
|
|
||||||
|
bool TraverseVarDecl(clang::VarDecl *VD);
|
||||||
|
|
||||||
bool TraverseCXXMemberCallExpr(clang::CXXMemberCallExpr *expr);
|
bool TraverseCXXMemberCallExpr(clang::CXXMemberCallExpr *expr);
|
||||||
|
|
||||||
bool TraverseCXXOperatorCallExpr(clang::CXXOperatorCallExpr *expr);
|
bool TraverseCXXOperatorCallExpr(clang::CXXOperatorCallExpr *expr);
|
||||||
@@ -540,5 +542,9 @@ private:
|
|||||||
std::tuple<std::string /* field name */, common::model::relationship_t,
|
std::tuple<std::string /* field name */, common::model::relationship_t,
|
||||||
common::model::access_t>>
|
common::model::access_t>>
|
||||||
anonymous_struct_relationships_;
|
anonymous_struct_relationships_;
|
||||||
|
|
||||||
|
mutable unsigned within_static_variable_declaration_{0};
|
||||||
|
mutable std::set<const clang::Expr *>
|
||||||
|
already_visited_in_static_declaration_{};
|
||||||
};
|
};
|
||||||
} // namespace clanguml::sequence_diagram::visitor
|
} // namespace clanguml::sequence_diagram::visitor
|
||||||
|
|||||||
14
tests/t20037/.clang-uml
Normal file
14
tests/t20037/.clang-uml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
compilation_database_dir: ..
|
||||||
|
output_directory: diagrams
|
||||||
|
diagrams:
|
||||||
|
t20037_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- ../../tests/t20037/t20037.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20037
|
||||||
|
using_namespace:
|
||||||
|
- clanguml::t20037
|
||||||
|
from:
|
||||||
|
- function: "clanguml::t20037::tmain(int,char **)"
|
||||||
38
tests/t20037/t20037.cc
Normal file
38
tests/t20037/t20037.cc
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
namespace clanguml {
|
||||||
|
namespace t20037 {
|
||||||
|
|
||||||
|
struct A {
|
||||||
|
A()
|
||||||
|
: a{100}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int a;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
int get() { return b; }
|
||||||
|
|
||||||
|
int b{100};
|
||||||
|
};
|
||||||
|
|
||||||
|
B initb() { return B{}; }
|
||||||
|
|
||||||
|
int c() { return 1; }
|
||||||
|
|
||||||
|
int a()
|
||||||
|
{
|
||||||
|
static A a;
|
||||||
|
static B b = initb();
|
||||||
|
|
||||||
|
return a.a + b.get() + c();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tmain(int argc, char **argv)
|
||||||
|
{
|
||||||
|
auto a1 = a();
|
||||||
|
auto a2 = a();
|
||||||
|
auto a3 = a();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
tests/t20037/test_case.h
Normal file
75
tests/t20037/test_case.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20037/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2023 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("t20037", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20037");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20037_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20037_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20037_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(src);
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain(int,char **)"), _A("a()"), ""));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("initb()"), ""));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("B"), "get()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("c()"), ""));
|
||||||
|
|
||||||
|
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
std::vector<int> messages = {
|
||||||
|
FindMessage(j, "tmain(int,char **)", "a()", ""),
|
||||||
|
FindMessage(j, "a()", "initb()", ""),
|
||||||
|
FindMessage(j, "a()", "B", "get()"),
|
||||||
|
FindMessage(j, "a()", "c()", "")};
|
||||||
|
|
||||||
|
REQUIRE(std::is_sorted(messages.begin(), messages.end()));
|
||||||
|
|
||||||
|
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_mermaid(diagram, *model);
|
||||||
|
|
||||||
|
mermaid::SequenceDiagramAliasMatcher _A(src);
|
||||||
|
using mermaid::HasCall;
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain(int,char **)"), _A("a()"), ""));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("initb()"), ""));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("B"), "get()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("a()"), _A("c()"), ""));
|
||||||
|
|
||||||
|
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -423,6 +423,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20034/test_case.h"
|
#include "t20034/test_case.h"
|
||||||
#include "t20035/test_case.h"
|
#include "t20035/test_case.h"
|
||||||
#include "t20036/test_case.h"
|
#include "t20036/test_case.h"
|
||||||
|
#include "t20037/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -307,6 +307,9 @@ test_cases:
|
|||||||
- name: t20036
|
- name: t20036
|
||||||
title: Test case for rendering all call chains leading to an activity (to)
|
title: Test case for rendering all call chains leading to an activity (to)
|
||||||
description:
|
description:
|
||||||
|
- name: t20037
|
||||||
|
title: Test case checking if activities in static variable declarations appear only once
|
||||||
|
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