You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1162 lines
35 KiB
1162 lines
35 KiB
//===--- CommentToXML.cpp - Convert comments to XML representation --------===// |
|
// |
|
// The LLVM Compiler Infrastructure |
|
// |
|
// This file is distributed under the University of Illinois Open Source |
|
// License. See LICENSE.TXT for details. |
|
// |
|
//===----------------------------------------------------------------------===// |
|
|
|
#include "clang/Index/CommentToXML.h" |
|
#include "SimpleFormatContext.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. |
|
std::stable_sort(Params.begin(), Params.end(), |
|
ParamCommandCommentCompareIndex()); |
|
|
|
std::stable_sort(TParams.begin(), TParams.end(), |
|
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, |
|
SimpleFormatContext &SFC, |
|
unsigned FUID) : |
|
FC(FC), Result(Str), Traits(Traits), SM(SM), |
|
FormatRewriterContext(SFC), |
|
FormatInMemoryUniqueId(FUID) { } |
|
|
|
// 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; |
|
SimpleFormatContext &FormatRewriterContext; |
|
unsigned FormatInMemoryUniqueId; |
|
}; |
|
|
|
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; |
|
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. |
|
// Form a unique in memory buffer name. |
|
SmallString<128> filename; |
|
filename += "xmldecl"; |
|
filename += llvm::utostr(FormatInMemoryUniqueId); |
|
filename += ".xd"; |
|
FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl); |
|
SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID) |
|
.getLocWithOffset(0); |
|
unsigned Length = Declaration.size(); |
|
|
|
tooling::Replacements Replace = reformat( |
|
format::getLLVMStyle(), FormatRewriterContext.Sources, ID, |
|
CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length))); |
|
applyAllReplacements(Replace, FormatRewriterContext.Rewrite); |
|
Declaration = FormatRewriterContext.getRewrittenText(ID); |
|
} |
|
|
|
} // 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); |
|
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() : FormatInMemoryUniqueId(0) {} |
|
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) { |
|
if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) { |
|
// Create a new format context, or re-create it after some number of |
|
// iterations, so the buffers don't grow too large. |
|
FormatContext.reset(new SimpleFormatContext(Context.getLangOpts())); |
|
} |
|
|
|
CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), |
|
Context.getSourceManager(), *FormatContext, |
|
FormatInMemoryUniqueId++); |
|
Converter.visit(FC); |
|
} |
|
|
|
|