//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Class to parse doxygen comments into a flat structure for consumption // in e.g. Hover and Code Completion // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H #include "support/Markup.h" #include "clang/AST/Comment.h" #include "clang/AST/CommentLexer.h" #include "clang/AST/CommentParser.h" #include "clang/AST/CommentSema.h" #include "clang/AST/CommentVisitor.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" #include namespace clang { namespace clangd { class SymbolDocCommentVisitor : public comments::ConstCommentVisitor { public: SymbolDocCommentVisitor(comments::FullComment *FC, const CommentOptions &CommentOpts) : Traits(Allocator, CommentOpts), Allocator() { if (!FC) return; for (auto *Block : FC->getBlocks()) { visit(Block); } } SymbolDocCommentVisitor(llvm::StringRef Documentation, const CommentOptions &CommentOpts) : Traits(Allocator, CommentOpts), Allocator() { if (Documentation.empty()) return; CommentWithMarkers.reserve(Documentation.size() + Documentation.count('\n') * 3); preprocessDocumentation(Documentation); SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers); SourceManager &SourceMgr = SourceMgrForFile.get(); // The doxygen Sema requires a Diagostics consumer, since it reports // warnings e.g. when parameters are not documented correctly. These // warnings are not relevant for us, so we can ignore them. SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer); comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits, /*PP=*/nullptr); comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits, SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()), CommentWithMarkers.data(), CommentWithMarkers.data() + CommentWithMarkers.size()); comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits); comments::FullComment *FC = P.parseFullComment(); if (!FC) return; for (auto *Block : FC->getBlocks()) { visit(Block); } // If we have not seen a brief command, use the very first free paragraph as // the brief. if (!BriefParagraph && !FreeParagraphs.empty() && FreeParagraphs.contains(0)) { BriefParagraph = FreeParagraphs.lookup(0); FreeParagraphs.erase(0); } } bool isParameterDocumented(StringRef ParamName) const { return Parameters.contains(ParamName); } bool isTemplateTypeParmDocumented(StringRef ParamName) const { return TemplateParameters.contains(ParamName); } bool hasBriefCommand() const { return BriefParagraph; } bool hasReturnCommand() const { return ReturnParagraph; } bool hasDetailedDoc() const { return !FreeParagraphs.empty() || !BlockCommands.empty(); } /// Converts all unhandled comment commands to a markup document. void detailedDocToMarkup(markup::Document &Out) const; /// Converts the "brief" command(s) to a markup document. void briefToMarkup(markup::Paragraph &Out) const; /// Converts the "return" command(s) to a markup document. void returnToMarkup(markup::Paragraph &Out) const; /// Converts the "retval" command(s) to a markup document. void retvalsToMarkup(markup::Document &Out) const; void visitBlockCommandComment(const comments::BlockCommandComment *B); void templateTypeParmDocToMarkup(StringRef TemplateParamName, markup::Paragraph &Out) const; void templateTypeParmDocToString(StringRef TemplateParamName, llvm::raw_string_ostream &Out) const; void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const; void parameterDocToString(StringRef ParamName, llvm::raw_string_ostream &Out) const; void visitParagraphComment(const comments::ParagraphComment *P) { if (!P->isWhitespace()) { FreeParagraphs[CommentPartIndex] = P; CommentPartIndex++; } } void visitParamCommandComment(const comments::ParamCommandComment *P) { Parameters[P->getParamNameAsWritten()] = P; } void visitTParamCommandComment(const comments::TParamCommandComment *TP) { TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP); } /// \brief Preprocesses the raw documentation string to prepare it for doxygen /// parsing. /// /// This is a workaround to provide better support for markdown in /// doxygen. Clang's doxygen parser e.g. does not handle markdown code blocks. /// /// The documentation string is preprocessed to replace some markdown /// constructs with parsable doxygen commands. E.g. markdown code blocks are /// replaced with doxygen \\code{.lang} ... /// \\endcode blocks. /// /// Additionally, potential doxygen commands inside markdown /// inline code spans are escaped to avoid that doxygen tries to interpret /// them as commands. /// /// \note Although this is a workaround, it is very similar to what /// doxygen itself does for markdown. In doxygen, the first parsing step is /// also a markdown preprocessing step. /// See https://www.doxygen.nl/manual/markdown.html void preprocessDocumentation(StringRef Doc); private: comments::CommandTraits Traits; llvm::BumpPtrAllocator Allocator; std::string CommentWithMarkers; /// Index to keep track of the order of the comments. /// We want to rearange some commands like \\param. /// This index allows us to keep the order of the other comment parts. unsigned CommentPartIndex = 0; /// Paragraph of the "brief" command. const comments::ParagraphComment *BriefParagraph = nullptr; /// Paragraph of the "return" command. const comments::ParagraphComment *ReturnParagraph = nullptr; /// All the "retval" command(s) llvm::SmallVector RetvalCommands; /// All the parsed doxygen block commands. /// They might have special handling internally like \\note or \\warning llvm::SmallDenseMap BlockCommands; /// Parsed paragaph(s) of the "param" comamnd(s) llvm::SmallDenseMap Parameters; /// Parsed paragaph(s) of the "tparam" comamnd(s) llvm::SmallDenseMap TemplateParameters; /// All "free" text paragraphs. llvm::SmallDenseMap FreeParagraphs; }; } // namespace clangd } // namespace clang #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H