diff --git a/src/config/config.h b/src/config/config.h index 66e09771..4e25d8ac 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -543,6 +543,7 @@ struct sequence_diagram : public diagram { option> start_from{"start_from"}; option>> from_to{"from_to"}; + option> to{"to"}; }; /** diff --git a/src/config/schema.h b/src/config/schema.h index fad26094..a4d77dc6 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -196,6 +196,7 @@ types: participants_order: !optional [string] start_from: !optional [source_location_t] from_to: !optional [[source_location_t]] + to: !optional [source_location_t] package_diagram_t: type: !variant [package] # diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 00880276..0ba49e8f 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -575,6 +575,7 @@ template <> struct convert { get_option(node, rhs.start_from); get_option(node, rhs.from_to); + get_option(node, rhs.to); get_option(node, rhs.combine_free_functions_into_file_participants); get_option(node, rhs.generate_return_types); get_option(node, rhs.generate_condition_statements); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 94493631..893f0747 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -349,6 +349,7 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const sequence_diagram &c) out << YAML::Key << "type" << YAML::Value << c.type(); out << c.start_from; out << c.from_to; + out << c.to; out << dynamic_cast(c); out << YAML::EndMap; return out; diff --git a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc index 73b6278a..2fb34ff6 100644 --- a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc @@ -617,8 +617,8 @@ void generator::generate_diagram(nlohmann::json &parent) const const auto &from_location = ft.front(); const auto &to_location = ft.back(); - auto [from_activity_id, to_activity_id] = - model().get_from_to_activity_ids(from_location, to_location); + auto from_activity_id = model().get_from_activity_id(from_location); + auto to_activity_id = model().get_to_activity_id(to_location); if (from_activity_id == 0 || to_activity_id == 0) continue; @@ -655,6 +655,42 @@ void generator::generate_diagram(nlohmann::json &parent) const json_["sequences"].push_back(std::move(sequence)); } + for (const auto &to_location : config().to()) { + auto to_activity_id = model().get_to_activity_id(to_location); + + if (to_activity_id == 0) + continue; + + auto message_chains_unique = + model().get_all_from_to_message_chains(0, to_activity_id); + + nlohmann::json sequence; + sequence["to"]["location"] = to_location.location; + sequence["to"]["id"] = to_activity_id; + + block_statements_stack_.push_back(std::ref(sequence)); + + sequence["message_chains"] = nlohmann::json::array(); + + for (const auto &mc : message_chains_unique) { + nlohmann::json message_chain; + + block_statements_stack_.push_back(std::ref(message_chain)); + + for (const auto &m : mc) { + generate_call(m, current_block_statement()); + } + + block_statements_stack_.pop_back(); + + sequence["message_chains"].push_back(std::move(message_chain)); + } + + block_statements_stack_.pop_back(); + + json_["sequences"].push_back(std::move(sequence)); + } + for (const auto &sf : config().start_from()) { if (sf.location_type == location_t::function) { common::model::diagram_element::id_t start_from{0}; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index b80bbe1b..bcbed236 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -416,8 +416,8 @@ void generator::generate_diagram(std::ostream &ostr) const const auto &from_location = ft.front(); const auto &to_location = ft.back(); - auto [from_activity_id, to_activity_id] = - model().get_from_to_activity_ids(from_location, to_location); + auto from_activity_id = model().get_from_activity_id(from_location); + auto to_activity_id = model().get_to_activity_id(to_location); if (from_activity_id == 0 || to_activity_id == 0) continue; @@ -451,6 +451,43 @@ void generator::generate_diagram(std::ostream &ostr) const } } + for (const auto &to_location : config().to()) { + auto to_activity_id = model().get_to_activity_id(to_location); + + if (to_activity_id == 0) + continue; + + auto message_chains_unique = + model().get_all_from_to_message_chains(0, to_activity_id); + + bool first_separator_skipped{false}; + for (const auto &mc : message_chains_unique) { + if (!first_separator_skipped) + first_separator_skipped = true; + else + ostr << "====\n"; + + const auto from_activity_id = mc.front().from(); + + const auto &from = + model().get_participant(from_activity_id); + + if (from.value().type_name() == "method" || + config().combine_free_functions_into_file_participants()) { + generate_participant(ostr, from_activity_id); + ostr << "[->" + << " " << generate_alias(from.value()) << " : " + << from.value().message_name( + select_method_arguments_render_mode()) + << std::endl; + } + + for (const auto &m : mc) { + generate_call(m, ostr); + } + } + } + for (const auto &sf : config().start_from()) { if (sf.location_type == location_t::function) { common::model::diagram_element::id_t start_from{0}; diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 260dfbac..963843bd 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -209,31 +209,11 @@ std::vector diagram::list_start_from_values() const return result; } -std::pair -diagram::get_from_to_activity_ids(const config::source_location &from_location, +common::model::diagram_element::id_t diagram::get_to_activity_id( const config::source_location &to_location) const { - common::model::diagram_element::id_t from_activity{0}; common::model::diagram_element::id_t to_activity{0}; - for (const auto &[k, v] : sequences()) { - const auto &caller = *participants().at(v.from()); - std::string vfrom = caller.full_name(false); - if (vfrom == from_location.location) { - LOG_DBG("Found sequence diagram start point '{}': {}", vfrom, k); - from_activity = k; - break; - } - } - - if (from_activity == 0) { - LOG_WARN("Failed to find 'from' participant {} for start_from " - "condition", - from_location.location); - return {from_activity, to_activity}; - } - for (const auto &[k, v] : sequences()) { for (const auto &m : v.messages()) { if (m.type() != common::model::message_t::kCall) @@ -250,13 +230,36 @@ diagram::get_from_to_activity_ids(const config::source_location &from_location, } if (to_activity == 0) { - LOG_WARN("Failed to find 'to' participant {} for from_to " + LOG_WARN("Failed to find 'to' participant {} for to " "condition", to_location.location); - return {from_activity, to_activity}; } - return {from_activity, to_activity}; + return to_activity; +} + +common::model::diagram_element::id_t diagram::get_from_activity_id( + const config::source_location &from_location) const +{ + common::model::diagram_element::id_t from_activity{0}; + + for (const auto &[k, v] : sequences()) { + const auto &caller = *participants().at(v.from()); + std::string vfrom = caller.full_name(false); + if (vfrom == from_location.location) { + LOG_DBG("Found sequence diagram start point '{}': {}", vfrom, k); + from_activity = k; + break; + } + } + + if (from_activity == 0) { + LOG_WARN("Failed to find 'from' participant {} for from " + "condition", + from_location.location); + } + + return from_activity; } std::unordered_set diagram::get_all_from_to_message_chains( @@ -362,7 +365,8 @@ std::unordered_set diagram::get_all_from_to_message_chains( std::copy_if(message_chains.begin(), message_chains.end(), std::inserter(message_chains_unique, message_chains_unique.begin()), [from_activity](const message_chain_t &mc) { - return !mc.empty() && (mc.front().from() == from_activity); + return !mc.empty() && + (from_activity == 0 || (mc.front().from() == from_activity)); }); return message_chains_unique; diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index eb30b432..6e1a128d 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -241,6 +241,9 @@ public: /** * @brief Generate a list of message chains matching a from_to constraint * + * If 'from_activity' is 0, this method will return all message chains + * ending in 'to_activity'. + * * @param from_activity Source activity for from_to message chain * @param to_activity Target activity for from_to message chain * @return List of message chains @@ -250,16 +253,22 @@ public: common::model::diagram_element::id_t to_activity) const; /** - * @brief Get ids of from and to activities in from_to constraint + * @brief Get id of a 'to' activity * - * @param from_activity Source activity for from_to message chain - * @param to_activity Target activity for from_to message chain - * @return Pair of activity ids (0 if not found) + * @param to_location Target activity + * @return Activity id */ - std::pair - get_from_to_activity_ids(const config::source_location &from_activity, - const config::source_location &to_activity) const; + common::model::diagram_element::id_t get_to_activity_id( + const config::source_location &to_location) const; + + /** + * @brief Get id of a 'from' activity + * + * @param from_location Source activity + * @return Activity id + */ + common::model::diagram_element::id_t get_from_activity_id( + const config::source_location &from_location) const; /** * @brief Once the diagram is complete, run any final processing. diff --git a/tests/t20034/t20034.cc b/tests/t20034/t20034.cc index 369b6072..7f1a20d6 100644 --- a/tests/t20034/t20034.cc +++ b/tests/t20034/t20034.cc @@ -1,4 +1,5 @@ #include + namespace clanguml { namespace t20034 { struct A { diff --git a/tests/t20036/.clang-uml b/tests/t20036/.clang-uml new file mode 100644 index 00000000..f6bc00ff --- /dev/null +++ b/tests/t20036/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20036_sequence: + type: sequence + glob: + - ../../tests/t20036/t20036.cc + include: + namespaces: + - clanguml::t20036 + using_namespace: + - clanguml::t20036 + to: + - function: "clanguml::t20036::A::a2()" \ No newline at end of file diff --git a/tests/t20036/t20036.cc b/tests/t20036/t20036.cc new file mode 100644 index 00000000..d5b82efb --- /dev/null +++ b/tests/t20036/t20036.cc @@ -0,0 +1,44 @@ +#include + +namespace clanguml { +namespace t20036 { +struct A { + void a1() { } + void a2() { } + void a3() { } +}; + +struct B { + void b1() { a.a2(); } + void b2() { a.a2(); } + void b3() { a.a3(); } + + A a; +}; + +struct C { + void c1() { b.b1(); } + void c2() { b.b2(); } + void c3() + { + if (reinterpret_cast(&b) == 0xbadc0de) + c3(); + else + c2(); + } + + void c4() { b.b2(); } + + B b; +}; + +struct D { + void d1() { c.c2(); } + void d2() { c.c2(); } + void d3() { a.a2(); } + + A a; + C c; +}; +} +} \ No newline at end of file diff --git a/tests/t20036/test_case.h b/tests/t20036/test_case.h new file mode 100644 index 00000000..67381a9f --- /dev/null +++ b/tests/t20036/test_case.h @@ -0,0 +1,73 @@ +/** + * tests/t20036/test_case.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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("t20036", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20036"); + + auto diagram = config.diagrams["t20036_sequence"]; + + REQUIRE(diagram->name == "t20036_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20036_sequence"); + + { + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "c2()")); + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()")); + + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()")); + + REQUIRE_THAT(puml, HasCall(_A("D"), _A("A"), "a2()")); + + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b1()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()")); + + save_puml( + config.output_directory() + "/" + diagram->name + ".puml", puml); + } + + { + auto j = generate_sequence_json(diagram, *model); + + using namespace json; + + REQUIRE(HasMessageChain(j, + {{"C::c3()", "C::c2()", "void"}, {"C::c2()", "B::b2()", "void"}, + {"B::b2()", "A::a2()", "void"}})); + REQUIRE(HasMessageChain(j, + {{"C::c4()", "B::b2()", "void"}, {"B::b2()", "A::a2()", "void"}})); + REQUIRE(HasMessageChain(j, {{"D::d3()", "A::a2()", "void"}})); + REQUIRE(HasMessageChain(j, + {{"D::d1()", "C::c2()", "void"}, {"C::c2()", "B::b2()", "void"}, + {"B::b2()", "A::a2()", "void"}})); + REQUIRE(HasMessageChain(j, + {{"C::c1()", "B::b1()", "void"}, {"B::b1()", "A::a2()", "void"}})); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a87943b5..1c9b3919 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -349,6 +349,7 @@ using namespace clanguml::test::matchers; #include "t20033/test_case.h" #include "t20034/test_case.h" #include "t20035/test_case.h" +#include "t20036/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 0beedfda..bf07b136 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -299,11 +299,14 @@ test_cases: title: Control statement text in sequence diagram test case description: - name: t20034 - title: from_to sequence diagram test case + title: Test case for rendering all call chains from one activity to another (from_to) description: - name: t20035 title: from_to sequence diagram test case with free functions description: + - name: t20036 + title: Test case for rendering all call chains leading to an activity (to) + description: Package diagrams: - name: t30001 title: Basic package diagram test case