blob: 88c7ade6335169c7c872dceb3629f93a344c66bb [file] [log] [blame]
//===--- 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 <string>
namespace clang {
namespace clangd {
class SymbolDocCommentVisitor
: public comments::ConstCommentVisitor<SymbolDocCommentVisitor> {
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<const comments::BlockCommandComment *> RetvalCommands;
/// All the parsed doxygen block commands.
/// They might have special handling internally like \\note or \\warning
llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
BlockCommands;
/// Parsed paragaph(s) of the "param" comamnd(s)
llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
Parameters;
/// Parsed paragaph(s) of the "tparam" comamnd(s)
llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *>
TemplateParameters;
/// All "free" text paragraphs.
llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
FreeParagraphs;
};
} // namespace clangd
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H