//===- CXComment.cpp - libclang APIs for manipulating CXComments ----------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file defines all libclang APIs related to walking comment AST. // //===----------------------------------------------------------------------===// #include "clang-c/Index.h" #include "CXComment.h" #include "CXCursor.h" #include "CXString.h" #include "SimpleFormatContext.h" #include "clang/AST/CommentCommandTraits.h" #include "clang/AST/CommentVisitor.h" #include "clang/AST/Decl.h" #include "clang/AST/PrettyPrinter.h" #include "clang/Format/Format.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include using namespace clang; using namespace clang::comments; using namespace clang::cxcomment; extern "C" { enum CXCommentKind clang_Comment_getKind(CXComment CXC) { const Comment *C = getASTNode(CXC); if (!C) return CXComment_Null; switch (C->getCommentKind()) { case Comment::NoCommentKind: return CXComment_Null; case Comment::TextCommentKind: return CXComment_Text; case Comment::InlineCommandCommentKind: return CXComment_InlineCommand; case Comment::HTMLStartTagCommentKind: return CXComment_HTMLStartTag; case Comment::HTMLEndTagCommentKind: return CXComment_HTMLEndTag; case Comment::ParagraphCommentKind: return CXComment_Paragraph; case Comment::BlockCommandCommentKind: return CXComment_BlockCommand; case Comment::ParamCommandCommentKind: return CXComment_ParamCommand; case Comment::TParamCommandCommentKind: return CXComment_TParamCommand; case Comment::VerbatimBlockCommentKind: return CXComment_VerbatimBlockCommand; case Comment::VerbatimBlockLineCommentKind: return CXComment_VerbatimBlockLine; case Comment::VerbatimLineCommentKind: return CXComment_VerbatimLine; case Comment::FullCommentKind: return CXComment_FullComment; } llvm_unreachable("unknown CommentKind"); } unsigned clang_Comment_getNumChildren(CXComment CXC) { const Comment *C = getASTNode(CXC); if (!C) return 0; return C->child_count(); } CXComment clang_Comment_getChild(CXComment CXC, unsigned ChildIdx) { const Comment *C = getASTNode(CXC); if (!C || ChildIdx >= C->child_count()) return createCXComment(NULL, NULL); return createCXComment(*(C->child_begin() + ChildIdx), CXC.TranslationUnit); } unsigned clang_Comment_isWhitespace(CXComment CXC) { const Comment *C = getASTNode(CXC); if (!C) return false; if (const TextComment *TC = dyn_cast(C)) return TC->isWhitespace(); if (const ParagraphComment *PC = dyn_cast(C)) return PC->isWhitespace(); return false; } unsigned clang_InlineContentComment_hasTrailingNewline(CXComment CXC) { const InlineContentComment *ICC = getASTNodeAs(CXC); if (!ICC) return false; return ICC->hasTrailingNewline(); } CXString clang_TextComment_getText(CXComment CXC) { const TextComment *TC = getASTNodeAs(CXC); if (!TC) return cxstring::createNull(); return cxstring::createRef(TC->getText()); } CXString clang_InlineCommandComment_getCommandName(CXComment CXC) { const InlineCommandComment *ICC = getASTNodeAs(CXC); if (!ICC) return cxstring::createNull(); const CommandTraits &Traits = getCommandTraits(CXC); return cxstring::createRef(ICC->getCommandName(Traits)); } enum CXCommentInlineCommandRenderKind clang_InlineCommandComment_getRenderKind(CXComment CXC) { const InlineCommandComment *ICC = getASTNodeAs(CXC); if (!ICC) return CXCommentInlineCommandRenderKind_Normal; switch (ICC->getRenderKind()) { case InlineCommandComment::RenderNormal: return CXCommentInlineCommandRenderKind_Normal; case InlineCommandComment::RenderBold: return CXCommentInlineCommandRenderKind_Bold; case InlineCommandComment::RenderMonospaced: return CXCommentInlineCommandRenderKind_Monospaced; case InlineCommandComment::RenderEmphasized: return CXCommentInlineCommandRenderKind_Emphasized; } llvm_unreachable("unknown InlineCommandComment::RenderKind"); } unsigned clang_InlineCommandComment_getNumArgs(CXComment CXC) { const InlineCommandComment *ICC = getASTNodeAs(CXC); if (!ICC) return 0; return ICC->getNumArgs(); } CXString clang_InlineCommandComment_getArgText(CXComment CXC, unsigned ArgIdx) { const InlineCommandComment *ICC = getASTNodeAs(CXC); if (!ICC || ArgIdx >= ICC->getNumArgs()) return cxstring::createNull(); return cxstring::createRef(ICC->getArgText(ArgIdx)); } CXString clang_HTMLTagComment_getTagName(CXComment CXC) { const HTMLTagComment *HTC = getASTNodeAs(CXC); if (!HTC) return cxstring::createNull(); return cxstring::createRef(HTC->getTagName()); } unsigned clang_HTMLStartTagComment_isSelfClosing(CXComment CXC) { const HTMLStartTagComment *HST = getASTNodeAs(CXC); if (!HST) return false; return HST->isSelfClosing(); } unsigned clang_HTMLStartTag_getNumAttrs(CXComment CXC) { const HTMLStartTagComment *HST = getASTNodeAs(CXC); if (!HST) return 0; return HST->getNumAttrs(); } CXString clang_HTMLStartTag_getAttrName(CXComment CXC, unsigned AttrIdx) { const HTMLStartTagComment *HST = getASTNodeAs(CXC); if (!HST || AttrIdx >= HST->getNumAttrs()) return cxstring::createNull(); return cxstring::createRef(HST->getAttr(AttrIdx).Name); } CXString clang_HTMLStartTag_getAttrValue(CXComment CXC, unsigned AttrIdx) { const HTMLStartTagComment *HST = getASTNodeAs(CXC); if (!HST || AttrIdx >= HST->getNumAttrs()) return cxstring::createNull(); return cxstring::createRef(HST->getAttr(AttrIdx).Value); } CXString clang_BlockCommandComment_getCommandName(CXComment CXC) { const BlockCommandComment *BCC = getASTNodeAs(CXC); if (!BCC) return cxstring::createNull(); const CommandTraits &Traits = getCommandTraits(CXC); return cxstring::createRef(BCC->getCommandName(Traits)); } unsigned clang_BlockCommandComment_getNumArgs(CXComment CXC) { const BlockCommandComment *BCC = getASTNodeAs(CXC); if (!BCC) return 0; return BCC->getNumArgs(); } CXString clang_BlockCommandComment_getArgText(CXComment CXC, unsigned ArgIdx) { const BlockCommandComment *BCC = getASTNodeAs(CXC); if (!BCC || ArgIdx >= BCC->getNumArgs()) return cxstring::createNull(); return cxstring::createRef(BCC->getArgText(ArgIdx)); } CXComment clang_BlockCommandComment_getParagraph(CXComment CXC) { const BlockCommandComment *BCC = getASTNodeAs(CXC); if (!BCC) return createCXComment(NULL, NULL); return createCXComment(BCC->getParagraph(), CXC.TranslationUnit); } CXString clang_ParamCommandComment_getParamName(CXComment CXC) { const ParamCommandComment *PCC = getASTNodeAs(CXC); if (!PCC || !PCC->hasParamName()) return cxstring::createNull(); return cxstring::createRef(PCC->getParamNameAsWritten()); } unsigned clang_ParamCommandComment_isParamIndexValid(CXComment CXC) { const ParamCommandComment *PCC = getASTNodeAs(CXC); if (!PCC) return false; return PCC->isParamIndexValid(); } unsigned clang_ParamCommandComment_getParamIndex(CXComment CXC) { const ParamCommandComment *PCC = getASTNodeAs(CXC); if (!PCC || !PCC->isParamIndexValid()) return ParamCommandComment::InvalidParamIndex; return PCC->getParamIndex(); } unsigned clang_ParamCommandComment_isDirectionExplicit(CXComment CXC) { const ParamCommandComment *PCC = getASTNodeAs(CXC); if (!PCC) return false; return PCC->isDirectionExplicit(); } enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection( CXComment CXC) { const ParamCommandComment *PCC = getASTNodeAs(CXC); if (!PCC) return CXCommentParamPassDirection_In; switch (PCC->getDirection()) { case ParamCommandComment::In: return CXCommentParamPassDirection_In; case ParamCommandComment::Out: return CXCommentParamPassDirection_Out; case ParamCommandComment::InOut: return CXCommentParamPassDirection_InOut; } llvm_unreachable("unknown ParamCommandComment::PassDirection"); } CXString clang_TParamCommandComment_getParamName(CXComment CXC) { const TParamCommandComment *TPCC = getASTNodeAs(CXC); if (!TPCC || !TPCC->hasParamName()) return cxstring::createNull(); return cxstring::createRef(TPCC->getParamNameAsWritten()); } unsigned clang_TParamCommandComment_isParamPositionValid(CXComment CXC) { const TParamCommandComment *TPCC = getASTNodeAs(CXC); if (!TPCC) return false; return TPCC->isPositionValid(); } unsigned clang_TParamCommandComment_getDepth(CXComment CXC) { const TParamCommandComment *TPCC = getASTNodeAs(CXC); if (!TPCC || !TPCC->isPositionValid()) return 0; return TPCC->getDepth(); } unsigned clang_TParamCommandComment_getIndex(CXComment CXC, unsigned Depth) { const TParamCommandComment *TPCC = getASTNodeAs(CXC); if (!TPCC || !TPCC->isPositionValid() || Depth >= TPCC->getDepth()) return 0; return TPCC->getIndex(Depth); } CXString clang_VerbatimBlockLineComment_getText(CXComment CXC) { const VerbatimBlockLineComment *VBL = getASTNodeAs(CXC); if (!VBL) return cxstring::createNull(); return cxstring::createRef(VBL->getText()); } CXString clang_VerbatimLineComment_getText(CXComment CXC) { const VerbatimLineComment *VLC = getASTNodeAs(CXC); if (!VLC) return cxstring::createNull(); return cxstring::createRef(VLC->getText()); } } // end extern "C" //===----------------------------------------------------------------------===// // Helpers for converting comment AST to HTML. //===----------------------------------------------------------------------===// namespace { /// This comparison will sort parameters with valid index by index 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()) LHSIndex = LHS->getParamIndex(); if (RHS->isParamIndexValid()) 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; const BlockCommandComment *Returns; SmallVector Params; SmallVector TParams; SmallVector MiscBlocks; }; FullCommentParts::FullCommentParts(const FullComment *C, const CommandTraits &Traits) : Brief(NULL), Headerfile(NULL), FirstParagraph(NULL), Returns(NULL) { 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(Child); if (PC->isWhitespace()) break; if (!FirstParagraph) FirstParagraph = PC; MiscBlocks.push_back(PC); break; } case Comment::BlockCommandCommentKind: { const BlockCommandComment *BCC = cast(Child); const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID()); if (!Brief && Info->IsBriefCommand) { Brief = BCC; break; } if (!Headerfile && Info->IsHeaderfileCommand) { Headerfile = BCC; break; } if (!Returns && Info->IsReturnsCommand) { Returns = BCC; break; } MiscBlocks.push_back(BCC); break; } case Comment::ParamCommandCommentKind: { const ParamCommandComment *PCC = cast(Child); if (!PCC->hasParamName()) break; if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) break; Params.push_back(PCC); break; } case Comment::TParamCommandCommentKind: { const TParamCommandComment *TPCC = cast(Child); if (!TPCC->hasParamName()) break; if (!TPCC->hasNonWhitespaceParagraph()) break; TParams.push_back(TPCC); break; } case Comment::VerbatimBlockCommentKind: MiscBlocks.push_back(cast(Child)); break; case Comment::VerbatimLineCommentKind: { const VerbatimLineComment *VLC = cast(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 { public: /// \param Str accumulator for HTML. CommentASTToHTMLConverter(const FullComment *FC, SmallVectorImpl &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 << ""; appendToResultWithHTMLEscaping(Arg0); Result << ""; return; case InlineCommandComment::RenderMonospaced: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithHTMLEscaping(Arg0); Result<< ""; return; case InlineCommandComment::RenderEmphasized: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithHTMLEscaping(Arg0); Result << ""; return; } } void CommentASTToHTMLConverter::visitHTMLStartTagComment( const HTMLStartTagComment *C) { PrintHTMLStartTagComment(C, Result); } void CommentASTToHTMLConverter::visitHTMLEndTagComment( const HTMLEndTagComment *C) { Result << "getTagName() << ">"; } void CommentASTToHTMLConverter::visitParagraphComment( const ParagraphComment *C) { if (C->isWhitespace()) return; Result << "

