From 920388d84aea2016a3dc0042f7b6f6c5216067bb Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 18 Sep 2022 23:57:02 +0200 Subject: [PATCH] Refactored comment parsing to clang comments --- .../visitor/translation_unit_visitor.cc | 46 ++-- .../visitor/translation_unit_visitor.h | 23 +- src/common/generators/plantuml/generator.h | 59 +++-- src/common/model/decorated_element.cc | 7 +- src/common/model/decorated_element.h | 12 +- src/common/model/diagram_element.h | 3 +- src/common/model/element.cc | 3 +- src/common/model/element.h | 2 +- src/common/model/source_file.h | 1 - src/common/visitor/comment/clang_visitor.cc | 221 ++++++++++++++++++ src/common/visitor/comment/clang_visitor.h | 54 +++++ src/common/visitor/comment/comment_visitor.cc | 33 +++ src/common/visitor/comment/comment_visitor.h | 41 ++++ src/common/visitor/comment/plain_visitor.cc | 50 ++++ src/common/visitor/comment/plain_visitor.h | 35 +++ .../visitor/translation_unit_visitor.cc | 76 ++++++ src/common/visitor/translation_unit_visitor.h | 62 +++++ src/config/config.cc | 19 ++ src/config/config.h | 4 + .../visitor/translation_unit_visitor.cc | 4 +- .../visitor/translation_unit_visitor.h | 6 +- .../visitor/translation_unit_visitor.cc | 6 +- .../visitor/translation_unit_visitor.h | 31 +-- tests/t00002/.clang-uml | 1 + tests/t00002/t00002.cc | 4 +- tests/t00050/.clang-uml | 56 +++-- tests/t00050/t00050.cc | 24 ++ tests/t00050/test_case.h | 4 +- tests/test_cases.cc | 2 +- tests/test_config.cc | 3 + tests/test_config_data/simple.yml | 1 + 31 files changed, 749 insertions(+), 144 deletions(-) create mode 100644 src/common/visitor/comment/clang_visitor.cc create mode 100644 src/common/visitor/comment/clang_visitor.h create mode 100644 src/common/visitor/comment/comment_visitor.cc create mode 100644 src/common/visitor/comment/comment_visitor.h create mode 100644 src/common/visitor/comment/plain_visitor.cc create mode 100644 src/common/visitor/comment/plain_visitor.h create mode 100644 src/common/visitor/translation_unit_visitor.cc create mode 100644 src/common/visitor/translation_unit_visitor.h diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index e89e4ddd..c85cc445 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -66,7 +66,7 @@ access_t access_specifier_to_access_t(clang::AccessSpecifier access_specifier) translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::class_diagram::model::diagram &diagram, const clanguml::config::class_diagram &config) - : source_manager_{sm} + : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} { @@ -81,7 +81,7 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) LOG_DBG("= Visiting namespace declaration {} at {}", ns->getQualifiedNameAsString(), - ns->getLocation().printToString(source_manager_)); + ns->getLocation().printToString(source_manager())); auto package_path = namespace_{common::get_qualified_name(*ns)}; auto package_parent = package_path; @@ -138,7 +138,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) LOG_DBG("= Visiting enum declaration {} at {}", enm->getQualifiedNameAsString(), - enm->getLocation().printToString(source_manager_)); + enm->getLocation().printToString(source_manager())); auto e_ptr = std::make_unique(config_.using_namespace()); auto &e = *e_ptr; @@ -217,7 +217,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( clang::ClassTemplateSpecializationDecl *cls) { - if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; if (!diagram().should_include(cls->getQualifiedNameAsString())) @@ -225,7 +225,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( LOG_DBG("= Visiting template specialization declaration {} at {}", cls->getQualifiedNameAsString(), - cls->getLocation().printToString(source_manager_)); + cls->getLocation().printToString(source_manager())); // TODO: Add support for classes defined in function/method bodies if (cls->isLocalClass()) @@ -264,7 +264,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( bool translation_unit_visitor::VisitTypeAliasTemplateDecl( clang::TypeAliasTemplateDecl *cls) { - if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; if (!diagram().should_include(cls->getQualifiedNameAsString())) @@ -272,7 +272,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl( LOG_DBG("= Visiting template type alias declaration {} at {}", cls->getQualifiedNameAsString(), - cls->getLocation().printToString(source_manager_)); + cls->getLocation().printToString(source_manager())); auto *template_type_specialization_ptr = cls->getTemplatedDecl() @@ -303,7 +303,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl( bool translation_unit_visitor::VisitClassTemplateDecl( clang::ClassTemplateDecl *cls) { - if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; if (!diagram().should_include(cls->getQualifiedNameAsString())) @@ -311,7 +311,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( LOG_DBG("= Visiting class template declaration {} at {}", cls->getQualifiedNameAsString(), - cls->getLocation().printToString(source_manager_)); + cls->getLocation().printToString(source_manager())); auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); @@ -352,7 +352,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) { // Skip system headers - if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; if (!diagram().should_include(cls->getQualifiedNameAsString())) @@ -360,7 +360,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) LOG_DBG("= Visiting class declaration {} at {}", cls->getQualifiedNameAsString(), - cls->getLocation().printToString(source_manager_)); + cls->getLocation().printToString(source_manager())); LOG_DBG( "== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString()); @@ -1004,7 +1004,7 @@ void translation_unit_visitor::process_function_parameter( const auto *default_arg = p.getDefaultArg(); if (default_arg != nullptr) { auto default_arg_str = common::get_source_text( - default_arg->getSourceRange(), source_manager_); + default_arg->getSourceRange(), source_manager()); parameter.set_default_value(default_arg_str); } } @@ -1241,7 +1241,7 @@ void translation_unit_visitor::process_template_specialization_argument( // them from raw source code... if (type_name.find("type-parameter-") == 0) { auto declaration_text = common::get_source_text_raw( - cls->getSourceRange(), source_manager_); + cls->getSourceRange(), source_manager()); declaration_text = declaration_text.substr( declaration_text.find(cls->getNameAsString()) + @@ -1282,7 +1282,7 @@ void translation_unit_visitor::process_template_specialization_argument( } else if (type_name.find("type-parameter-") == 0) { auto declaration_text = common::get_source_text_raw( - cls->getSourceRange(), source_manager_); + cls->getSourceRange(), source_manager()); declaration_text = declaration_text.substr( declaration_text.find(cls->getNameAsString()) + @@ -1327,14 +1327,14 @@ void translation_unit_visitor::process_template_specialization_argument( template_parameter argument; argument.is_template_parameter(false); argument.set_type(common::get_source_text( - arg.getAsExpr()->getSourceRange(), source_manager_)); + arg.getAsExpr()->getSourceRange(), source_manager())); template_instantiation.add_template(std::move(argument)); } else if (argument_kind == clang::TemplateArgument::TemplateExpansion) { template_parameter argument; argument.is_template_parameter(true); - cls->getLocation().dump(source_manager_); + cls->getLocation().dump(source_manager()); } else if (argument_kind == clang::TemplateArgument::Pack) { // This will only work for now if pack is at the end @@ -1347,7 +1347,7 @@ void translation_unit_visitor::process_template_specialization_argument( } else { LOG_ERROR("Unsupported template argument kind {} [{}]", arg.getKind(), - cls->getLocation().printToString(source_manager_)); + cls->getLocation().printToString(source_manager())); } } @@ -1888,7 +1888,7 @@ void translation_unit_visitor:: argument.is_template_parameter(false); argument.set_type(common::get_source_text( - arg.getAsExpr()->getSourceRange(), source_manager_)); + arg.getAsExpr()->getSourceRange(), source_manager())); } void translation_unit_visitor:: @@ -2128,16 +2128,6 @@ void translation_unit_visitor::process_field( c.add_member(std::move(field)); } -void translation_unit_visitor::set_source_location( - const clang::Decl &decl, clanguml::common::model::source_location &element) -{ - if (decl.getLocation().isValid()) { - element.set_file(source_manager_.getFilename(decl.getLocation()).str()); - element.set_line( - source_manager_.getSpellingLineNumber(decl.getLocation())); - } -} - void translation_unit_visitor::add_incomplete_forward_declarations() { for (auto &[id, c] : forward_declarations_) { diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 4477e7b9..0e5fafb1 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -20,6 +20,7 @@ #include "class_diagram/model/class.h" #include "class_diagram/model/diagram.h" #include "common/model/enums.h" +#include "common/visitor/translation_unit_visitor.h" #include "config/config.h" #include @@ -38,7 +39,8 @@ using found_relationships_t = common::model::relationship_t>>; class translation_unit_visitor - : public clang::RecursiveASTVisitor { + : public clang::RecursiveASTVisitor, + public common::visitor::translation_unit_visitor { public: explicit translation_unit_visitor(clang::SourceManager &sm, clanguml::class_diagram::model::diagram &diagram, @@ -130,9 +132,6 @@ private: const found_relationships_t &relationships, bool break_on_first_aggregation = false); - void set_source_location(const clang::Decl &decl, - clanguml::common::model::source_location &element); - std::unique_ptr build_template_instantiation( const clang::TemplateSpecializationType &template_type, @@ -199,20 +198,6 @@ private: const clanguml::class_diagram::model::template_parameter &ct, found_relationships_t &relationships); - template - void process_comment( - const ClangDecl &decl, clanguml::common::model::decorated_element &e) - { - const auto *comment = - decl.getASTContext().getRawCommentForDeclNoCache(&decl); - - if (comment != nullptr) { - e.set_comment(comment->getFormattedText( - source_manager_, decl.getASTContext().getDiagnostics())); - e.add_decorators(decorators::parse(e.comment().value())); - } - } - void add_incomplete_forward_declarations(); void resolve_local_to_global_ids(); @@ -229,8 +214,6 @@ private: std::optional get_ast_local_id( int64_t local_id) const; - clang::SourceManager &source_manager_; - // Reference to the output diagram model clanguml::class_diagram::model::diagram &diagram_; diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h index 34c6eaae..8b8caabc 100644 --- a/src/common/generators/plantuml/generator.h +++ b/src/common/generators/plantuml/generator.h @@ -133,10 +133,7 @@ inja::json generator::element_context(const E &e) const } if (e.comment().has_value()) { - std::string c = e.comment().value(); - if (!c.empty()) { - ctx["element"]["comment"] = util::trim(c); - } + ctx["element"]["comment"] = e.comment().value(); } return ctx; @@ -186,27 +183,35 @@ void generator::generate_plantuml_directives( using common::model::namespace_; for (const auto &d : directives) { - // Render the directive with template engine first - std::string directive{env().render(std::string_view{d}, context())}; + try { + // Render the directive with template engine first + std::string directive{env().render(std::string_view{d}, context())}; - // Now search for alias @A() directives in the text - std::tuple alias_match; - while (util::find_element_alias(directive, alias_match)) { - const auto full_name = - m_config.using_namespace() | std::get<0>(alias_match); - auto element_opt = m_model.get(full_name.to_string()); + // Now search for alias @A() directives in the text + // (this is deprecated) + std::tuple alias_match; + while (util::find_element_alias(directive, alias_match)) { + const auto full_name = + m_config.using_namespace() | std::get<0>(alias_match); + auto element_opt = m_model.get(full_name.to_string()); - if (element_opt) - directive.replace(std::get<1>(alias_match), - std::get<2>(alias_match), element_opt.value().alias()); - else { - LOG_ERROR("Cannot find clang-uml alias for element {}", - full_name.to_string()); - directive.replace(std::get<1>(alias_match), - std::get<2>(alias_match), "UNKNOWN_ALIAS"); + if (element_opt) + directive.replace(std::get<1>(alias_match), + std::get<2>(alias_match), element_opt.value().alias()); + else { + LOG_ERROR("Cannot find clang-uml alias for element {}", + full_name.to_string()); + directive.replace(std::get<1>(alias_match), + std::get<2>(alias_match), "UNKNOWN_ALIAS"); + } } + + ostr << directive << '\n'; + } + catch (const inja::json::exception &e) { + LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", + d, e.what()); } - ostr << directive << '\n'; } } @@ -433,10 +438,14 @@ template void generator::init_env() // {{ element("clanguml::t00050::A").comment }} // m_env.add_callback("element", 1, [this](inja::Arguments &args) { + inja::json res{}; auto element_opt = m_model.get_with_namespace( args[0]->get(), m_config.using_namespace()); - return element_opt.value().context(); + if (element_opt.has_value()) + res = element_opt.value().context(); + + return res; }); // Convert C++ entity to PlantUML alias, e.g. @@ -457,15 +466,17 @@ template void generator::init_env() // {{ element("A").comment }} // m_env.add_callback("comment", 1, [this](inja::Arguments &args) { - std::string res{}; + inja::json res{}; auto element = m_model.get_with_namespace( args[0]->get(), m_config.using_namespace()); if (element.has_value()) { auto comment = element.value().comment(); - if (comment.has_value()) + if (comment.has_value()) { + assert(comment.value().is_object()); res = comment.value(); + } } return res; diff --git a/src/common/model/decorated_element.cc b/src/common/model/decorated_element.cc index f33d7226..54422fba 100644 --- a/src/common/model/decorated_element.cc +++ b/src/common/model/decorated_element.cc @@ -88,10 +88,7 @@ void decorated_element::append(const decorated_element &de) } } -std::optional decorated_element::comment() const -{ - return comment_; -} +std::optional decorated_element::comment() const { return comment_; } -void decorated_element::set_comment(const std::string &c) { comment_ = c; } +void decorated_element::set_comment(const comment_t &c) { comment_ = c; } } diff --git a/src/common/model/decorated_element.h b/src/common/model/decorated_element.h index 05fdd92c..828aef1c 100644 --- a/src/common/model/decorated_element.h +++ b/src/common/model/decorated_element.h @@ -20,14 +20,20 @@ #include "enums.h" #include "decorators/decorators.h" +#include "inja/inja.hpp" +#include #include #include #include +#include +#include #include namespace clanguml::common::model { +using comment_t = inja::json; + class decorated_element { public: bool skip() const; @@ -46,13 +52,13 @@ public: void append(const decorated_element &de); - std::optional comment() const; + std::optional comment() const; - void set_comment(const std::string &c); + void set_comment(const comment_t &c); private: std::vector> decorators_; - std::optional comment_; + std::optional comment_; }; } diff --git a/src/common/model/diagram_element.h b/src/common/model/diagram_element.h index 6a6db750..084b09e2 100644 --- a/src/common/model/diagram_element.h +++ b/src/common/model/diagram_element.h @@ -19,6 +19,7 @@ #include "decorated_element.h" #include "relationship.h" +#include "source_location.h" #include "util/util.h" #include @@ -30,7 +31,7 @@ namespace clanguml::common::model { -class diagram_element : public decorated_element { +class diagram_element : public decorated_element, public source_location { public: using id_t = int64_t; diff --git a/src/common/model/element.cc b/src/common/model/element.cc index 61ef96fc..a0581956 100644 --- a/src/common/model/element.cc +++ b/src/common/model/element.cc @@ -44,8 +44,9 @@ inja::json element::context() const ctx["alias"] = alias(); ctx["full_name"] = full_name(false); ctx["namespace"] = get_namespace().to_string(); - if (comment().has_value()) + if (comment().has_value()) { ctx["comment"] = comment().value(); + } return ctx; } diff --git a/src/common/model/element.h b/src/common/model/element.h index 6348d78c..b7a2b525 100644 --- a/src/common/model/element.h +++ b/src/common/model/element.h @@ -32,7 +32,7 @@ namespace clanguml::common::model { -class element : public diagram_element, public source_location { +class element : public diagram_element { public: element(const namespace_ &using_namespace); diff --git a/src/common/model/source_file.h b/src/common/model/source_file.h index 00026f01..5117be49 100644 --- a/src/common/model/source_file.h +++ b/src/common/model/source_file.h @@ -45,7 +45,6 @@ using filesystem_path = common::model::path; class source_file : public common::model::diagram_element, public common::model::stylable_element, - public source_location, public common::model::nested_trait { public: diff --git a/src/common/visitor/comment/clang_visitor.cc b/src/common/visitor/comment/clang_visitor.cc new file mode 100644 index 00000000..07cdf5a8 --- /dev/null +++ b/src/common/visitor/comment/clang_visitor.cc @@ -0,0 +1,221 @@ +/** + * src/common/visitor/comment/clang_visitor.cc + * + * Copyright (c) 2021-2022 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. + */ + +#include "clang_visitor.h" + +namespace clanguml::common::visitor::comment { + +clang_visitor::clang_visitor(clang::SourceManager &source_manager) + : comment_visitor{source_manager} +{ +} + +void clang_visitor::visit( + const clang::NamedDecl &decl, common::model::decorated_element &e) +{ + const auto *comment = + decl.getASTContext().getRawCommentForDeclNoCache(&decl); + + if (comment == nullptr) { + return; + } + + auto raw_comment = comment->getRawText(source_manager()); + + auto formatted_comment = comment->getFormattedText( + source_manager(), decl.getASTContext().getDiagnostics()); + + common::model::comment_t cmt = inja::json::object(); + cmt["raw"] = raw_comment; + cmt["formatted"] = formatted_comment; + + using clang::comments::BlockCommandComment; + using clang::comments::Comment; + using clang::comments::FullComment; + using clang::comments::ParagraphComment; + using clang::comments::ParamCommandComment; + using clang::comments::TextComment; + using clang::comments::TParamCommandComment; + + FullComment *full_comment = + comment->parse(decl.getASTContext(), nullptr, &decl); + + const auto &traits = decl.getASTContext().getCommentCommandTraits(); + + for (const auto *block : full_comment->getBlocks()) { + const auto block_kind = block->getCommentKind(); + if (block_kind == Comment::ParagraphCommentKind) { + std::string paragraph_text; + + visit_paragraph(clang::dyn_cast(block), traits, + paragraph_text); + if (!cmt.contains("text")) + cmt["text"] = ""; + + cmt["text"] = + cmt["text"].get() + "\n" + paragraph_text; + } + else if (block_kind == Comment::TextCommentKind) { + // TODO + } + else if (block_kind == Comment::ParamCommandCommentKind) { + visit_param_command( + clang::dyn_cast(block), traits, cmt); + } + else if (block_kind == Comment::TParamCommandCommentKind) { + visit_tparam_command( + clang::dyn_cast(block), traits, cmt); + } + else if (block_kind == Comment::BlockCommandCommentKind) { + auto *command = clang::dyn_cast(block); + auto command_info = traits.getCommandInfo(command->getCommandID()); + + if (command_info->IsBlockCommand && command_info->NumArgs == 0) { + // Visit block command with a single text argument, e.g.: + // \brief text + // \todo text + // ... + visit_block_command(command, traits, cmt); + } + else if (command_info->IsParamCommand) { + // Visit function param block: + // \param arg text + visit_param_command( + clang::dyn_cast(command), traits, cmt); + } + else if (command_info->IsTParamCommand) { + // Visit template param block: + // \tparam typename text + visit_tparam_command( + clang::dyn_cast(command), traits, + cmt); + } + } + } + e.set_comment(cmt); +} + +void clang_visitor::visit_block_command( + const clang::comments::BlockCommandComment *command, + const clang::comments::CommandTraits &traits, common::model::comment_t &cmt) +{ + using clang::comments::Comment; + using clang::comments::ParagraphComment; + using clang::comments::TextComment; + + std::string command_text; + + for (auto paragraph_it = command->child_begin(); + paragraph_it != command->child_end(); ++paragraph_it) { + + if ((*paragraph_it)->getCommentKind() == + Comment::ParagraphCommentKind) { + visit_paragraph(clang::dyn_cast(*paragraph_it), + traits, command_text); + } + } + + const auto command_name = command->getCommandName(traits).str(); + if (!command_text.empty()) { + if (!cmt.contains(command_name)) + cmt[command_name] = inja::json::array(); + + cmt[command_name].push_back(command_text); + } +} + +void clang_visitor::visit_param_command( + const clang::comments::ParamCommandComment *command, + const clang::comments::CommandTraits &traits, common::model::comment_t &cmt) +{ + using clang::comments::Comment; + using clang::comments::ParagraphComment; + using clang::comments::TextComment; + + std::string description; + + const auto name = command->getParamNameAsWritten().str(); + + for (auto it = command->child_begin(); it != command->child_end(); ++it) { + + if ((*it)->getCommentKind() == Comment::ParagraphCommentKind) { + visit_paragraph( + clang::dyn_cast(*it), traits, description); + } + } + + if (!name.empty()) { + if (!cmt.contains("param")) + cmt["param"] = inja::json::array(); + + inja::json param = inja::json::object(); + param["name"] = name; + param["description"] = description; + cmt["param"].push_back(std::move(param)); + } +} + +void clang_visitor::visit_tparam_command( + const clang::comments::TParamCommandComment *command, + const clang::comments::CommandTraits &traits, common::model::comment_t &cmt) +{ + using clang::comments::Comment; + using clang::comments::ParagraphComment; + using clang::comments::TextComment; + + std::string description; + + const auto name = command->getParamNameAsWritten().str(); + + for (auto it = command->child_begin(); it != command->child_end(); ++it) { + if ((*it)->getCommentKind() == Comment::ParagraphCommentKind) { + visit_paragraph( + clang::dyn_cast(*it), traits, description); + } + } + + if (!name.empty()) { + if (!cmt.contains("tparam")) + cmt["tparam"] = inja::json::array(); + + inja::json param = inja::json::object(); + param["name"] = name; + param["description"] = description; + cmt["tparam"].push_back(std::move(param)); + } +} + +void clang_visitor::visit_paragraph( + const clang::comments::ParagraphComment *paragraph, + const clang::comments::CommandTraits &traits, std::string &text) +{ + using clang::comments::Comment; + using clang::comments::TextComment; + + for (auto text_it = paragraph->child_begin(); + text_it != paragraph->child_end(); ++text_it) { + + if ((*text_it)->getCommentKind() == Comment::TextCommentKind) { + // Merge paragraph lines into a single string + text += clang::dyn_cast((*text_it))->getText(); + text += "\n"; + } + } +} + +} \ No newline at end of file diff --git a/src/common/visitor/comment/clang_visitor.h b/src/common/visitor/comment/clang_visitor.h new file mode 100644 index 00000000..4718bf92 --- /dev/null +++ b/src/common/visitor/comment/clang_visitor.h @@ -0,0 +1,54 @@ +/** + * src/common/visitor/comment/clang_visitor.h + * + * Copyright (c) 2021-2022 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. + */ +#pragma once + +#include +#include +#include + +#include "comment_visitor.h" + +namespace clanguml::common::visitor::comment { + +class clang_visitor : public comment_visitor { +public: + clang_visitor(clang::SourceManager &source_manager); + + void visit(const clang::NamedDecl &decl, + common::model::decorated_element &e) override; + +private: + void visit_block_command( + const clang::comments::BlockCommandComment *command, + const clang::comments::CommandTraits &traits, + common::model::comment_t &cmt); + + void visit_param_command( + const clang::comments::ParamCommandComment *command, + const clang::comments::CommandTraits &traits, + common::model::comment_t &cmt); + + void visit_tparam_command( + const clang::comments::TParamCommandComment *command, + const clang::comments::CommandTraits &traits, + common::model::comment_t &cmt); + + void visit_paragraph(const clang::comments::ParagraphComment *paragraph, + const clang::comments::CommandTraits &traits, std::string &text); +}; +} \ No newline at end of file diff --git a/src/common/visitor/comment/comment_visitor.cc b/src/common/visitor/comment/comment_visitor.cc new file mode 100644 index 00000000..aabe8d75 --- /dev/null +++ b/src/common/visitor/comment/comment_visitor.cc @@ -0,0 +1,33 @@ +/** + * src/common/visitor/comment/comment_visitor.cc + * + * Copyright (c) 2021-2022 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. + */ + +#include "comment_visitor.h" + +namespace clanguml::common::visitor::comment { + +comment_visitor::comment_visitor(clang::SourceManager &source_manager) + : source_manager_{source_manager} +{ +} + +clang::SourceManager &comment_visitor::source_manager() +{ + return source_manager_; +} + +} \ No newline at end of file diff --git a/src/common/visitor/comment/comment_visitor.h b/src/common/visitor/comment/comment_visitor.h new file mode 100644 index 00000000..328d4a3a --- /dev/null +++ b/src/common/visitor/comment/comment_visitor.h @@ -0,0 +1,41 @@ +/** + * src/common/visitor/comment/comment_visitor.h + * + * Copyright (c) 2021-2022 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. + */ +#pragma once + +#include +#include + +#include "common/model/decorated_element.h" + +namespace clanguml::common::visitor::comment { + +class comment_visitor { +public: + comment_visitor(clang::SourceManager &source_manager); + + virtual ~comment_visitor() = default; + + virtual void visit( + const clang::NamedDecl &decl, common::model::decorated_element &e) = 0; + + clang::SourceManager &source_manager(); + +private: + clang::SourceManager &source_manager_; +}; +} \ No newline at end of file diff --git a/src/common/visitor/comment/plain_visitor.cc b/src/common/visitor/comment/plain_visitor.cc new file mode 100644 index 00000000..4297f80b --- /dev/null +++ b/src/common/visitor/comment/plain_visitor.cc @@ -0,0 +1,50 @@ +/** + * src/common/visitor/comment/plain_visitor.cc + * + * Copyright (c) 2021-2022 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. + */ + +#include "plain_visitor.h" + +namespace clanguml::common::visitor::comment { + +plain_visitor::plain_visitor(clang::SourceManager &source_manager) + : comment_visitor{source_manager} +{ +} + +void plain_visitor::visit( + const clang::NamedDecl &decl, common::model::decorated_element &e) +{ + const auto *comment = + decl.getASTContext().getRawCommentForDeclNoCache(&decl); + + if (comment == nullptr) { + return; + } + + auto raw_comment = comment->getRawText(source_manager()); + + auto formatted_comment = comment->getFormattedText( + source_manager(), decl.getASTContext().getDiagnostics()); + + common::model::comment_t cmt = inja::json::object(); + cmt["raw"] = raw_comment; + cmt["formatted"] = formatted_comment; + + e.set_comment(cmt); +} + +} \ No newline at end of file diff --git a/src/common/visitor/comment/plain_visitor.h b/src/common/visitor/comment/plain_visitor.h new file mode 100644 index 00000000..70842616 --- /dev/null +++ b/src/common/visitor/comment/plain_visitor.h @@ -0,0 +1,35 @@ +/** + * src/common/visitor/comment/plain_visitor.h + * + * Copyright (c) 2021-2022 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. + */ +#pragma once + +#include +#include +#include + +#include "comment_visitor.h" + +namespace clanguml::common::visitor::comment { + +class plain_visitor : public comment_visitor { +public: + plain_visitor(clang::SourceManager &source_manager); + + void visit(const clang::NamedDecl &decl, + common::model::decorated_element &e) override; +}; +} \ No newline at end of file diff --git a/src/common/visitor/translation_unit_visitor.cc b/src/common/visitor/translation_unit_visitor.cc new file mode 100644 index 00000000..6c42e29f --- /dev/null +++ b/src/common/visitor/translation_unit_visitor.cc @@ -0,0 +1,76 @@ +/** + * src/common/visitor/translation_unit_visitor.cc + * + * Copyright (c) 2021-2022 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. + */ + +#include "translation_unit_visitor.h" + +#include "comment/clang_visitor.h" +#include "comment/plain_visitor.h" + +namespace clanguml::common::visitor { + +translation_unit_visitor::translation_unit_visitor( + clang::SourceManager &sm, const clanguml::config::diagram &config) + : source_manager_{sm} + , config_{config} +{ + if (config.comment_parser() == config::comment_parser_t::plain) { + comment_visitor_ = + std::make_unique(source_manager_); + } + else if (config.comment_parser() == config::comment_parser_t::clang) { + comment_visitor_ = + std::make_unique(source_manager_); + } +} + +clang::SourceManager &translation_unit_visitor::source_manager() const +{ + return source_manager_; +} + +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); + + const auto *comment = + decl.getASTContext().getRawCommentForDeclNoCache(&decl); + + if (comment != nullptr) { + // 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()))); + } +} + +void translation_unit_visitor::set_source_location( + const clang::Decl &decl, clanguml::common::model::source_location &element) +{ + if (decl.getLocation().isValid()) { + element.set_file(source_manager_.getFilename(decl.getLocation()).str()); + element.set_line( + source_manager_.getSpellingLineNumber(decl.getLocation())); + } +} + +} \ No newline at end of file diff --git a/src/common/visitor/translation_unit_visitor.h b/src/common/visitor/translation_unit_visitor.h new file mode 100644 index 00000000..bb82075f --- /dev/null +++ b/src/common/visitor/translation_unit_visitor.h @@ -0,0 +1,62 @@ +/** + * src/common/visitor/translation_unit_visitor.h + * + * Copyright (c) 2021-2022 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. + */ +#pragma once + +#include "comment/comment_visitor.h" +#include "config/config.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace clanguml::common::visitor { + +using found_relationships_t = + std::vector>; + +class translation_unit_visitor { +public: + explicit translation_unit_visitor( + clang::SourceManager &sm, const clanguml::config::diagram &config); + + clang::SourceManager &source_manager() const; + + void finalize(); + +protected: + void set_source_location(const clang::Decl &decl, + clanguml::common::model::source_location &element); + + void process_comment(const clang::NamedDecl &decl, + clanguml::common::model::decorated_element &e); + +private: + clang::SourceManager &source_manager_; + + // Reference to diagram config + const clanguml::config::diagram &config_; + + std::unique_ptr comment_visitor_; +}; +} diff --git a/src/config/config.cc b/src/config/config.cc index 377a7d70..d8e0ffff 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -100,6 +100,7 @@ void inheritable_diagram_options::inherit( git.override(parent.git); base_directory.override(parent.base_directory); relative_to.override(parent.relative_to); + comment_parser.override(parent.comment_parser); } std::string inheritable_diagram_options::simplify_template_type( @@ -284,6 +285,21 @@ void get_option( } } +template <> +void get_option(const Node &node, + clanguml::config::option &option) +{ + if (node[option.name]) { + const auto &val = node[option.name].as(); + if (val == "plain") + option.set(clanguml::config::comment_parser_t::plain); + else if (val == "clang") + option.set(clanguml::config::comment_parser_t::clang); + else + throw std::runtime_error("Invalid comment_parser value: " + val); + } +} + std::shared_ptr parse_diagram_config(const Node &d) { const auto diagram_type = d["type"].as(); @@ -532,6 +548,8 @@ template bool decode_diagram(const Node &node, T &rhs) get_option(node, rhs.puml); get_option(node, rhs.git); get_option(node, rhs.generate_links); + get_option(node, rhs.type_aliases); + get_option(node, rhs.comment_parser); return true; } @@ -552,6 +570,7 @@ template <> struct convert { get_option(node, rhs.generate_packages); get_option(node, rhs.relationship_hints); get_option(node, rhs.type_aliases); + // get_option(node, rhs.comment_parser); rhs.initialize_relationship_hints(); rhs.initialize_type_aliases(); diff --git a/src/config/config.h b/src/config/config.h index 2117768f..7d659b6d 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -37,6 +37,8 @@ namespace config { enum class method_arguments { full, abbreviated, none }; +enum class comment_parser_t { plain, clang }; + struct plantuml { std::vector before; std::vector after; @@ -138,6 +140,8 @@ struct inheritable_diagram_options { option generate_system_headers{"generate_system_headers", false}; option relationship_hints{"relationship_hints"}; option type_aliases{"type_aliases"}; + option comment_parser{ + "comment_parser", comment_parser_t::plain}; void inherit(const inheritable_diagram_options &parent); diff --git a/src/include_diagram/visitor/translation_unit_visitor.cc b/src/include_diagram/visitor/translation_unit_visitor.cc index dda9dbc6..122b270b 100644 --- a/src/include_diagram/visitor/translation_unit_visitor.cc +++ b/src/include_diagram/visitor/translation_unit_visitor.cc @@ -37,7 +37,7 @@ translation_unit_visitor::include_visitor::include_visitor( clang::SourceManager &sm, clanguml::include_diagram::model::diagram &diagram, const clanguml::config::include_diagram &config) - : source_manager_{sm} + : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} { @@ -55,7 +55,7 @@ void translation_unit_visitor::include_visitor::InclusionDirective( using common::model::source_file_t; auto current_file = - std::filesystem::path{source_manager_.getFilename(hash_loc).str()}; + std::filesystem::path{source_manager().getFilename(hash_loc).str()}; current_file = std::filesystem::absolute(current_file); current_file = current_file.lexically_normal(); diff --git a/src/include_diagram/visitor/translation_unit_visitor.h b/src/include_diagram/visitor/translation_unit_visitor.h index c74bcb19..d1c7467c 100644 --- a/src/include_diagram/visitor/translation_unit_visitor.h +++ b/src/include_diagram/visitor/translation_unit_visitor.h @@ -19,6 +19,7 @@ #include "common/model/enums.h" #include "common/model/package.h" +#include "common/visitor/translation_unit_visitor.h" #include "config/config.h" #include "include_diagram/model/diagram.h" @@ -39,7 +40,8 @@ class translation_unit_visitor public: // This is an internal class for convenience to be able to access the // include_visitor type from translation_unit_visitor type - class include_visitor : public clang::PPCallbacks { + class include_visitor : public clang::PPCallbacks, + public common::visitor::translation_unit_visitor { public: include_visitor(clang::SourceManager &sm, clanguml::include_diagram::model::diagram &diagram, @@ -74,8 +76,6 @@ public: } private: - clang::SourceManager &source_manager_; - // Reference to the output diagram model clanguml::include_diagram::model::diagram &diagram_; diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index a4eb1d49..efb5bf2e 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -39,7 +39,7 @@ using clanguml::package_diagram::model::diagram; translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::package_diagram::model::diagram &diagram, const clanguml::config::package_diagram &config) - : source_manager_{sm} + : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} { @@ -107,7 +107,7 @@ bool translation_unit_visitor::VisitFunctionDecl( assert(function_declaration != nullptr); // Skip system headers - if (source_manager_.isInSystemHeader( + if (source_manager().isInSystemHeader( function_declaration->getSourceRange().getBegin())) return true; @@ -130,7 +130,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) assert(cls != nullptr); // Skip system headers - if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; // Templated records are handled by VisitClassTemplateDecl() diff --git a/src/package_diagram/visitor/translation_unit_visitor.h b/src/package_diagram/visitor/translation_unit_visitor.h index 0be26d7a..097a58a4 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.h +++ b/src/package_diagram/visitor/translation_unit_visitor.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/visitor/translation_unit_visitor.h" #include "config/config.h" #include "package_diagram/model/diagram.h" @@ -37,7 +38,8 @@ using found_relationships_t = common::model::relationship_t>>; class translation_unit_visitor - : public clang::RecursiveASTVisitor { + : public clang::RecursiveASTVisitor, + public common::visitor::translation_unit_visitor { public: translation_unit_visitor(clang::SourceManager &sm, clanguml::package_diagram::model::diagram &diagram, @@ -88,33 +90,6 @@ private: void add_relationships( clang::DeclContext *cls, found_relationships_t &relationships); - template - void process_comment( - const ClangDecl &decl, clanguml::common::model::decorated_element &e) - { - const auto *comment = - decl.getASTContext().getRawCommentForDeclNoCache(&decl); - - if (comment != nullptr) { - e.set_comment(comment->getFormattedText( - source_manager_, decl.getASTContext().getDiagnostics())); - e.add_decorators(decorators::parse(e.comment().value())); - } - } - - void set_source_location(const clang::Decl &decl, - clanguml::common::model::source_location &element) - { - if (decl.getLocation().isValid()) { - element.set_file( - source_manager_.getFilename(decl.getLocation()).str()); - element.set_line( - source_manager_.getSpellingLineNumber(decl.getLocation())); - } - } - - clang::SourceManager &source_manager_; - // Reference to the output diagram model clanguml::package_diagram::model::diagram &diagram_; diff --git a/tests/t00002/.clang-uml b/tests/t00002/.clang-uml index 2304d547..4e0840f2 100644 --- a/tests/t00002/.clang-uml +++ b/tests/t00002/.clang-uml @@ -5,6 +5,7 @@ diagrams: type: class glob: - ../../tests/t00002/t00002.cc + comment_parser: clang using_namespace: - clanguml::t00002 include: diff --git a/tests/t00002/t00002.cc b/tests/t00002/t00002.cc index 1cdee02e..aa862420 100644 --- a/tests/t00002/t00002.cc +++ b/tests/t00002/t00002.cc @@ -3,7 +3,7 @@ namespace clanguml { namespace t00002 { -/// This is class A +/// \brief This is class A class A { public: /// Abstract foo_a @@ -12,7 +12,7 @@ public: virtual void foo_c() = 0; }; -/// This is class B +/// \brief This is class B class B : public A { public: virtual void foo_a() override { } diff --git a/tests/t00050/.clang-uml b/tests/t00050/.clang-uml index 43084a27..724a323d 100644 --- a/tests/t00050/.clang-uml +++ b/tests/t00050/.clang-uml @@ -5,6 +5,7 @@ diagrams: type: class glob: - ../../tests/t00050/t00050.cc + comment_parser: clang include: namespaces: - clanguml::t00050 @@ -12,32 +13,49 @@ diagrams: plantuml: after: - > - note left of {{ alias("A") }} - {{ comment("clanguml::t00050::A") }} + note left of {{ alias("A") }} + {{ comment("clanguml::t00050::A").formatted }} end note - - > note right of {{ element("clanguml::t00050::A").alias }} - {{ element("A").comment }} + {% set e=element("clanguml::t00050::A") %} {{ e.comment.formatted }} + end note + - > + note left of {{ alias("C") }} #AABBCC + {{ trim(comment("clanguml::t00050::C").text) }} end note - > - {% for element in diagram.elements %} - {% if element.type == "class" and existsIn(element, "comment") %} - - note top of {{ element.alias }} - {{ element.comment }} + {# Render brief comments and todos, if any were written for an element #} + {% for e in diagram.elements %} + {% if existsIn(e, "comment") and existsIn(e.comment, "brief") %} + + note top of {{ e.alias }} {% if e.type == "class" %} #22AA22 {% else %} #2222AA {% endif %} + {% set c=e.comment %} {{ c.brief.0 }} end note + {% endif %} + {% if existsIn(e, "comment") and existsIn(e.comment, "todo") %} + {% set c=e.comment %} + {% for t in c.todo %} + note top of {{ e.alias }} #882222 + **TODO** + {{ t }} + end note + + {% endfor %} + + {% endif %} + {# Render template paramete if any #} + {% if existsIn(e, "comment") and existsIn(e.comment, "tparam") %} + {% set c=e.comment %} + + note top of {{ e.alias }} #AAAAFF + **Template parameters** + {% for tp in c.tparam %} + //{{ tp.name }}// {{ trim(tp.description) }} + {% endfor %} + end note + {% endif %} {% endfor %} - - > - {% for element in diagram.elements %} - {% if element.type == "enum" and existsIn(element, "comment") %} - - note bottom of {{ element.alias }} - {{ element.comment }} - end note - - {% endif %} - {% endfor %} \ No newline at end of file diff --git a/tests/t00050/t00050.cc b/tests/t00050/t00050.cc index adcba03f..978d95ce 100644 --- a/tests/t00050/t00050.cc +++ b/tests/t00050/t00050.cc @@ -11,15 +11,23 @@ class A { }; /** + * \brief Lorem ipsum + * * Lorem ipsum dolor sit amet consectetur adipiscing elit, urna consequat felis * vehicula class ultricies mollis dictumst, aenean non a in donec nulla. * Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam, * integer placerat et turpis mi eros nec lobortis taciti, vehicula nisl litora * tellus ligula porttitor metus. + * + * \todo 1. Write meaningful comment + * \todo 2. Write tests + * \todo 3. Implement */ class B { }; +/// \brief Long comment example +/// /// Lorem ipsum dolor sit amet consectetur adipiscing elit, urna consequat felis /// vehicula class ultricies mollis dictumst, aenean non a in donec nulla. /// Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam, @@ -56,6 +64,8 @@ namespace utils { /// Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam, /// integer placerat et turpis mi eros nec lobortis taciti, vehicula nisl litora /// tellus ligula porttitor metus. +/// +/// \todo Implement... class D { }; @@ -64,6 +74,20 @@ class D { /// Mollis pretium lorem primis enum class E { E1, E2, E3 }; +/// \brief Simple array wrapper. +/// +/// This class is just for testing tparam parsing, it serves no other +/// purpose. +/// +/// \tparam T Type of array elements. +/// \tparam V Type of regular element. +/// \tparam N Size of T array. +/// +template class F { + T t[N]; + V v; +}; + class NoComment { }; diff --git a/tests/t00050/test_case.h b/tests/t00050/test_case.h index 07c8ad30..e11d0d36 100644 --- a/tests/t00050/test_case.h +++ b/tests/t00050/test_case.h @@ -42,13 +42,13 @@ TEST_CASE("t00050", "[test-case][class]") REQUIRE_THAT(puml, IsEnum(_A("E"))); REQUIRE_THAT(puml, HasNote(_A("A"), "left")); - REQUIRE_THAT(puml, HasNote(_A("A"), "top")); REQUIRE_THAT(puml, HasNote(_A("A"), "right")); REQUIRE_THAT(puml, HasNote(_A("B"), "top")); REQUIRE_THAT(puml, HasNote(_A("C"), "top")); REQUIRE_THAT(puml, HasNote(_A("utils::D"), "top")); - REQUIRE_THAT(puml, HasNote(_A("E"), "bottom")); + REQUIRE_THAT(puml, !HasNote(_A("E"), "bottom")); REQUIRE_THAT(puml, !HasNote(_A("NoComment"), "top")); + REQUIRE_THAT(puml, HasNote(_A("F"), "top")); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/test_cases.cc b/tests/test_cases.cc index e06c72a4..5f5381b1 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -25,7 +25,7 @@ void inject_diagram_options(std::shared_ptr diagram) // Inject links config to all test cases clanguml::config::generate_links_config links_config{ R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})", - R"({% if "comment" in element %}{{ abbrv(trim(replace(element.comment, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"}; + R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"}; diagram->generate_links.set(links_config); } diff --git a/tests/test_config.cc b/tests/test_config.cc index 297ce04d..dc2ed3ba 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -44,6 +44,9 @@ TEST_CASE("Test config simple", "[unit-test]") "element.source.file }}#L{{ element.source.line }}"); CHECK(diagram.generate_links().tooltip == "{{ element.comment }}"); + CHECK( + diagram.comment_parser() == clanguml::config::comment_parser_t::clang); + CHECK(contains(diagram.include().access, access_t::kPublic)); CHECK(contains(diagram.include().access, access_t::kProtected)); CHECK(contains(diagram.include().access, access_t::kPrivate)); diff --git a/tests/test_config_data/simple.yml b/tests/test_config_data/simple.yml index 332fb1fa..805123c6 100644 --- a/tests/test_config_data/simple.yml +++ b/tests/test_config_data/simple.yml @@ -7,6 +7,7 @@ diagrams: glob: - src/**/*.cc - src/**/*.h + comment_parser: clang using_namespace: clanguml generate_method_arguments: full generate_packages: true