/** * @file src/common/visitor/comment/clang_visitor.cc * * Copyright (c) 2021-2024 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" #if LLVM_VERSION_MAJOR > 17 #define CLANG_UML_LLVM_COMMENT_KIND(COMMENT_KIND) \ clang::comments::CommentKind::COMMENT_KIND #else #define CLANG_UML_LLVM_COMMENT_KIND(COMMENT_KIND) \ clang::comments::Comment::COMMENT_KIND##Kind #endif 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::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 == CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) { 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; if (!cmt.contains("paragraph")) cmt["paragraph"] = inja::json::array(); cmt["paragraph"].push_back(paragraph_text); } else if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(TextComment)) { // TODO } else if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(ParamCommandComment)) { visit_param_command( clang::dyn_cast(block), traits, cmt); } else if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(TParamCommandComment)) { visit_tparam_command( clang::dyn_cast(block), traits, cmt); } else if (block_kind == CLANG_UML_LLVM_COMMENT_KIND(BlockCommandComment)) { if (const auto *command = clang::dyn_cast(block); command != nullptr) { const auto *command_info = traits.getCommandInfo(command->getCommandID()); if (command_info->IsBlockCommand && command_info->NumArgs == 0U) { // 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 (const auto *paragraph_it = command->child_begin(); paragraph_it != command->child_end(); ++paragraph_it) { if ((*paragraph_it)->getCommentKind() == CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) { 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; if (command == nullptr) return; const auto name = command->getParamNameAsWritten().str(); for (const auto *it = command->child_begin(); it != command->child_end(); ++it) { if ((*it)->getCommentKind() == CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) { 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; if (command == nullptr) return; const auto name = command->getParamNameAsWritten().str(); for (const auto *it = command->child_begin(); it != command->child_end(); ++it) { if ((*it)->getCommentKind() == CLANG_UML_LLVM_COMMENT_KIND(ParagraphComment)) { 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; if (paragraph == nullptr) return; for (const auto *text_it = paragraph->child_begin(); text_it != paragraph->child_end(); ++text_it) { if ((*text_it)->getCommentKind() == CLANG_UML_LLVM_COMMENT_KIND(TextComment) && clang::dyn_cast(*text_it) != nullptr) { // Merge paragraph lines into a single string text += clang::dyn_cast(*text_it)->getText(); text += "\n"; } } } } // namespace clanguml::common::visitor::comment