"; for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { visit(*I); } Result << "

"; } void CommentASTToHTMLConverter::visitBlockCommandComment( const BlockCommandComment *C) { const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID()); if (Info->IsBriefCommand) { Result << "

"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "

"; return; } if (Info->IsReturnsCommand) { Result << "

" "Returns "; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "

"; 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()) { Result << "
getParamIndex() << "\">"; appendToResultWithHTMLEscaping(C->getParamName(FC)); } else { Result << "
"; appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); } Result << "
"; if (C->isParamIndexValid()) { Result << "
getParamIndex() << "\">"; } else Result << "
"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "
"; } void CommentASTToHTMLConverter::visitTParamCommandComment( const TParamCommandComment *C) { if (C->isPositionValid()) { if (C->getDepth() == 1) Result << "
getIndex(0) << "\">"; else Result << "
"; appendToResultWithHTMLEscaping(C->getParamName(FC)); } else { Result << "
"; appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); } Result << "
"; if (C->isPositionValid()) { if (C->getDepth() == 1) Result << "
getIndex(0) << "\">"; else Result << "
"; } else Result << "
"; visitNonStandaloneParagraphComment(C->getParagraph()); Result << "
"; } void CommentASTToHTMLConverter::visitVerbatimBlockComment( const VerbatimBlockComment *C) { unsigned NumLines = C->getNumLines(); if (NumLines == 0) return; Result << "
";
  for (unsigned i = 0; i != NumLines; ++i) {
    appendToResultWithHTMLEscaping(C->getText(i));
    if (i + 1 != NumLines)
      Result << '\n';
  }
  Result << "
