| //===--- CommentToXML.cpp - Convert comments to XML representation --------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Index/CommentToXML.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Attr.h" |
| #include "clang/AST/Comment.h" |
| #include "clang/AST/CommentVisitor.h" |
| #include "clang/Format/Format.h" |
| #include "clang/Index/USRGeneration.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/ADT/TinyPtrVector.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace clang; |
| using namespace clang::comments; |
| using namespace clang::index; |
| |
| namespace { |
| |
| /// This comparison will sort parameters with valid index by index, then vararg |
| /// parameters, and invalid (unresolved) parameters last. |
| class ParamCommandCommentCompareIndex { |
| public: |
| bool operator()(const ParamCommandComment *LHS, |
| const ParamCommandComment *RHS) const { |
| unsigned LHSIndex = UINT_MAX; |
| unsigned RHSIndex = UINT_MAX; |
| |
| if (LHS->isParamIndexValid()) { |
| if (LHS->isVarArgParam()) |
| LHSIndex = UINT_MAX - 1; |
| else |
| LHSIndex = LHS->getParamIndex(); |
| } |
| if (RHS->isParamIndexValid()) { |
| if (RHS->isVarArgParam()) |
| RHSIndex = UINT_MAX - 1; |
| else |
| RHSIndex = RHS->getParamIndex(); |
| } |
| return LHSIndex < RHSIndex; |
| } |
| }; |
| |
| /// This comparison will sort template parameters in the following order: |
| /// \li real template parameters (depth = 1) in index order; |
| /// \li all other names (depth > 1); |
| /// \li unresolved names. |
| class TParamCommandCommentComparePosition { |
| public: |
| bool operator()(const TParamCommandComment *LHS, |
| const TParamCommandComment *RHS) const { |
| // Sort unresolved names last. |
| if (!LHS->isPositionValid()) |
| return false; |
| if (!RHS->isPositionValid()) |
| return true; |
| |
| if (LHS->getDepth() > 1) |
| return false; |
| if (RHS->getDepth() > 1) |
| return true; |
| |
| // Sort template parameters in index order. |
| if (LHS->getDepth() == 1 && RHS->getDepth() == 1) |
| return LHS->getIndex(0) < RHS->getIndex(0); |
| |
| // Leave all other names in source order. |
| return true; |
| } |
| }; |
| |
| /// Separate parts of a FullComment. |
| struct FullCommentParts { |
| /// Take a full comment apart and initialize members accordingly. |
| FullCommentParts(const FullComment *C, |
| const CommandTraits &Traits); |
| |
| const BlockContentComment *Brief; |
| const BlockContentComment *Headerfile; |
| const ParagraphComment *FirstParagraph; |
| SmallVector<const BlockCommandComment *, 4> Returns; |
| SmallVector<const ParamCommandComment *, 8> Params; |
| SmallVector<const TParamCommandComment *, 4> TParams; |
| llvm::TinyPtrVector<const BlockCommandComment *> Exceptions; |
| SmallVector<const BlockContentComment *, 8> MiscBlocks; |
| }; |
| |
| FullCommentParts::FullCommentParts(const FullComment *C, |
| const CommandTraits &Traits) : |
| Brief(nullptr), Headerfile(nullptr), FirstParagraph(nullptr) { |
| for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); |
| I != E; ++I) { |
| const Comment *Child = *I; |
| if (!Child) |
| continue; |
| switch (Child->getCommentKind()) { |
| case Comment::NoCommentKind: |
| continue; |
| |
| case Comment::ParagraphCommentKind: { |
| const ParagraphComment *PC = cast<ParagraphComment>(Child); |
| if (PC->isWhitespace()) |
| break; |
| if (!FirstParagraph) |
| FirstParagraph = PC; |
| |
| MiscBlocks.push_back(PC); |
| break; |
| } |
| |
| case Comment::BlockCommandCommentKind: { |
| const BlockCommandComment *BCC = cast<BlockCommandComment>(Child); |
| const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID()); |
| if (!Brief && Info->IsBriefCommand) { |
| Brief = BCC; |
| break; |
| } |
| if (!Headerfile && Info->IsHeaderfileCommand) { |
| Headerfile = BCC; |
| break; |
| } |
| if (Info->IsReturnsCommand) { |
| Returns.push_back(BCC); |
| break; |
| } |
| if (Info->IsThrowsCommand) { |
| Exceptions.push_back(BCC); |
| break; |
| } |
| MiscBlocks.push_back(BCC); |
| break; |
| } |
| |
| case Comment::ParamCommandCommentKind: { |
| const ParamCommandComment *PCC = cast<ParamCommandComment>(Child); |
| if (!PCC->hasParamName()) |
| break; |
| |
| if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) |
| break; |
| |
| Params.push_back(PCC); |
| break; |
| } |
| |
| case Comment::TParamCommandCommentKind: { |
| const TParamCommandComment *TPCC = cast<TParamCommandComment>(Child); |
| if (!TPCC->hasParamName()) |
| break; |
| |
| if (!TPCC->hasNonWhitespaceParagraph()) |
| break; |
| |
| TParams.push_back(TPCC); |
| break; |
| } |
| |
| case Comment::VerbatimBlockCommentKind: |
| MiscBlocks.push_back(cast<BlockCommandComment>(Child)); |
| break; |
| |
| case Comment::VerbatimLineCommentKind: { |
| const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Child); |
| const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID()); |
| if (!Info->IsDeclarationCommand) |
| MiscBlocks.push_back(VLC); |
| break; |
| } |
| |
| case Comment::TextCommentKind: |
| case Comment::InlineCommandCommentKind: |
| case Comment::HTMLStartTagCommentKind: |
| case Comment::HTMLEndTagCommentKind: |
| case Comment::VerbatimBlockLineCommentKind: |
| case Comment::FullCommentKind: |
| llvm_unreachable("AST node of this kind can't be a child of " |
| "a FullComment"); |
| } |
| } |
| |
| // Sort params in order they are declared in the function prototype. |
| // Unresolved parameters are put at the end of the list in the same order |
| // they were seen in the comment. |
| llvm::stable_sort(Params, ParamCommandCommentCompareIndex()); |
| llvm::stable_sort(TParams, TParamCommandCommentComparePosition()); |
| } |
| |
| void printHTMLStartTagComment(const HTMLStartTagComment *C, |
| llvm::raw_svector_ostream &Result) { |
| Result << "<" << C->getTagName(); |
| |
| if (C->getNumAttrs() != 0) { |
| for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) { |
| Result << " "; |
| const HTMLStartTagComment::Attribute &Attr = C->getAttr(i); |
| Result << Attr.Name; |
| if (!Attr.Value.empty()) |
| Result << "=\"" << Attr.Value << "\""; |
| } |
| } |
| |
| if (!C->isSelfClosing()) |
| Result << ">"; |
| else |
| Result << "/>"; |
| } |
| |
| class CommentASTToHTMLConverter : |
| public ConstCommentVisitor<CommentASTToHTMLConverter> { |
| public: |
| /// \param Str accumulator for HTML. |
| CommentASTToHTMLConverter(const FullComment *FC, |
| SmallVectorImpl<char> &Str, |
| const CommandTraits &Traits) : |
| FC(FC), Result(Str), Traits(Traits) |
| { } |
| |
| // Inline content. |
| void visitTextComment(const TextComment *C); |
| void visitInlineCommandComment(const InlineCommandComment *C); |
| void visitHTMLStartTagComment(const HTMLStartTagComment *C); |
| void visitHTMLEndTagComment(const HTMLEndTagComment *C); |
| |
| // Block content. |
| void visitParagraphComment(const ParagraphComment *C); |
| void visitBlockCommandComment(const BlockCommandComment *C); |
| void visitParamCommandComment(const ParamCommandComment *C); |
| void visitTParamCommandComment(const TParamCommandComment *C); |
| void visitVerbatimBlockComment(const VerbatimBlockComment *C); |
| void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); |
| void visitVerbatimLineComment(const VerbatimLineComment *C); |
| |
| void visitFullComment(const FullComment *C); |
| |
| // Helpers. |
| |
| /// Convert a paragraph that is not a block by itself (an argument to some |
| /// command). |
| void visitNonStandaloneParagraphComment(const ParagraphComment *C); |
| |
| void appendToResultWithHTMLEscaping(StringRef S); |
| |
| private: |
| const FullComment *FC; |
| /// Output stream for HTML. |
| llvm::raw_svector_ostream Result; |
| |
| const CommandTraits &Traits; |
| }; |
| } // end unnamed namespace |
| |
| void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) { |
| appendToResultWithHTMLEscaping(C->getText()); |
| } |
| |
| void CommentASTToHTMLConverter::visitInlineCommandComment( |
| const InlineCommandComment *C) { |
| // Nothing to render if no arguments supplied. |
| if (C->getNumArgs() == 0) |
| return; |
| |
| // Nothing to render if argument is empty. |
| StringRef Arg0 = C->getArgText(0); |
| if (Arg0.empty()) |
| return; |
| |
| switch (C->getRenderKind()) { |
| case InlineCommandComment::RenderNormal: |
| for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { |
| appendToResultWithHTMLEscaping(C->getArgText(i)); |
| Result << " "; |
| } |
| return; |
| |
| case InlineCommandComment::RenderBold: |
| assert(C->getNumArgs() == 1); |
| Result << "<b>"; |
| appendToResultWithHTMLEscaping(Arg0); |
| Result << "</b>"; |
| return; |
| case InlineCommandComment::RenderMonospaced: |
| assert(C->getNumArgs() == 1); |
| Result << "<tt>"; |
| appendToResultWithHTMLEscaping(Arg0); |
| Result<< "</tt>"; |
| return; |
| case InlineCommandComment::RenderEmphasized: |
| assert(C->getNumArgs() == 1); |
| Result << "<em>"; |
| appendToResultWithHTMLEscaping(Arg0); |
| Result << "</em>"; |
| return; |
| } |
| } |
| |
| void CommentASTToHTMLConverter::visitHTMLStartTagComment( |
| const HTMLStartTagComment *C) { |
| printHTMLStartTagComment(C, Result); |
| } |
| |
| void CommentASTToHTMLConverter::visitHTMLEndTagComment( |
| const HTMLEndTagComment *C) { |
| Result << "</" << C->getTagName() << ">"; |
| } |
| |
| void CommentASTToHTMLConverter::visitParagraphComment( |
| const ParagraphComment *C) { |
| if (C->isWhitespace()) |
| return; |
| |
| Result << "<p>"; |
| for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); |
| I != E; ++I) { |
| visit(*I); |
| } |
| Result << "</p>"; |
| } |
| |
| void CommentASTToHTMLConverter::visitBlockCommandComment( |
| const BlockCommandComment *C) { |
| const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID()); |
| if (Info->IsBriefCommand) { |
| Result << "<p class=\"para-brief\">"; |
| visitNonStandaloneParagraphComment(C->getParagraph()); |
| Result << "</p>"; |
| return; |
| } |
| if (Info->IsReturnsCommand) { |
| Result << "<p class=\"para-returns\">" |
| "<span class=\"word-returns\">Returns</span> "; |
| visitNonStandaloneParagraphComment(C->getParagraph()); |
| Result << "</p>"; |
| return; |
| } |
| // We don't know anything about this command. Just render the paragraph. |
| visit(C->getParagraph()); |
| } |
| |
| void CommentASTToHTMLConverter::visitParamCommandComment( |
| const ParamCommandComment *C) { |
| if (C->isParamIndexValid()) { |
| if (C->isVarArgParam()) { |
| Result << "<dt class=\"param-name-index-vararg\">"; |
| appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); |
| } else { |
| Result << "<dt class=\"param-name-index-" |
| << C->getParamIndex() |
| << "\">"; |
| appendToResultWithHTMLEscaping(C->getParamName(FC)); |
| } |
| } else { |
| Result << "<dt class=\"param-name-index-invalid\">"; |
| appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); |
| } |
| Result << "</dt>"; |
| |
| if (C->isParamIndexValid()) { |
| if (C->isVarArgParam()) |
| Result << "<dd class=\"param-descr-index-vararg\">"; |
| else |
| Result << "<dd class=\"param-descr-index-" |
| << C->getParamIndex() |
| << "\">"; |
| } else |
| Result << "<dd class=\"param-descr-index-invalid\">"; |
| |
| visitNonStandaloneParagraphComment(C->getParagraph()); |
| Result << "</dd>"; |
| } |
| |
| void CommentASTToHTMLConverter::visitTParamCommandComment( |
| const TParamCommandComment *C) { |
| if (C->isPositionValid()) { |
| if (C->getDepth() == 1) |
| Result << "<dt class=\"tparam-name-index-" |
| << C->getIndex(0) |
| << "\">"; |
| else |
| Result << "<dt class=\"tparam-name-index-other\">"; |
| appendToResultWithHTMLEscaping(C->getParamName(FC)); |
| } else { |
| Result << "<dt class=\"tparam-name-index-invalid\">"; |
| appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); |
| } |
| |
| Result << "</dt>"; |
| |
| if (C->isPositionValid()) { |
| if (C->getDepth() == 1) |
| Result << "<dd class=\"tparam-descr-index-" |
| << C->getIndex(0) |
| << "\">"; |
| else |
| Result << "<dd class=\"tparam-descr-index-other\">"; |
| } else |
| Result << "<dd class=\"tparam-descr-index-invalid\">"; |
| |
| visitNonStandaloneParagraphComment(C->getParagraph()); |
| Result << "</dd>"; |
| } |
| |
| void CommentASTToHTMLConverter::visitVerbatimBlockComment( |
| const VerbatimBlockComment *C) { |
| unsigned NumLines = C->getNumLines(); |
| if (NumLines == 0) |
| return; |
| |
| Result << "<pre>"; |
| for (unsigned i = 0; i != NumLines; ++i) { |
| appendToResultWithHTMLEscaping(C->getText(i)); |
| if (i + 1 != NumLines) |
| Result << '\n'; |
| } |
| Result << "</pre>"; |
| } |
| |
| void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( |
| const VerbatimBlockLineComment *C) { |
| llvm_unreachable("should not see this AST node"); |
| } |
| |
| void CommentASTToHTMLConverter::visitVerbatimLineComment( |
| const VerbatimLineComment *C) { |
| Result << "<pre>"; |
| appendToResultWithHTMLEscaping(C->getText()); |
| Result << "</pre>"; |
| } |
| |
| void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { |
| FullCommentParts Parts(C, Traits); |
| |
| bool FirstParagraphIsBrief = false; |
| if (Parts.Headerfile) |
| visit(Parts.Headerfile); |
| if (Parts.Brief) |
| visit(Parts.Brief); |
| else if (Parts.FirstParagraph) { |
| Result << "<p class=\"para-brief\">"; |
| visitNonStandaloneParagraphComment(Parts.FirstParagraph); |
| Result << "</p>"; |
| FirstParagraphIsBrief = true; |
| } |
| |
| for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { |
| const Comment *C = Parts.MiscBlocks[i]; |
| if (FirstParagraphIsBrief && C == Parts.FirstParagraph) |
| continue; |
| visit(C); |
| } |
| |
| if (Parts.TParams.size() != 0) { |
| Result << "<dl>"; |
| for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) |
| visit(Parts.TParams[i]); |
| Result << "</dl>"; |
| } |
| |
| if (Parts.Params.size() != 0) { |
| Result << "<dl>"; |
| for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) |
| visit(Parts.Params[i]); |
| Result << "</dl>"; |
| } |
| |
| if (Parts.Returns.size() != 0) { |
| Result << "<div class=\"result-discussion\">"; |
| for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) |
| visit(Parts.Returns[i]); |
| Result << "</div>"; |
| } |
| |
| } |
| |
| void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment( |
| const ParagraphComment *C) { |
| if (!C) |
| return; |
| |
| for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); |
| I != E; ++I) { |
| visit(*I); |
| } |
| } |
| |
| void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) { |
| for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { |
| const char C = *I; |
| switch (C) { |
| case '&': |
| Result << "&"; |
| break; |
| case '<': |
| Result << "<"; |
| break; |
| case '>': |
| Result << ">"; |
| break; |
| case '"': |
| Result << """; |
| break; |
| case '\'': |
| Result << "'"; |
| break; |
| case '/': |
| Result << "/"; |
| break; |
| default: |
| Result << C; |
| break; |
| } |
| } |
| } |
| |
| namespace { |
| class CommentASTToXMLConverter : |
| public ConstCommentVisitor<CommentASTToXMLConverter> { |
| public: |
| /// \param Str accumulator for XML. |
| CommentASTToXMLConverter(const FullComment *FC, |
| SmallVectorImpl<char> &Str, |
| const CommandTraits &Traits, |
| const SourceManager &SM) : |
| FC(FC), Result(Str), Traits(Traits), SM(SM) { } |
| |
| // Inline content. |
| void visitTextComment(const TextComment *C); |
| void visitInlineCommandComment(const InlineCommandComment *C); |
| void visitHTMLStartTagComment(const HTMLStartTagComment *C); |
| void visitHTMLEndTagComment(const HTMLEndTagComment *C); |
| |
| // Block content. |
| void visitParagraphComment(const ParagraphComment *C); |
| |
| void appendParagraphCommentWithKind(const ParagraphComment *C, |
| StringRef Kind); |
| |
| void visitBlockCommandComment(const BlockCommandComment *C); |
| void visitParamCommandComment(const ParamCommandComment *C); |
| void visitTParamCommandComment(const TParamCommandComment *C); |
| void visitVerbatimBlockComment(const VerbatimBlockComment *C); |
| void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); |
| void visitVerbatimLineComment(const VerbatimLineComment *C); |
| |
| void visitFullComment(const FullComment *C); |
| |
| // Helpers. |
| void appendToResultWithXMLEscaping(StringRef S); |
| void appendToResultWithCDATAEscaping(StringRef S); |
| |
| void formatTextOfDeclaration(const DeclInfo *DI, |
| SmallString<128> &Declaration); |
| |
| private: |
| const FullComment *FC; |
| |
| /// Output stream for XML. |
| llvm::raw_svector_ostream Result; |
| |
| const CommandTraits &Traits; |
| const SourceManager &SM; |
| }; |
| |
| void getSourceTextOfDeclaration(const DeclInfo *ThisDecl, |
| SmallVectorImpl<char> &Str) { |
| ASTContext &Context = ThisDecl->CurrentDecl->getASTContext(); |
| const LangOptions &LangOpts = Context.getLangOpts(); |
| llvm::raw_svector_ostream OS(Str); |
| PrintingPolicy PPolicy(LangOpts); |
| PPolicy.PolishForDeclaration = true; |
| PPolicy.TerseOutput = true; |
| PPolicy.ConstantsAsWritten = true; |
| ThisDecl->CurrentDecl->print(OS, PPolicy, |
| /*Indentation*/0, /*PrintInstantiation*/false); |
| } |
| |
| void CommentASTToXMLConverter::formatTextOfDeclaration( |
| const DeclInfo *DI, SmallString<128> &Declaration) { |
| // Formatting API expects null terminated input string. |
| StringRef StringDecl(Declaration.c_str(), Declaration.size()); |
| |
| // Formatter specific code. |
| unsigned Offset = 0; |
| unsigned Length = Declaration.size(); |
| |
| format::FormatStyle Style = format::getLLVMStyle(); |
| Style.FixNamespaceComments = false; |
| tooling::Replacements Replaces = |
| reformat(Style, StringDecl, tooling::Range(Offset, Length), "xmldecl.xd"); |
| auto FormattedStringDecl = applyAllReplacements(StringDecl, Replaces); |
| if (static_cast<bool>(FormattedStringDecl)) { |
| Declaration = *FormattedStringDecl; |
| } |
| } |
| |
| } // end unnamed namespace |
| |
| void CommentASTToXMLConverter::visitTextComment(const TextComment *C) { |
| appendToResultWithXMLEscaping(C->getText()); |
| } |
| |
| void CommentASTToXMLConverter::visitInlineCommandComment( |
| const InlineCommandComment *C) { |
| // Nothing to render if no arguments supplied. |
| if (C->getNumArgs() == 0) |
| return; |
| |
| // Nothing to render if argument is empty. |
| StringRef Arg0 = C->getArgText(0); |
| if (Arg0.empty()) |
| return; |
| |
| switch (C->getRenderKind()) { |
| case InlineCommandComment::RenderNormal: |
| for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { |
| appendToResultWithXMLEscaping(C->getArgText(i)); |
| Result << " "; |
| } |
| return; |
| case InlineCommandComment::RenderBold: |
| assert(C->getNumArgs() == 1); |
| Result << "<bold>"; |
| appendToResultWithXMLEscaping(Arg0); |
| Result << "</bold>"; |
| return; |
| case InlineCommandComment::RenderMonospaced: |
| assert(C->getNumArgs() == 1); |
| Result << "<monospaced>"; |
| appendToResultWithXMLEscaping(Arg0); |
| Result << "</monospaced>"; |
| return; |
| case InlineCommandComment::RenderEmphasized: |
| assert(C->getNumArgs() == 1); |
| Result << "<emphasized>"; |
| appendToResultWithXMLEscaping(Arg0); |
| Result << "</emphasized>"; |
| return; |
| } |
| } |
| |
| void CommentASTToXMLConverter::visitHTMLStartTagComment( |
| const HTMLStartTagComment *C) { |
| Result << "<rawHTML"; |
| if (C->isMalformed()) |
| Result << " isMalformed=\"1\""; |
| Result << ">"; |
| { |
| SmallString<32> Tag; |
| { |
| llvm::raw_svector_ostream TagOS(Tag); |
| printHTMLStartTagComment(C, TagOS); |
| } |
| appendToResultWithCDATAEscaping(Tag); |
| } |
| Result << "</rawHTML>"; |
| } |
| |
| void |
| CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { |
| Result << "<rawHTML"; |
| if (C->isMalformed()) |
| Result << " isMalformed=\"1\""; |
| Result << "></" << C->getTagName() << "></rawHTML>"; |
| } |
| |
| void |
| CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) { |
| appendParagraphCommentWithKind(C, StringRef()); |
| } |
| |
| void CommentASTToXMLConverter::appendParagraphCommentWithKind( |
| const ParagraphComment *C, |
| StringRef ParagraphKind) { |
| if (C->isWhitespace()) |
| return; |
| |
| if (ParagraphKind.empty()) |
| Result << "<Para>"; |
| else |
| Result << "<Para kind=\"" << ParagraphKind << "\">"; |
| |
| for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); |
| I != E; ++I) { |
| visit(*I); |
| } |
| Result << "</Para>"; |
| } |
| |
| void CommentASTToXMLConverter::visitBlockCommandComment( |
| const BlockCommandComment *C) { |
| StringRef ParagraphKind; |
| |
| switch (C->getCommandID()) { |
| case CommandTraits::KCI_attention: |
| case CommandTraits::KCI_author: |
| case CommandTraits::KCI_authors: |
| case CommandTraits::KCI_bug: |
| case CommandTraits::KCI_copyright: |
| case CommandTraits::KCI_date: |
| case CommandTraits::KCI_invariant: |
| case CommandTraits::KCI_note: |
| case CommandTraits::KCI_post: |
| case CommandTraits::KCI_pre: |
| case CommandTraits::KCI_remark: |
| case CommandTraits::KCI_remarks: |
| case CommandTraits::KCI_sa: |
| case CommandTraits::KCI_see: |
| case CommandTraits::KCI_since: |
| case CommandTraits::KCI_todo: |
| case CommandTraits::KCI_version: |
| case CommandTraits::KCI_warning: |
| ParagraphKind = C->getCommandName(Traits); |
| break; |
| default: |
| break; |
| } |
| |
| appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind); |
| } |
| |
| void CommentASTToXMLConverter::visitParamCommandComment( |
| const ParamCommandComment *C) { |
| Result << "<Parameter><Name>"; |
| appendToResultWithXMLEscaping(C->isParamIndexValid() |
| ? C->getParamName(FC) |
| : C->getParamNameAsWritten()); |
| Result << "</Name>"; |
| |
| if (C->isParamIndexValid()) { |
| if (C->isVarArgParam()) |
| Result << "<IsVarArg />"; |
| else |
| Result << "<Index>" << C->getParamIndex() << "</Index>"; |
| } |
| |
| Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">"; |
| switch (C->getDirection()) { |
| case ParamCommandComment::In: |
| Result << "in"; |
| break; |
| case ParamCommandComment::Out: |
| Result << "out"; |
| break; |
| case ParamCommandComment::InOut: |
| Result << "in,out"; |
| break; |
| } |
| Result << "</Direction><Discussion>"; |
| visit(C->getParagraph()); |
| Result << "</Discussion></Parameter>"; |
| } |
| |
| void CommentASTToXMLConverter::visitTParamCommandComment( |
| const TParamCommandComment *C) { |
| Result << "<Parameter><Name>"; |
| appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC) |
| : C->getParamNameAsWritten()); |
| Result << "</Name>"; |
| |
| if (C->isPositionValid() && C->getDepth() == 1) { |
| Result << "<Index>" << C->getIndex(0) << "</Index>"; |
| } |
| |
| Result << "<Discussion>"; |
| visit(C->getParagraph()); |
| Result << "</Discussion></Parameter>"; |
| } |
| |
| void CommentASTToXMLConverter::visitVerbatimBlockComment( |
| const VerbatimBlockComment *C) { |
| unsigned NumLines = C->getNumLines(); |
| if (NumLines == 0) |
| return; |
| |
| switch (C->getCommandID()) { |
| case CommandTraits::KCI_code: |
| Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">"; |
| break; |
| default: |
| Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; |
| break; |
| } |
| for (unsigned i = 0; i != NumLines; ++i) { |
| appendToResultWithXMLEscaping(C->getText(i)); |
| if (i + 1 != NumLines) |
| Result << '\n'; |
| } |
| Result << "</Verbatim>"; |
| } |
| |
| void CommentASTToXMLConverter::visitVerbatimBlockLineComment( |
| const VerbatimBlockLineComment *C) { |
| llvm_unreachable("should not see this AST node"); |
| } |
| |
| void CommentASTToXMLConverter::visitVerbatimLineComment( |
| const VerbatimLineComment *C) { |
| Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; |
| appendToResultWithXMLEscaping(C->getText()); |
| Result << "</Verbatim>"; |
| } |
| |
| void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { |
| FullCommentParts Parts(C, Traits); |
| |
| const DeclInfo *DI = C->getDeclInfo(); |
| StringRef RootEndTag; |
| if (DI) { |
| switch (DI->getKind()) { |
| case DeclInfo::OtherKind: |
| RootEndTag = "</Other>"; |
| Result << "<Other"; |
| break; |
| case DeclInfo::FunctionKind: |
| RootEndTag = "</Function>"; |
| Result << "<Function"; |
| switch (DI->TemplateKind) { |
| case DeclInfo::NotTemplate: |
| break; |
| case DeclInfo::Template: |
| Result << " templateKind=\"template\""; |
| break; |
| case DeclInfo::TemplateSpecialization: |
| Result << " templateKind=\"specialization\""; |
| break; |
| case DeclInfo::TemplatePartialSpecialization: |
| llvm_unreachable("partial specializations of functions " |
| "are not allowed in C++"); |
| } |
| if (DI->IsInstanceMethod) |
| Result << " isInstanceMethod=\"1\""; |
| if (DI->IsClassMethod) |
| Result << " isClassMethod=\"1\""; |
| break; |
| case DeclInfo::ClassKind: |
| RootEndTag = "</Class>"; |
| Result << "<Class"; |
| switch (DI->TemplateKind) { |
| case DeclInfo::NotTemplate: |
| break; |
| case DeclInfo::Template: |
| Result << " templateKind=\"template\""; |
| break; |
| case DeclInfo::TemplateSpecialization: |
| Result << " templateKind=\"specialization\""; |
| break; |
| case DeclInfo::TemplatePartialSpecialization: |
| Result << " templateKind=\"partialSpecialization\""; |
| break; |
| } |
| break; |
| case DeclInfo::VariableKind: |
| RootEndTag = "</Variable>"; |
| Result << "<Variable"; |
| break; |
| case DeclInfo::NamespaceKind: |
| RootEndTag = "</Namespace>"; |
| Result << "<Namespace"; |
| break; |
| case DeclInfo::TypedefKind: |
| RootEndTag = "</Typedef>"; |
| Result << "<Typedef"; |
| break; |
| case DeclInfo::EnumKind: |
| RootEndTag = "</Enum>"; |
| Result << "<Enum"; |
| break; |
| } |
| |
| { |
| // Print line and column number. |
| SourceLocation Loc = DI->CurrentDecl->getLocation(); |
| std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
| FileID FID = LocInfo.first; |
| unsigned FileOffset = LocInfo.second; |
| |
| if (FID.isValid()) { |
| if (const FileEntry *FE = SM.getFileEntryForID(FID)) { |
| Result << " file=\""; |
| appendToResultWithXMLEscaping(FE->getName()); |
| Result << "\""; |
| } |
| Result << " line=\"" << SM.getLineNumber(FID, FileOffset) |
| << "\" column=\"" << SM.getColumnNumber(FID, FileOffset) |
| << "\""; |
| } |
| } |
| |
| // Finish the root tag. |
| Result << ">"; |
| |
| bool FoundName = false; |
| if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) { |
| if (DeclarationName DeclName = ND->getDeclName()) { |
| Result << "<Name>"; |
| std::string Name = DeclName.getAsString(); |
| appendToResultWithXMLEscaping(Name); |
| FoundName = true; |
| Result << "</Name>"; |
| } |
| } |
| if (!FoundName) |
| Result << "<Name><anonymous></Name>"; |
| |
| { |
| // Print USR. |
| SmallString<128> USR; |
| generateUSRForDecl(DI->CommentDecl, USR); |
| if (!USR.empty()) { |
| Result << "<USR>"; |
| appendToResultWithXMLEscaping(USR); |
| Result << "</USR>"; |
| } |
| } |
| } else { |
| // No DeclInfo -- just emit some root tag and name tag. |
| RootEndTag = "</Other>"; |
| Result << "<Other><Name>unknown</Name>"; |
| } |
| |
| if (Parts.Headerfile) { |
| Result << "<Headerfile>"; |
| visit(Parts.Headerfile); |
| Result << "</Headerfile>"; |
| } |
| |
| { |
| // Pretty-print the declaration. |
| Result << "<Declaration>"; |
| SmallString<128> Declaration; |
| getSourceTextOfDeclaration(DI, Declaration); |
| formatTextOfDeclaration(DI, Declaration); |
| appendToResultWithXMLEscaping(Declaration); |
| Result << "</Declaration>"; |
| } |
| |
| bool FirstParagraphIsBrief = false; |
| if (Parts.Brief) { |
| Result << "<Abstract>"; |
| visit(Parts.Brief); |
| Result << "</Abstract>"; |
| } else if (Parts.FirstParagraph) { |
| Result << "<Abstract>"; |
| visit(Parts.FirstParagraph); |
| Result << "</Abstract>"; |
| FirstParagraphIsBrief = true; |
| } |
| |
| if (Parts.TParams.size() != 0) { |
| Result << "<TemplateParameters>"; |
| for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) |
| visit(Parts.TParams[i]); |
| Result << "</TemplateParameters>"; |
| } |
| |
| if (Parts.Params.size() != 0) { |
| Result << "<Parameters>"; |
| for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) |
| visit(Parts.Params[i]); |
| Result << "</Parameters>"; |
| } |
| |
| if (Parts.Exceptions.size() != 0) { |
| Result << "<Exceptions>"; |
| for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i) |
| visit(Parts.Exceptions[i]); |
| Result << "</Exceptions>"; |
| } |
| |
| if (Parts.Returns.size() != 0) { |
| Result << "<ResultDiscussion>"; |
| for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) |
| visit(Parts.Returns[i]); |
| Result << "</ResultDiscussion>"; |
| } |
| |
| if (DI->CommentDecl->hasAttrs()) { |
| const AttrVec &Attrs = DI->CommentDecl->getAttrs(); |
| for (unsigned i = 0, e = Attrs.size(); i != e; i++) { |
| const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]); |
| if (!AA) { |
| if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) { |
| if (DA->getMessage().empty()) |
| Result << "<Deprecated/>"; |
| else { |
| Result << "<Deprecated>"; |
| appendToResultWithXMLEscaping(DA->getMessage()); |
| Result << "</Deprecated>"; |
| } |
| } |
| else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) { |
| if (UA->getMessage().empty()) |
| Result << "<Unavailable/>"; |
| else { |
| Result << "<Unavailable>"; |
| appendToResultWithXMLEscaping(UA->getMessage()); |
| Result << "</Unavailable>"; |
| } |
| } |
| continue; |
| } |
| |
| // 'availability' attribute. |
| Result << "<Availability"; |
| StringRef Distribution; |
| if (AA->getPlatform()) { |
| Distribution = AvailabilityAttr::getPrettyPlatformName( |
| AA->getPlatform()->getName()); |
| if (Distribution.empty()) |
| Distribution = AA->getPlatform()->getName(); |
| } |
| Result << " distribution=\"" << Distribution << "\">"; |
| VersionTuple IntroducedInVersion = AA->getIntroduced(); |
| if (!IntroducedInVersion.empty()) { |
| Result << "<IntroducedInVersion>" |
| << IntroducedInVersion.getAsString() |
| << "</IntroducedInVersion>"; |
| } |
| VersionTuple DeprecatedInVersion = AA->getDeprecated(); |
| if (!DeprecatedInVersion.empty()) { |
| Result << "<DeprecatedInVersion>" |
| << DeprecatedInVersion.getAsString() |
| << "</DeprecatedInVersion>"; |
| } |
| VersionTuple RemovedAfterVersion = AA->getObsoleted(); |
| if (!RemovedAfterVersion.empty()) { |
| Result << "<RemovedAfterVersion>" |
| << RemovedAfterVersion.getAsString() |
| << "</RemovedAfterVersion>"; |
| } |
| StringRef DeprecationSummary = AA->getMessage(); |
| if (!DeprecationSummary.empty()) { |
| Result << "<DeprecationSummary>"; |
| appendToResultWithXMLEscaping(DeprecationSummary); |
| Result << "</DeprecationSummary>"; |
| } |
| if (AA->getUnavailable()) |
| Result << "<Unavailable/>"; |
| Result << "</Availability>"; |
| } |
| } |
| |
| { |
| bool StartTagEmitted = false; |
| for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { |
| const Comment *C = Parts.MiscBlocks[i]; |
| if (FirstParagraphIsBrief && C == Parts.FirstParagraph) |
| continue; |
| if (!StartTagEmitted) { |
| Result << "<Discussion>"; |
| StartTagEmitted = true; |
| } |
| visit(C); |
| } |
| if (StartTagEmitted) |
| Result << "</Discussion>"; |
| } |
| |
| Result << RootEndTag; |
| } |
| |
| void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) { |
| for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { |
| const char C = *I; |
| switch (C) { |
| case '&': |
| Result << "&"; |
| break; |
| case '<': |
| Result << "<"; |
| break; |
| case '>': |
| Result << ">"; |
| break; |
| case '"': |
| Result << """; |
| break; |
| case '\'': |
| Result << "'"; |
| break; |
| default: |
| Result << C; |
| break; |
| } |
| } |
| } |
| |
| void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) { |
| if (S.empty()) |
| return; |
| |
| Result << "<![CDATA["; |
| while (!S.empty()) { |
| size_t Pos = S.find("]]>"); |
| if (Pos == 0) { |
| Result << "]]]]><![CDATA[>"; |
| S = S.drop_front(3); |
| continue; |
| } |
| if (Pos == StringRef::npos) |
| Pos = S.size(); |
| |
| Result << S.substr(0, Pos); |
| |
| S = S.drop_front(Pos); |
| } |
| Result << "]]>"; |
| } |
| |
| CommentToXMLConverter::CommentToXMLConverter() {} |
| CommentToXMLConverter::~CommentToXMLConverter() {} |
| |
| void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC, |
| SmallVectorImpl<char> &HTML, |
| const ASTContext &Context) { |
| CommentASTToHTMLConverter Converter(FC, HTML, |
| Context.getCommentCommandTraits()); |
| Converter.visit(FC); |
| } |
| |
| void CommentToXMLConverter::convertHTMLTagNodeToText( |
| const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text, |
| const ASTContext &Context) { |
| CommentASTToHTMLConverter Converter(nullptr, Text, |
| Context.getCommentCommandTraits()); |
| Converter.visit(HTC); |
| } |
| |
| void CommentToXMLConverter::convertCommentToXML(const FullComment *FC, |
| SmallVectorImpl<char> &XML, |
| const ASTContext &Context) { |
| CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), |
| Context.getSourceManager()); |
| Converter.visit(FC); |
| } |