diff --git a/README.md b/README.md index 27a7dab9..93d69455 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Main features supported so far include: * Support for plain C99/C11 code (struct and units relationships) - [_example_](docs/test_cases/t00057.md) * C++20 concept constraints - [_example_](docs/test_cases/t00059.md) * **Sequence diagram generation** - * Generation of sequence diagram from specific method or function - [_example_](docs/test_cases/t00002.md) + * Generation of sequence diagram from specific method or function - [_example_](docs/test_cases/t20001.md) * Generation of loop and conditional statements - [_example_](docs/test_cases/t20021.md) * Generation of switch statements - [_example_](docs/test_cases/t20024.md) * Generation of try/catch blocks - [_example_](docs/test_cases/t20023.md) diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index dea23d68..7a9713ba 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -849,9 +849,8 @@ bool parse_source_location(const std::string &location_str, std::string &file, return true; } -std::optional get_expression_comment( - const clang::SourceManager &sm, const clang::ASTContext &context, - const clang::Stmt *stmt) +clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm, + const clang::ASTContext &context, const clang::Stmt *stmt) { // First get the first line of the expression auto expr_begin = stmt->getSourceRange().getBegin(); @@ -859,17 +858,13 @@ std::optional get_expression_comment( if (!context.Comments.empty()) for (const auto [offset, raw_comment] : - *context.Comments.getCommentsInFile(sm.getMainFileID())) { - - auto comment = - raw_comment->getFormattedText(sm, sm.getDiagnostics()); - + *context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) { const auto comment_end_line = sm.getSpellingLineNumber( raw_comment->getSourceRange().getEnd()); if (expr_begin_line == comment_end_line || expr_begin_line == comment_end_line + 1) - return comment; + return raw_comment; } return {}; diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 538176c8..7c36e2d3 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -282,8 +282,15 @@ clang::QualType dereference(clang::QualType type); std::pair> consume_type_context(clang::QualType type); -std::optional get_expression_comment( - const clang::SourceManager &sm, const clang::ASTContext &context, - const clang::Stmt *stmt); +/** + * @brief Extract a comment before or next to a statement + * + * @param sm clang::SourceManager reference + * @param context clang::ASTContext reference + * @param stmt Pointer to the current clang::Stmt + * @return Pointer to a clang::RawComment* or nullptr + */ +clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm, + const clang::ASTContext &context, const clang::Stmt *stmt); } // namespace clanguml::common diff --git a/src/common/visitor/translation_unit_visitor.cc b/src/common/visitor/translation_unit_visitor.cc index 7a2e61fd..be873951 100644 --- a/src/common/visitor/translation_unit_visitor.cc +++ b/src/common/visitor/translation_unit_visitor.cc @@ -59,7 +59,6 @@ clang::SourceManager &translation_unit_visitor::source_manager() const void translation_unit_visitor::process_comment( const clang::NamedDecl &decl, clanguml::common::model::decorated_element &e) { - assert(comment_visitor_.get() != nullptr); comment_visitor_->visit(decl, e); @@ -67,12 +66,23 @@ void translation_unit_visitor::process_comment( const auto *comment = decl.getASTContext().getRawCommentForDeclNoCache(&decl); + process_comment(comment, decl.getASTContext().getDiagnostics(), e); +} + +void translation_unit_visitor::process_comment(const clang::RawComment *comment, + clang::DiagnosticsEngine &de, clanguml::common::model::decorated_element &e) +{ if (comment != nullptr) { + auto [it, inserted] = processed_comments_.emplace(comment); + + if (!inserted) + return; + // Process clang-uml decorators in the comments // TODO: Refactor to use standard block comments processable by clang // comments - e.add_decorators(decorators::parse(comment->getFormattedText( - source_manager_, decl.getASTContext().getDiagnostics()))); + e.add_decorators( + decorators::parse(comment->getFormattedText(source_manager_, de))); } } diff --git a/src/common/visitor/translation_unit_visitor.h b/src/common/visitor/translation_unit_visitor.h index 7a2ae6b2..22139863 100644 --- a/src/common/visitor/translation_unit_visitor.h +++ b/src/common/visitor/translation_unit_visitor.h @@ -102,7 +102,7 @@ public: protected: /** - * @brief Set source location in diagram element + * @brief Process comment directives in comment attached to a declaration * * @param decl Reference to @ref clang::NamedDecl * @param element Reference to element to be updated @@ -110,6 +110,17 @@ protected: void process_comment(const clang::NamedDecl &decl, clanguml::common::model::decorated_element &e); + /** + * @brief Process comment directives in raw comment + * + * @param comment clang::RawComment pointer + * @param de Reference to clang::DiagnosticsEngine + * @param element Reference to element to be updated + */ + void process_comment(const clang::RawComment *comment, + clang::DiagnosticsEngine &de, + clanguml::common::model::decorated_element &e); + private: clang::SourceManager &source_manager_; @@ -118,5 +129,7 @@ private: std::filesystem::path relative_to_path_; std::filesystem::path translation_unit_path_; + + std::set processed_comments_; }; } // namespace clanguml::common::visitor diff --git a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc index bd034f92..55458c48 100644 --- a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc @@ -56,12 +56,31 @@ void generator::generate_diagram_type(std::ostream &ostr) const void generator::generate_message_comment( std::ostream &ostr, const model::message &m) const { - if (!config().generate_message_comments() || !m.comment()) + const auto &from = model().get_participant(m.from()); + if (!from) return; - const auto &from = model().get_participant(m.from()); + bool comment_generated_from_note_decorators{false}; + for (const auto &decorator : m.decorators()) { + auto note = std::dynamic_pointer_cast(decorator); + if (note && note->applies_to_diagram(config().name)) { + comment_generated_from_note_decorators = true; - if (!from) + ostr << indent(1) << "note over " << generate_alias(from.value()) + << ": "; + + auto formatted_message = util::format_message_comment( + note->text, config().message_comment_width()); + + util::replace_all(formatted_message, "\n", "
"); + ostr << formatted_message << '\n'; + } + } + + if (comment_generated_from_note_decorators) + return; + + if (!config().generate_message_comments() || !m.comment()) return; ostr << indent(1) << "note over " << generate_alias(from.value()) << ": "; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index bb9e7e90..36d5f808 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -279,12 +279,30 @@ void generator::generate_activity(const activity &a, std::ostream &ostr, void generator::generate_message_comment( std::ostream &ostr, const model::message &m) const { - if (!config().generate_message_comments() || !m.comment()) + const auto &from = model().get_participant(m.from()); + if (!from) return; - const auto &from = model().get_participant(m.from()); + bool comment_generated_from_note_decorators{false}; + for (const auto &decorator : m.decorators()) { + auto note = std::dynamic_pointer_cast(decorator); + if (note && note->applies_to_diagram(config().name)) { + comment_generated_from_note_decorators = true; - if (!from) + ostr << "note over " << generate_alias(from.value()) << '\n'; + + ostr << util::format_message_comment( + note->text, config().message_comment_width()) + << '\n'; + + ostr << "end note" << '\n'; + } + } + + if (comment_generated_from_note_decorators) + return; + + if (!config().generate_message_comments() || !m.comment()) return; ostr << "note over " << generate_alias(from.value()) << '\n'; diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 33311c77..34cf4fc9 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -445,10 +445,11 @@ void diagram::print() const const auto &to_participant = *participants_.at(message.to()); LOG_TRACE(" Message from={}, from_id={}, " - "to={}, to_id={}, name={}, type={}", + "to={}, to_id={}, name={}, type={}, comment={}", from_participant.full_name(false), from_participant.id(), to_participant.full_name(false), to_participant.id(), - message.message_name(), to_string(message.type())); + message.message_name(), to_string(message.type()), + message.comment() ? message.comment().value() : "None"); } } } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 6ffaffd8..64673ab8 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -613,8 +613,8 @@ bool translation_unit_visitor::TraverseIfStmt(clang::IfStmt *stmt) message m{message_t::kElseIf, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } else { @@ -623,8 +623,8 @@ bool translation_unit_visitor::TraverseIfStmt(clang::IfStmt *stmt) message m{message_t::kIf, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } } @@ -659,8 +659,8 @@ bool translation_unit_visitor::TraverseWhileStmt(clang::WhileStmt *stmt) message m{message_t::kWhile, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } RecursiveASTVisitor::TraverseWhileStmt(stmt); @@ -691,8 +691,8 @@ bool translation_unit_visitor::TraverseDoStmt(clang::DoStmt *stmt) message m{message_t::kDo, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -725,8 +725,8 @@ bool translation_unit_visitor::TraverseForStmt(clang::ForStmt *stmt) set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -754,8 +754,8 @@ bool translation_unit_visitor::TraverseCXXTryStmt(clang::CXXTryStmt *stmt) context().enter_trystmt(stmt); message m{message_t::kTry, current_caller_id}; set_source_location(*stmt, m); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -814,8 +814,8 @@ bool translation_unit_visitor::TraverseCXXForRangeStmt( message m{message_t::kFor, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -841,8 +841,8 @@ bool translation_unit_visitor::TraverseSwitchStmt(clang::SwitchStmt *stmt) context().enter_switchstmt(stmt); model::message m{message_t::kSwitch, current_caller_id}; set_source_location(*stmt, m); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -909,8 +909,8 @@ bool translation_unit_visitor::TraverseConditionalOperator( model::message m{message_t::kConditional, current_caller_id}; set_source_location(*stmt, m); m.condition_text(condition_text); - m.set_comment(clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), stmt)); + m.set_comment(get_expression_comment(source_manager(), + *context().get_ast_context(), current_caller_id, stmt)); diagram().add_block_message(std::move(m)); } @@ -960,6 +960,13 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) set_source_location(*expr, m); + process_comment(clanguml::common::get_expression_raw_comment( + source_manager(), *context().get_ast_context(), expr), + context().get_ast_context()->getDiagnostics(), m); + + if (m.skip()) + return true; + // If we're currently inside a lambda expression, set it's id as // message source rather then enclosing context // Unless the lambda is declared in a function or method call @@ -1035,8 +1042,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) } if (m.from() > 0 && m.to() > 0) { - auto expr_comment = clanguml::common::get_expression_comment( - source_manager(), *context().get_ast_context(), expr); + auto expr_comment = get_expression_comment(source_manager(), + *context().get_ast_context(), context().caller_id(), expr); m.set_comment(expr_comment); if (diagram().sequences().find(m.from()) == @@ -2519,4 +2526,21 @@ bool translation_unit_visitor::should_include( namespace_{decl->getQualifiedNameAsString()}) && diagram().should_include(common::model::source_file{decl_file}); } + +std::optional translation_unit_visitor::get_expression_comment( + const clang::SourceManager &sm, const clang::ASTContext &context, + const int64_t caller_id, const clang::Stmt *stmt) +{ + const auto *raw_comment = + clanguml::common::get_expression_raw_comment(sm, context, stmt); + + if (raw_comment == nullptr) + return {}; + + if (!processed_comments_.emplace(caller_id, raw_comment).second) { + return {}; + } + + return raw_comment->getFormattedText(sm, sm.getDiagnostics()); +} } // namespace clanguml::sequence_diagram::visitor diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 56d24430..4a7d7deb 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -510,6 +510,10 @@ private: void pop_message_to_diagram(clang::CallExpr *expr); void pop_message_to_diagram(clang::CXXConstructExpr *expr); + std::optional get_expression_comment( + const clang::SourceManager &sm, const clang::ASTContext &context, + const int64_t caller_id, const clang::Stmt *stmt); + // Reference to the output diagram model clanguml::sequence_diagram::model::diagram &diagram_; @@ -546,5 +550,8 @@ private: mutable unsigned within_static_variable_declaration_{0}; mutable std::set already_visited_in_static_declaration_{}; + + mutable std::set> + processed_comments_; }; } // namespace clanguml::sequence_diagram::visitor diff --git a/tests/t20001/.clang-uml b/tests/t20001/.clang-uml index af58596c..10304840 100644 --- a/tests/t20001/.clang-uml +++ b/tests/t20001/.clang-uml @@ -1,5 +1,7 @@ compilation_database_dir: .. output_directory: diagrams +add_compile_flags: + - -fparse-all-comments diagrams: t20001_sequence: type: sequence diff --git a/tests/t20001/t20001.cc b/tests/t20001/t20001.cc index 2c7cdaca..4fd3be27 100644 --- a/tests/t20001/t20001.cc +++ b/tests/t20001/t20001.cc @@ -63,8 +63,10 @@ int tmain() A a; B b(a); + // \uml{note Just add 2 numbers} auto tmp = a.add(1, 2); + // \uml{note[] And now add another 2} return b.wrap_add3(tmp, 2, 3); } } diff --git a/tests/t20001/test_case.h b/tests/t20001/test_case.h index 7fea32bd..6f601ad2 100644 --- a/tests/t20001/test_case.h +++ b/tests/t20001/test_case.h @@ -45,6 +45,12 @@ TEST_CASE("t20001", "[test-case][sequence]") REQUIRE_THAT(src, HasComment("t20001 test diagram of type sequence")); + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "Just add 2 numbers")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "And now add another 2")); + save_puml(config.output_directory(), diagram->name + ".puml", src); } { diff --git a/tests/t20038/.clang-uml b/tests/t20038/.clang-uml new file mode 100644 index 00000000..70601a0a --- /dev/null +++ b/tests/t20038/.clang-uml @@ -0,0 +1,18 @@ +compilation_database_dir: .. +output_directory: diagrams +add_compile_flags: + - -fparse-all-comments +diagrams: + t20038_sequence: + type: sequence + glob: + - ../../tests/t20038/t20038.cc + include: + namespaces: + - clanguml::t20038 + from: + - function: clanguml::t20038::tmain() + generate_message_comments: true + message_comment_width: 35 + using_namespace: + - clanguml::t20038 \ No newline at end of file diff --git a/tests/t20038/include/t20038.h b/tests/t20038/include/t20038.h new file mode 100644 index 00000000..d79e3323 --- /dev/null +++ b/tests/t20038/include/t20038.h @@ -0,0 +1,14 @@ +#pragma once + +namespace clanguml { +namespace t20038 { + +template T add_impl(T a, T b) { return a + b; }; + +template T add(T a, T b) +{ + // Invoke 'add' implementation + return add_impl(a, b); +}; +} +} \ No newline at end of file diff --git a/tests/t20038/t20038.cc b/tests/t20038/t20038.cc new file mode 100644 index 00000000..d07dc138 --- /dev/null +++ b/tests/t20038/t20038.cc @@ -0,0 +1,91 @@ +#include + +#include "include/t20038.h" + +namespace clanguml { +namespace t20038 { + +struct A { + int a() { return 1; } + + int aa() + { + int i; + // Repeat 10 times + for (i = 0; i < 10;) { + i += a(); + } + return i; + } + + int aaa() { return 3; } + + int aaaa() { return add(4, 4); } + + int aaaaa() { return 5; } +}; + +struct B { + int b() { return a.a(); } + + int bb() { return a.aa(); } + + int bbb() { return a.aaa(); } + + int bbbb() { return a.aaaa(); } + + int bbbbb() { return a.aaaa(); } + + int wrap(int b) { return b; } + + A a; +}; + +int tmain() +{ + B b; + + // Nisl purus in mollis nunc sed id semper. Varius vel pharetra vel + // turpis. Arcu cursus vitae congue mauris rhoncus. Risus feugiat in + // ante metus dictum at tempor. Lacus vel facilisis volutpat est. Auctor + // urna nunc id cursus metus aliquam. Diam sit amet nisl suscipit + // adipiscing. Potenti nullam ac tortor vitae purus faucibus ornare + // suspendisse sed. Lobortis feugiat vivamus at augue eget arcu dictum + // varius. Non tellus orci ac auctor. + if (true) { + auto r = 0; + // Repeat 5 times... + while (r < 5) { + r += b.b(); + } + return r; + } + else { + // ... or just once + return 2 * b.b(); + } + + // \uml{skip} + b.bb(); + + // TODO: \uml{call B::bbb()} + auto bbb_future = std::async(std::launch::deferred, &B::bbb, b); + + bbb_future.wait(); + + // This comment should be rendered only once + b.wrap(b.bbbb()); + + add_impl(2, 2); // What is 2 + 2? + + // This is a generic comment about calling bbbbb() + // + // \uml{note:some_other_diagram[] This is specific for some_other_diagram} + // \uml{note:t20038_sequence[] Calling B::bbbbb()} + b.bbbbb(); + + // This is a conditional operator + return b.bbb() > 5 ? 0 : 1; +} +} +} \ No newline at end of file diff --git a/tests/t20038/test_case.h b/tests/t20038/test_case.h new file mode 100644 index 00000000..3d1a86da --- /dev/null +++ b/tests/t20038/test_case.h @@ -0,0 +1,127 @@ +/** + * tests/t20038/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("t20038", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20038"); + + auto diagram = config.diagrams["t20038_sequence"]; + + REQUIRE(diagram->name == "t20038_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20038_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()"), _A("B"), "b()")); + + REQUIRE_THAT(src, !HasCall(_A("tmain()"), _A("B"), "bb()")); + + // TODO: REQUIRE_THAT( + // src, HasCall(_A("tmain()"), _A("B"), "bbb()")); + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbb()")); + + REQUIRE_THAT(src, + HasMessageComment(_A("tmain()"), + "This comment should be rendered only\\n" + "once")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()"), _A("add_impl(double,double)"), "")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "What is 2 \\+ 2\\?")); + + REQUIRE_THAT(src, + !HasMessageComment( + _A("tmain()"), "This is specific for some_other_diagram")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "Calling B::bbbbb\\(\\)")); + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbbb()")); + + REQUIRE_THAT(src, + HasMessageComment(_A("tmain()"), "This is a conditional operator")); + + save_puml(config.output_directory(), diagram->name + ".puml", src); + } + + { + auto j = generate_sequence_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto src = generate_sequence_mermaid(diagram, *model); + + mermaid::SequenceDiagramAliasMatcher _A(src); + using mermaid::HasCall; + using mermaid::HasCallInControlCondition; + using mermaid::HasMessageComment; + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "b()")); + + REQUIRE_THAT(src, !HasCall(_A("tmain()"), _A("B"), "bb()")); + + // TODO: REQUIRE_THAT( + // src, HasCall(_A("tmain()"), _A("B"), "bbb()")); + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbb()")); + + REQUIRE_THAT(src, + HasMessageComment(_A("tmain()"), + "This comment should be rendered only
" + "once")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()"), _A("add_impl(double,double)"), "")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "What is 2 \\+ 2\\?")); + + REQUIRE_THAT(src, + !HasMessageComment( + _A("tmain()"), "This is specific for some_other_diagram")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "Calling B::bbbbb\\(\\)")); + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbbb()")); + + REQUIRE_THAT(src, + !HasMessageComment( + _A("tmain()"), "This is specific for some_other_diagram")); + + REQUIRE_THAT( + src, HasMessageComment(_A("tmain()"), "Calling B::bbbbb\\(\\)")); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", src); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index d3fd3824..a50b20df 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -429,6 +429,7 @@ using namespace clanguml::test::matchers; #include "t20035/test_case.h" #include "t20036/test_case.h" #include "t20037/test_case.h" +#include "t20038/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index d964cbe6..da533aa1 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -310,6 +310,9 @@ test_cases: - name: t20037 title: Test case checking if activities in static variable declarations appear only once description: + - name: t20038 + title: Sequence diagram comment decorator test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case