"; } void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( const VerbatimBlockLineComment *C) { llvm_unreachable("should not see this AST node"); } void CommentASTToHTMLConverter::visitVerbatimLineComment( const VerbatimLineComment *C) { Result << "
";
  appendToResultWithHTMLEscaping(C->getText());
  Result << "
"; } 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 << "

"; visitNonStandaloneParagraphComment(Parts.FirstParagraph); Result << "

"; 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 << "
"; for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) visit(Parts.TParams[i]); Result << "
"; } if (Parts.Params.size() != 0) { Result << "
"; for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) visit(Parts.Params[i]); Result << "
"; } if (Parts.Returns) visit(Parts.Returns); Result.flush(); } 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; } } } extern "C" { CXString clang_HTMLTagComment_getAsString(CXComment CXC) { const HTMLTagComment *HTC = getASTNodeAs(CXC); if (!HTC) return cxstring::createNull(); SmallString<128> HTML; CommentASTToHTMLConverter Converter(0, HTML, getCommandTraits(CXC)); Converter.visit(HTC); return cxstring::createDup(HTML.str()); } CXString clang_FullComment_getAsHTML(CXComment CXC) { const FullComment *FC = getASTNodeAs(CXC); if (!FC) return cxstring::createNull(); SmallString<1024> HTML; CommentASTToHTMLConverter Converter(FC, HTML, getCommandTraits(CXC)); Converter.visit(FC); return cxstring::createDup(HTML.str()); } } // end extern "C" namespace { class CommentASTToXMLConverter : public ConstCommentVisitor { public: /// \param Str accumulator for XML. CommentASTToXMLConverter(const FullComment *FC, SmallVectorImpl &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 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 &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) { // FIXME. formatting API expects null terminated input string. // There might be more efficient way of doing this. std::string StringDecl = Declaration.str(); // 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(); std::vector Ranges(1, CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length))); ASTContext &Context = DI->CurrentDecl->getASTContext(); const LangOptions &LangOpts = Context.getLangOpts(); Lexer Lex(ID, FormatRewriterContext.Sources.getBuffer(ID), FormatRewriterContext.Sources, LangOpts); tooling::Replacements Replace = reformat(format::getLLVMStyle(), Lex, FormatRewriterContext.Sources, Ranges); 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 << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; case InlineCommandComment::RenderMonospaced: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; case InlineCommandComment::RenderEmphasized: assert(C->getNumArgs() == 1); Result << ""; appendToResultWithXMLEscaping(Arg0); Result << ""; return; } } void CommentASTToXMLConverter::visitHTMLStartTagComment(const HTMLStartTagComment *C) { Result << ""; } void CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { Result << "</" << C->getTagName() << ">"; } 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 << ""; else Result << ""; for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; ++I) { visit(*I); } Result << ""; } 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 << ""; appendToResultWithXMLEscaping(C->isParamIndexValid() ? C->getParamName(FC) : C->getParamNameAsWritten()); Result << ""; if (C->isParamIndexValid()) Result << "" << C->getParamIndex() << ""; Result << "isDirectionExplicit() << "\">"; switch (C->getDirection()) { case ParamCommandComment::In: Result << "in"; break; case ParamCommandComment::Out: Result << "out"; break; case ParamCommandComment::InOut: Result << "in,out"; break; } Result << ""; visit(C->getParagraph()); Result << ""; } void CommentASTToXMLConverter::visitTParamCommandComment( const TParamCommandComment *C) { Result << ""; appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC) : C->getParamNameAsWritten()); Result << ""; if (C->isPositionValid() && C->getDepth() == 1) { Result << "" << C->getIndex(0) << ""; } Result << ""; visit(C->getParagraph()); Result << ""; } void CommentASTToXMLConverter::visitVerbatimBlockComment( const VerbatimBlockComment *C) { unsigned NumLines = C->getNumLines(); if (NumLines == 0) return; switch (C->getCommandID()) { case CommandTraits::KCI_code: Result << ""; break; default: Result << ""; break; } for (unsigned i = 0; i != NumLines; ++i) { appendToResultWithXMLEscaping(C->getText(i)); if (i + 1 != NumLines) Result << '\n'; } Result << ""; } void CommentASTToXMLConverter::visitVerbatimBlockLineComment( const VerbatimBlockLineComment *C) { llvm_unreachable("should not see this AST node"); } void CommentASTToXMLConverter::visitVerbatimLineComment( const VerbatimLineComment *C) { Result << ""; appendToResultWithXMLEscaping(C->getText()); Result << ""; } 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 = ""; Result << "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 = ""; Result << "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 = ""; Result << "CurrentDecl->getLocation(); std::pair LocInfo = SM.getDecomposedLoc(Loc); FileID FID = LocInfo.first; unsigned FileOffset = LocInfo.second; if (!FID.isInvalid()) { 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(DI->CommentDecl)) { if (DeclarationName DeclName = ND->getDeclName()) { Result << ""; std::string Name = DeclName.getAsString(); appendToResultWithXMLEscaping(Name); FoundName = true; Result << ""; } } if (!FoundName) Result << "<anonymous>"; { // Print USR. SmallString<128> USR; cxcursor::getDeclCursorUSR(DI->CommentDecl, USR); if (!USR.empty()) { Result << ""; appendToResultWithXMLEscaping(USR); Result << ""; } } } else { // No DeclInfo -- just emit some root tag and name tag. RootEndTag = ""; Result << "unknown"; } if (Parts.Headerfile) { Result << ""; visit(Parts.Headerfile); Result << ""; } { // Pretty-print the declaration. Result << ""; SmallString<128> Declaration; getSourceTextOfDeclaration(DI, Declaration); formatTextOfDeclaration(DI, Declaration); appendToResultWithXMLEscaping(Declaration); Result << ""; } bool FirstParagraphIsBrief = false; if (Parts.Brief) { Result << ""; visit(Parts.Brief); Result << ""; } else if (Parts.FirstParagraph) { Result << ""; visit(Parts.FirstParagraph); Result << ""; FirstParagraphIsBrief = true; } if (Parts.TParams.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) visit(Parts.TParams[i]); Result << ""; } if (Parts.Params.size() != 0) { Result << ""; for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) visit(Parts.Params[i]); Result << ""; } if (Parts.Returns) { Result << ""; visit(Parts.Returns); Result << ""; } 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(Attrs[i]); if (!AA) { if (const DeprecatedAttr *DA = dyn_cast(Attrs[i])) { if (DA->getMessage().empty()) Result << ""; else { Result << ""; appendToResultWithXMLEscaping(DA->getMessage()); Result << ""; } } else if (const UnavailableAttr *UA = dyn_cast(Attrs[i])) { if (UA->getMessage().empty()) Result << ""; else { Result << ""; appendToResultWithXMLEscaping(UA->getMessage()); Result << ""; } } continue; } // 'availability' attribute. Result << "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.getAsString() << ""; } VersionTuple DeprecatedInVersion = AA->getDeprecated(); if (!DeprecatedInVersion.empty()) { Result << "" << DeprecatedInVersion.getAsString() << ""; } VersionTuple RemovedAfterVersion = AA->getObsoleted(); if (!RemovedAfterVersion.empty()) { Result << "" << RemovedAfterVersion.getAsString() << ""; } StringRef DeprecationSummary = AA->getMessage(); if (!DeprecationSummary.empty()) { Result << ""; appendToResultWithXMLEscaping(DeprecationSummary); Result << ""; } if (AA->getUnavailable()) Result << ""; Result << ""; } } { 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 << ""; StartTagEmitted = true; } visit(C); } if (StartTagEmitted) Result << ""; } Result << RootEndTag; Result.flush(); } 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; } } } extern "C" { CXString clang_FullComment_getAsXML(CXComment CXC) { const FullComment *FC = getASTNodeAs(CXC); if (!FC) return cxstring::createNull(); ASTContext &Context = FC->getDeclInfo()->CurrentDecl->getASTContext(); CXTranslationUnit TU = CXC.TranslationUnit; SourceManager &SM = cxtu::getASTUnit(TU)->getSourceManager(); if (!TU->FormatContext) { TU->FormatContext = new SimpleFormatContext(Context.getLangOpts()); } else if ((TU->FormatInMemoryUniqueId % 1000) == 0) { // Delete after some number of iterators, so the buffers don't grow // too large. delete TU->FormatContext; TU->FormatContext = new SimpleFormatContext(Context.getLangOpts()); } SmallString<1024> XML; CommentASTToXMLConverter Converter(FC, XML, getCommandTraits(CXC), SM, *TU->FormatContext, TU->FormatInMemoryUniqueId++); Converter.visit(FC); return cxstring::createDup(XML.str()); } } // end extern "C"