CommentToXML.cpp revision 276479
1//===--- CommentToXML.cpp - Convert comments to XML representation --------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9 10#include "clang/Index/CommentToXML.h" 11#include "SimpleFormatContext.h" 12#include "clang/AST/ASTContext.h" 13#include "clang/AST/Attr.h" 14#include "clang/AST/Comment.h" 15#include "clang/AST/CommentVisitor.h" 16#include "clang/Format/Format.h" 17#include "clang/Index/USRGeneration.h" 18#include "clang/Lex/Lexer.h" 19#include "llvm/ADT/StringExtras.h" 20#include "llvm/ADT/TinyPtrVector.h" 21#include "llvm/Support/raw_ostream.h" 22 23using namespace clang; 24using namespace clang::comments; 25using namespace clang::index; 26 27namespace { 28 29/// This comparison will sort parameters with valid index by index, then vararg 30/// parameters, and invalid (unresolved) parameters last. 31class ParamCommandCommentCompareIndex { 32public: 33 bool operator()(const ParamCommandComment *LHS, 34 const ParamCommandComment *RHS) const { 35 unsigned LHSIndex = UINT_MAX; 36 unsigned RHSIndex = UINT_MAX; 37 38 if (LHS->isParamIndexValid()) { 39 if (LHS->isVarArgParam()) 40 LHSIndex = UINT_MAX - 1; 41 else 42 LHSIndex = LHS->getParamIndex(); 43 } 44 if (RHS->isParamIndexValid()) { 45 if (RHS->isVarArgParam()) 46 RHSIndex = UINT_MAX - 1; 47 else 48 RHSIndex = RHS->getParamIndex(); 49 } 50 return LHSIndex < RHSIndex; 51 } 52}; 53 54/// This comparison will sort template parameters in the following order: 55/// \li real template parameters (depth = 1) in index order; 56/// \li all other names (depth > 1); 57/// \li unresolved names. 58class TParamCommandCommentComparePosition { 59public: 60 bool operator()(const TParamCommandComment *LHS, 61 const TParamCommandComment *RHS) const { 62 // Sort unresolved names last. 63 if (!LHS->isPositionValid()) 64 return false; 65 if (!RHS->isPositionValid()) 66 return true; 67 68 if (LHS->getDepth() > 1) 69 return false; 70 if (RHS->getDepth() > 1) 71 return true; 72 73 // Sort template parameters in index order. 74 if (LHS->getDepth() == 1 && RHS->getDepth() == 1) 75 return LHS->getIndex(0) < RHS->getIndex(0); 76 77 // Leave all other names in source order. 78 return true; 79 } 80}; 81 82/// Separate parts of a FullComment. 83struct FullCommentParts { 84 /// Take a full comment apart and initialize members accordingly. 85 FullCommentParts(const FullComment *C, 86 const CommandTraits &Traits); 87 88 const BlockContentComment *Brief; 89 const BlockContentComment *Headerfile; 90 const ParagraphComment *FirstParagraph; 91 SmallVector<const BlockCommandComment *, 4> Returns; 92 SmallVector<const ParamCommandComment *, 8> Params; 93 SmallVector<const TParamCommandComment *, 4> TParams; 94 llvm::TinyPtrVector<const BlockCommandComment *> Exceptions; 95 SmallVector<const BlockContentComment *, 8> MiscBlocks; 96}; 97 98FullCommentParts::FullCommentParts(const FullComment *C, 99 const CommandTraits &Traits) : 100 Brief(nullptr), Headerfile(nullptr), FirstParagraph(nullptr) { 101 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 102 I != E; ++I) { 103 const Comment *Child = *I; 104 if (!Child) 105 continue; 106 switch (Child->getCommentKind()) { 107 case Comment::NoCommentKind: 108 continue; 109 110 case Comment::ParagraphCommentKind: { 111 const ParagraphComment *PC = cast<ParagraphComment>(Child); 112 if (PC->isWhitespace()) 113 break; 114 if (!FirstParagraph) 115 FirstParagraph = PC; 116 117 MiscBlocks.push_back(PC); 118 break; 119 } 120 121 case Comment::BlockCommandCommentKind: { 122 const BlockCommandComment *BCC = cast<BlockCommandComment>(Child); 123 const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID()); 124 if (!Brief && Info->IsBriefCommand) { 125 Brief = BCC; 126 break; 127 } 128 if (!Headerfile && Info->IsHeaderfileCommand) { 129 Headerfile = BCC; 130 break; 131 } 132 if (Info->IsReturnsCommand) { 133 Returns.push_back(BCC); 134 break; 135 } 136 if (Info->IsThrowsCommand) { 137 Exceptions.push_back(BCC); 138 break; 139 } 140 MiscBlocks.push_back(BCC); 141 break; 142 } 143 144 case Comment::ParamCommandCommentKind: { 145 const ParamCommandComment *PCC = cast<ParamCommandComment>(Child); 146 if (!PCC->hasParamName()) 147 break; 148 149 if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) 150 break; 151 152 Params.push_back(PCC); 153 break; 154 } 155 156 case Comment::TParamCommandCommentKind: { 157 const TParamCommandComment *TPCC = cast<TParamCommandComment>(Child); 158 if (!TPCC->hasParamName()) 159 break; 160 161 if (!TPCC->hasNonWhitespaceParagraph()) 162 break; 163 164 TParams.push_back(TPCC); 165 break; 166 } 167 168 case Comment::VerbatimBlockCommentKind: 169 MiscBlocks.push_back(cast<BlockCommandComment>(Child)); 170 break; 171 172 case Comment::VerbatimLineCommentKind: { 173 const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Child); 174 const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID()); 175 if (!Info->IsDeclarationCommand) 176 MiscBlocks.push_back(VLC); 177 break; 178 } 179 180 case Comment::TextCommentKind: 181 case Comment::InlineCommandCommentKind: 182 case Comment::HTMLStartTagCommentKind: 183 case Comment::HTMLEndTagCommentKind: 184 case Comment::VerbatimBlockLineCommentKind: 185 case Comment::FullCommentKind: 186 llvm_unreachable("AST node of this kind can't be a child of " 187 "a FullComment"); 188 } 189 } 190 191 // Sort params in order they are declared in the function prototype. 192 // Unresolved parameters are put at the end of the list in the same order 193 // they were seen in the comment. 194 std::stable_sort(Params.begin(), Params.end(), 195 ParamCommandCommentCompareIndex()); 196 197 std::stable_sort(TParams.begin(), TParams.end(), 198 TParamCommandCommentComparePosition()); 199} 200 201void printHTMLStartTagComment(const HTMLStartTagComment *C, 202 llvm::raw_svector_ostream &Result) { 203 Result << "<" << C->getTagName(); 204 205 if (C->getNumAttrs() != 0) { 206 for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) { 207 Result << " "; 208 const HTMLStartTagComment::Attribute &Attr = C->getAttr(i); 209 Result << Attr.Name; 210 if (!Attr.Value.empty()) 211 Result << "=\"" << Attr.Value << "\""; 212 } 213 } 214 215 if (!C->isSelfClosing()) 216 Result << ">"; 217 else 218 Result << "/>"; 219} 220 221class CommentASTToHTMLConverter : 222 public ConstCommentVisitor<CommentASTToHTMLConverter> { 223public: 224 /// \param Str accumulator for HTML. 225 CommentASTToHTMLConverter(const FullComment *FC, 226 SmallVectorImpl<char> &Str, 227 const CommandTraits &Traits) : 228 FC(FC), Result(Str), Traits(Traits) 229 { } 230 231 // Inline content. 232 void visitTextComment(const TextComment *C); 233 void visitInlineCommandComment(const InlineCommandComment *C); 234 void visitHTMLStartTagComment(const HTMLStartTagComment *C); 235 void visitHTMLEndTagComment(const HTMLEndTagComment *C); 236 237 // Block content. 238 void visitParagraphComment(const ParagraphComment *C); 239 void visitBlockCommandComment(const BlockCommandComment *C); 240 void visitParamCommandComment(const ParamCommandComment *C); 241 void visitTParamCommandComment(const TParamCommandComment *C); 242 void visitVerbatimBlockComment(const VerbatimBlockComment *C); 243 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); 244 void visitVerbatimLineComment(const VerbatimLineComment *C); 245 246 void visitFullComment(const FullComment *C); 247 248 // Helpers. 249 250 /// Convert a paragraph that is not a block by itself (an argument to some 251 /// command). 252 void visitNonStandaloneParagraphComment(const ParagraphComment *C); 253 254 void appendToResultWithHTMLEscaping(StringRef S); 255 256private: 257 const FullComment *FC; 258 /// Output stream for HTML. 259 llvm::raw_svector_ostream Result; 260 261 const CommandTraits &Traits; 262}; 263} // end unnamed namespace 264 265void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) { 266 appendToResultWithHTMLEscaping(C->getText()); 267} 268 269void CommentASTToHTMLConverter::visitInlineCommandComment( 270 const InlineCommandComment *C) { 271 // Nothing to render if no arguments supplied. 272 if (C->getNumArgs() == 0) 273 return; 274 275 // Nothing to render if argument is empty. 276 StringRef Arg0 = C->getArgText(0); 277 if (Arg0.empty()) 278 return; 279 280 switch (C->getRenderKind()) { 281 case InlineCommandComment::RenderNormal: 282 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { 283 appendToResultWithHTMLEscaping(C->getArgText(i)); 284 Result << " "; 285 } 286 return; 287 288 case InlineCommandComment::RenderBold: 289 assert(C->getNumArgs() == 1); 290 Result << "<b>"; 291 appendToResultWithHTMLEscaping(Arg0); 292 Result << "</b>"; 293 return; 294 case InlineCommandComment::RenderMonospaced: 295 assert(C->getNumArgs() == 1); 296 Result << "<tt>"; 297 appendToResultWithHTMLEscaping(Arg0); 298 Result<< "</tt>"; 299 return; 300 case InlineCommandComment::RenderEmphasized: 301 assert(C->getNumArgs() == 1); 302 Result << "<em>"; 303 appendToResultWithHTMLEscaping(Arg0); 304 Result << "</em>"; 305 return; 306 } 307} 308 309void CommentASTToHTMLConverter::visitHTMLStartTagComment( 310 const HTMLStartTagComment *C) { 311 printHTMLStartTagComment(C, Result); 312} 313 314void CommentASTToHTMLConverter::visitHTMLEndTagComment( 315 const HTMLEndTagComment *C) { 316 Result << "</" << C->getTagName() << ">"; 317} 318 319void CommentASTToHTMLConverter::visitParagraphComment( 320 const ParagraphComment *C) { 321 if (C->isWhitespace()) 322 return; 323 324 Result << "<p>"; 325 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 326 I != E; ++I) { 327 visit(*I); 328 } 329 Result << "</p>"; 330} 331 332void CommentASTToHTMLConverter::visitBlockCommandComment( 333 const BlockCommandComment *C) { 334 const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID()); 335 if (Info->IsBriefCommand) { 336 Result << "<p class=\"para-brief\">"; 337 visitNonStandaloneParagraphComment(C->getParagraph()); 338 Result << "</p>"; 339 return; 340 } 341 if (Info->IsReturnsCommand) { 342 Result << "<p class=\"para-returns\">" 343 "<span class=\"word-returns\">Returns</span> "; 344 visitNonStandaloneParagraphComment(C->getParagraph()); 345 Result << "</p>"; 346 return; 347 } 348 // We don't know anything about this command. Just render the paragraph. 349 visit(C->getParagraph()); 350} 351 352void CommentASTToHTMLConverter::visitParamCommandComment( 353 const ParamCommandComment *C) { 354 if (C->isParamIndexValid()) { 355 if (C->isVarArgParam()) { 356 Result << "<dt class=\"param-name-index-vararg\">"; 357 appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); 358 } else { 359 Result << "<dt class=\"param-name-index-" 360 << C->getParamIndex() 361 << "\">"; 362 appendToResultWithHTMLEscaping(C->getParamName(FC)); 363 } 364 } else { 365 Result << "<dt class=\"param-name-index-invalid\">"; 366 appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); 367 } 368 Result << "</dt>"; 369 370 if (C->isParamIndexValid()) { 371 if (C->isVarArgParam()) 372 Result << "<dd class=\"param-descr-index-vararg\">"; 373 else 374 Result << "<dd class=\"param-descr-index-" 375 << C->getParamIndex() 376 << "\">"; 377 } else 378 Result << "<dd class=\"param-descr-index-invalid\">"; 379 380 visitNonStandaloneParagraphComment(C->getParagraph()); 381 Result << "</dd>"; 382} 383 384void CommentASTToHTMLConverter::visitTParamCommandComment( 385 const TParamCommandComment *C) { 386 if (C->isPositionValid()) { 387 if (C->getDepth() == 1) 388 Result << "<dt class=\"tparam-name-index-" 389 << C->getIndex(0) 390 << "\">"; 391 else 392 Result << "<dt class=\"tparam-name-index-other\">"; 393 appendToResultWithHTMLEscaping(C->getParamName(FC)); 394 } else { 395 Result << "<dt class=\"tparam-name-index-invalid\">"; 396 appendToResultWithHTMLEscaping(C->getParamNameAsWritten()); 397 } 398 399 Result << "</dt>"; 400 401 if (C->isPositionValid()) { 402 if (C->getDepth() == 1) 403 Result << "<dd class=\"tparam-descr-index-" 404 << C->getIndex(0) 405 << "\">"; 406 else 407 Result << "<dd class=\"tparam-descr-index-other\">"; 408 } else 409 Result << "<dd class=\"tparam-descr-index-invalid\">"; 410 411 visitNonStandaloneParagraphComment(C->getParagraph()); 412 Result << "</dd>"; 413} 414 415void CommentASTToHTMLConverter::visitVerbatimBlockComment( 416 const VerbatimBlockComment *C) { 417 unsigned NumLines = C->getNumLines(); 418 if (NumLines == 0) 419 return; 420 421 Result << "<pre>"; 422 for (unsigned i = 0; i != NumLines; ++i) { 423 appendToResultWithHTMLEscaping(C->getText(i)); 424 if (i + 1 != NumLines) 425 Result << '\n'; 426 } 427 Result << "</pre>"; 428} 429 430void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( 431 const VerbatimBlockLineComment *C) { 432 llvm_unreachable("should not see this AST node"); 433} 434 435void CommentASTToHTMLConverter::visitVerbatimLineComment( 436 const VerbatimLineComment *C) { 437 Result << "<pre>"; 438 appendToResultWithHTMLEscaping(C->getText()); 439 Result << "</pre>"; 440} 441 442void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { 443 FullCommentParts Parts(C, Traits); 444 445 bool FirstParagraphIsBrief = false; 446 if (Parts.Headerfile) 447 visit(Parts.Headerfile); 448 if (Parts.Brief) 449 visit(Parts.Brief); 450 else if (Parts.FirstParagraph) { 451 Result << "<p class=\"para-brief\">"; 452 visitNonStandaloneParagraphComment(Parts.FirstParagraph); 453 Result << "</p>"; 454 FirstParagraphIsBrief = true; 455 } 456 457 for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { 458 const Comment *C = Parts.MiscBlocks[i]; 459 if (FirstParagraphIsBrief && C == Parts.FirstParagraph) 460 continue; 461 visit(C); 462 } 463 464 if (Parts.TParams.size() != 0) { 465 Result << "<dl>"; 466 for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) 467 visit(Parts.TParams[i]); 468 Result << "</dl>"; 469 } 470 471 if (Parts.Params.size() != 0) { 472 Result << "<dl>"; 473 for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) 474 visit(Parts.Params[i]); 475 Result << "</dl>"; 476 } 477 478 if (Parts.Returns.size() != 0) { 479 Result << "<div class=\"result-discussion\">"; 480 for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) 481 visit(Parts.Returns[i]); 482 Result << "</div>"; 483 } 484 485 Result.flush(); 486} 487 488void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment( 489 const ParagraphComment *C) { 490 if (!C) 491 return; 492 493 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 494 I != E; ++I) { 495 visit(*I); 496 } 497} 498 499void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) { 500 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { 501 const char C = *I; 502 switch (C) { 503 case '&': 504 Result << "&"; 505 break; 506 case '<': 507 Result << "<"; 508 break; 509 case '>': 510 Result << ">"; 511 break; 512 case '"': 513 Result << """; 514 break; 515 case '\'': 516 Result << "'"; 517 break; 518 case '/': 519 Result << "/"; 520 break; 521 default: 522 Result << C; 523 break; 524 } 525 } 526} 527 528namespace { 529class CommentASTToXMLConverter : 530 public ConstCommentVisitor<CommentASTToXMLConverter> { 531public: 532 /// \param Str accumulator for XML. 533 CommentASTToXMLConverter(const FullComment *FC, 534 SmallVectorImpl<char> &Str, 535 const CommandTraits &Traits, 536 const SourceManager &SM, 537 SimpleFormatContext &SFC, 538 unsigned FUID) : 539 FC(FC), Result(Str), Traits(Traits), SM(SM), 540 FormatRewriterContext(SFC), 541 FormatInMemoryUniqueId(FUID) { } 542 543 // Inline content. 544 void visitTextComment(const TextComment *C); 545 void visitInlineCommandComment(const InlineCommandComment *C); 546 void visitHTMLStartTagComment(const HTMLStartTagComment *C); 547 void visitHTMLEndTagComment(const HTMLEndTagComment *C); 548 549 // Block content. 550 void visitParagraphComment(const ParagraphComment *C); 551 552 void appendParagraphCommentWithKind(const ParagraphComment *C, 553 StringRef Kind); 554 555 void visitBlockCommandComment(const BlockCommandComment *C); 556 void visitParamCommandComment(const ParamCommandComment *C); 557 void visitTParamCommandComment(const TParamCommandComment *C); 558 void visitVerbatimBlockComment(const VerbatimBlockComment *C); 559 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); 560 void visitVerbatimLineComment(const VerbatimLineComment *C); 561 562 void visitFullComment(const FullComment *C); 563 564 // Helpers. 565 void appendToResultWithXMLEscaping(StringRef S); 566 void appendToResultWithCDATAEscaping(StringRef S); 567 568 void formatTextOfDeclaration(const DeclInfo *DI, 569 SmallString<128> &Declaration); 570 571private: 572 const FullComment *FC; 573 574 /// Output stream for XML. 575 llvm::raw_svector_ostream Result; 576 577 const CommandTraits &Traits; 578 const SourceManager &SM; 579 SimpleFormatContext &FormatRewriterContext; 580 unsigned FormatInMemoryUniqueId; 581}; 582 583void getSourceTextOfDeclaration(const DeclInfo *ThisDecl, 584 SmallVectorImpl<char> &Str) { 585 ASTContext &Context = ThisDecl->CurrentDecl->getASTContext(); 586 const LangOptions &LangOpts = Context.getLangOpts(); 587 llvm::raw_svector_ostream OS(Str); 588 PrintingPolicy PPolicy(LangOpts); 589 PPolicy.PolishForDeclaration = true; 590 PPolicy.TerseOutput = true; 591 ThisDecl->CurrentDecl->print(OS, PPolicy, 592 /*Indentation*/0, /*PrintInstantiation*/false); 593} 594 595void CommentASTToXMLConverter::formatTextOfDeclaration( 596 const DeclInfo *DI, SmallString<128> &Declaration) { 597 // FIXME. formatting API expects null terminated input string. 598 // There might be more efficient way of doing this. 599 std::string StringDecl = Declaration.str(); 600 601 // Formatter specific code. 602 // Form a unique in memory buffer name. 603 SmallString<128> filename; 604 filename += "xmldecl"; 605 filename += llvm::utostr(FormatInMemoryUniqueId); 606 filename += ".xd"; 607 FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl); 608 SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID) 609 .getLocWithOffset(0); 610 unsigned Length = Declaration.size(); 611 612 std::vector<CharSourceRange> Ranges( 613 1, CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length))); 614 ASTContext &Context = DI->CurrentDecl->getASTContext(); 615 const LangOptions &LangOpts = Context.getLangOpts(); 616 Lexer Lex(ID, FormatRewriterContext.Sources.getBuffer(ID), 617 FormatRewriterContext.Sources, LangOpts); 618 tooling::Replacements Replace = reformat( 619 format::getLLVMStyle(), Lex, FormatRewriterContext.Sources, Ranges); 620 applyAllReplacements(Replace, FormatRewriterContext.Rewrite); 621 Declaration = FormatRewriterContext.getRewrittenText(ID); 622} 623 624} // end unnamed namespace 625 626void CommentASTToXMLConverter::visitTextComment(const TextComment *C) { 627 appendToResultWithXMLEscaping(C->getText()); 628} 629 630void CommentASTToXMLConverter::visitInlineCommandComment( 631 const InlineCommandComment *C) { 632 // Nothing to render if no arguments supplied. 633 if (C->getNumArgs() == 0) 634 return; 635 636 // Nothing to render if argument is empty. 637 StringRef Arg0 = C->getArgText(0); 638 if (Arg0.empty()) 639 return; 640 641 switch (C->getRenderKind()) { 642 case InlineCommandComment::RenderNormal: 643 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { 644 appendToResultWithXMLEscaping(C->getArgText(i)); 645 Result << " "; 646 } 647 return; 648 case InlineCommandComment::RenderBold: 649 assert(C->getNumArgs() == 1); 650 Result << "<bold>"; 651 appendToResultWithXMLEscaping(Arg0); 652 Result << "</bold>"; 653 return; 654 case InlineCommandComment::RenderMonospaced: 655 assert(C->getNumArgs() == 1); 656 Result << "<monospaced>"; 657 appendToResultWithXMLEscaping(Arg0); 658 Result << "</monospaced>"; 659 return; 660 case InlineCommandComment::RenderEmphasized: 661 assert(C->getNumArgs() == 1); 662 Result << "<emphasized>"; 663 appendToResultWithXMLEscaping(Arg0); 664 Result << "</emphasized>"; 665 return; 666 } 667} 668 669void CommentASTToXMLConverter::visitHTMLStartTagComment( 670 const HTMLStartTagComment *C) { 671 Result << "<rawHTML"; 672 if (C->isMalformed()) 673 Result << " isMalformed=\"1\""; 674 Result << ">"; 675 { 676 SmallString<32> Tag; 677 { 678 llvm::raw_svector_ostream TagOS(Tag); 679 printHTMLStartTagComment(C, TagOS); 680 } 681 appendToResultWithCDATAEscaping(Tag); 682 } 683 Result << "</rawHTML>"; 684} 685 686void 687CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { 688 Result << "<rawHTML"; 689 if (C->isMalformed()) 690 Result << " isMalformed=\"1\""; 691 Result << "></" << C->getTagName() << "></rawHTML>"; 692} 693 694void 695CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) { 696 appendParagraphCommentWithKind(C, StringRef()); 697} 698 699void CommentASTToXMLConverter::appendParagraphCommentWithKind( 700 const ParagraphComment *C, 701 StringRef ParagraphKind) { 702 if (C->isWhitespace()) 703 return; 704 705 if (ParagraphKind.empty()) 706 Result << "<Para>"; 707 else 708 Result << "<Para kind=\"" << ParagraphKind << "\">"; 709 710 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 711 I != E; ++I) { 712 visit(*I); 713 } 714 Result << "</Para>"; 715} 716 717void CommentASTToXMLConverter::visitBlockCommandComment( 718 const BlockCommandComment *C) { 719 StringRef ParagraphKind; 720 721 switch (C->getCommandID()) { 722 case CommandTraits::KCI_attention: 723 case CommandTraits::KCI_author: 724 case CommandTraits::KCI_authors: 725 case CommandTraits::KCI_bug: 726 case CommandTraits::KCI_copyright: 727 case CommandTraits::KCI_date: 728 case CommandTraits::KCI_invariant: 729 case CommandTraits::KCI_note: 730 case CommandTraits::KCI_post: 731 case CommandTraits::KCI_pre: 732 case CommandTraits::KCI_remark: 733 case CommandTraits::KCI_remarks: 734 case CommandTraits::KCI_sa: 735 case CommandTraits::KCI_see: 736 case CommandTraits::KCI_since: 737 case CommandTraits::KCI_todo: 738 case CommandTraits::KCI_version: 739 case CommandTraits::KCI_warning: 740 ParagraphKind = C->getCommandName(Traits); 741 default: 742 break; 743 } 744 745 appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind); 746} 747 748void CommentASTToXMLConverter::visitParamCommandComment( 749 const ParamCommandComment *C) { 750 Result << "<Parameter><Name>"; 751 appendToResultWithXMLEscaping(C->isParamIndexValid() 752 ? C->getParamName(FC) 753 : C->getParamNameAsWritten()); 754 Result << "</Name>"; 755 756 if (C->isParamIndexValid()) { 757 if (C->isVarArgParam()) 758 Result << "<IsVarArg />"; 759 else 760 Result << "<Index>" << C->getParamIndex() << "</Index>"; 761 } 762 763 Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">"; 764 switch (C->getDirection()) { 765 case ParamCommandComment::In: 766 Result << "in"; 767 break; 768 case ParamCommandComment::Out: 769 Result << "out"; 770 break; 771 case ParamCommandComment::InOut: 772 Result << "in,out"; 773 break; 774 } 775 Result << "</Direction><Discussion>"; 776 visit(C->getParagraph()); 777 Result << "</Discussion></Parameter>"; 778} 779 780void CommentASTToXMLConverter::visitTParamCommandComment( 781 const TParamCommandComment *C) { 782 Result << "<Parameter><Name>"; 783 appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC) 784 : C->getParamNameAsWritten()); 785 Result << "</Name>"; 786 787 if (C->isPositionValid() && C->getDepth() == 1) { 788 Result << "<Index>" << C->getIndex(0) << "</Index>"; 789 } 790 791 Result << "<Discussion>"; 792 visit(C->getParagraph()); 793 Result << "</Discussion></Parameter>"; 794} 795 796void CommentASTToXMLConverter::visitVerbatimBlockComment( 797 const VerbatimBlockComment *C) { 798 unsigned NumLines = C->getNumLines(); 799 if (NumLines == 0) 800 return; 801 802 switch (C->getCommandID()) { 803 case CommandTraits::KCI_code: 804 Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">"; 805 break; 806 default: 807 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; 808 break; 809 } 810 for (unsigned i = 0; i != NumLines; ++i) { 811 appendToResultWithXMLEscaping(C->getText(i)); 812 if (i + 1 != NumLines) 813 Result << '\n'; 814 } 815 Result << "</Verbatim>"; 816} 817 818void CommentASTToXMLConverter::visitVerbatimBlockLineComment( 819 const VerbatimBlockLineComment *C) { 820 llvm_unreachable("should not see this AST node"); 821} 822 823void CommentASTToXMLConverter::visitVerbatimLineComment( 824 const VerbatimLineComment *C) { 825 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; 826 appendToResultWithXMLEscaping(C->getText()); 827 Result << "</Verbatim>"; 828} 829 830void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { 831 FullCommentParts Parts(C, Traits); 832 833 const DeclInfo *DI = C->getDeclInfo(); 834 StringRef RootEndTag; 835 if (DI) { 836 switch (DI->getKind()) { 837 case DeclInfo::OtherKind: 838 RootEndTag = "</Other>"; 839 Result << "<Other"; 840 break; 841 case DeclInfo::FunctionKind: 842 RootEndTag = "</Function>"; 843 Result << "<Function"; 844 switch (DI->TemplateKind) { 845 case DeclInfo::NotTemplate: 846 break; 847 case DeclInfo::Template: 848 Result << " templateKind=\"template\""; 849 break; 850 case DeclInfo::TemplateSpecialization: 851 Result << " templateKind=\"specialization\""; 852 break; 853 case DeclInfo::TemplatePartialSpecialization: 854 llvm_unreachable("partial specializations of functions " 855 "are not allowed in C++"); 856 } 857 if (DI->IsInstanceMethod) 858 Result << " isInstanceMethod=\"1\""; 859 if (DI->IsClassMethod) 860 Result << " isClassMethod=\"1\""; 861 break; 862 case DeclInfo::ClassKind: 863 RootEndTag = "</Class>"; 864 Result << "<Class"; 865 switch (DI->TemplateKind) { 866 case DeclInfo::NotTemplate: 867 break; 868 case DeclInfo::Template: 869 Result << " templateKind=\"template\""; 870 break; 871 case DeclInfo::TemplateSpecialization: 872 Result << " templateKind=\"specialization\""; 873 break; 874 case DeclInfo::TemplatePartialSpecialization: 875 Result << " templateKind=\"partialSpecialization\""; 876 break; 877 } 878 break; 879 case DeclInfo::VariableKind: 880 RootEndTag = "</Variable>"; 881 Result << "<Variable"; 882 break; 883 case DeclInfo::NamespaceKind: 884 RootEndTag = "</Namespace>"; 885 Result << "<Namespace"; 886 break; 887 case DeclInfo::TypedefKind: 888 RootEndTag = "</Typedef>"; 889 Result << "<Typedef"; 890 break; 891 case DeclInfo::EnumKind: 892 RootEndTag = "</Enum>"; 893 Result << "<Enum"; 894 break; 895 } 896 897 { 898 // Print line and column number. 899 SourceLocation Loc = DI->CurrentDecl->getLocation(); 900 std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); 901 FileID FID = LocInfo.first; 902 unsigned FileOffset = LocInfo.second; 903 904 if (!FID.isInvalid()) { 905 if (const FileEntry *FE = SM.getFileEntryForID(FID)) { 906 Result << " file=\""; 907 appendToResultWithXMLEscaping(FE->getName()); 908 Result << "\""; 909 } 910 Result << " line=\"" << SM.getLineNumber(FID, FileOffset) 911 << "\" column=\"" << SM.getColumnNumber(FID, FileOffset) 912 << "\""; 913 } 914 } 915 916 // Finish the root tag. 917 Result << ">"; 918 919 bool FoundName = false; 920 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) { 921 if (DeclarationName DeclName = ND->getDeclName()) { 922 Result << "<Name>"; 923 std::string Name = DeclName.getAsString(); 924 appendToResultWithXMLEscaping(Name); 925 FoundName = true; 926 Result << "</Name>"; 927 } 928 } 929 if (!FoundName) 930 Result << "<Name><anonymous></Name>"; 931 932 { 933 // Print USR. 934 SmallString<128> USR; 935 generateUSRForDecl(DI->CommentDecl, USR); 936 if (!USR.empty()) { 937 Result << "<USR>"; 938 appendToResultWithXMLEscaping(USR); 939 Result << "</USR>"; 940 } 941 } 942 } else { 943 // No DeclInfo -- just emit some root tag and name tag. 944 RootEndTag = "</Other>"; 945 Result << "<Other><Name>unknown</Name>"; 946 } 947 948 if (Parts.Headerfile) { 949 Result << "<Headerfile>"; 950 visit(Parts.Headerfile); 951 Result << "</Headerfile>"; 952 } 953 954 { 955 // Pretty-print the declaration. 956 Result << "<Declaration>"; 957 SmallString<128> Declaration; 958 getSourceTextOfDeclaration(DI, Declaration); 959 formatTextOfDeclaration(DI, Declaration); 960 appendToResultWithXMLEscaping(Declaration); 961 Result << "</Declaration>"; 962 } 963 964 bool FirstParagraphIsBrief = false; 965 if (Parts.Brief) { 966 Result << "<Abstract>"; 967 visit(Parts.Brief); 968 Result << "</Abstract>"; 969 } else if (Parts.FirstParagraph) { 970 Result << "<Abstract>"; 971 visit(Parts.FirstParagraph); 972 Result << "</Abstract>"; 973 FirstParagraphIsBrief = true; 974 } 975 976 if (Parts.TParams.size() != 0) { 977 Result << "<TemplateParameters>"; 978 for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) 979 visit(Parts.TParams[i]); 980 Result << "</TemplateParameters>"; 981 } 982 983 if (Parts.Params.size() != 0) { 984 Result << "<Parameters>"; 985 for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) 986 visit(Parts.Params[i]); 987 Result << "</Parameters>"; 988 } 989 990 if (Parts.Exceptions.size() != 0) { 991 Result << "<Exceptions>"; 992 for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i) 993 visit(Parts.Exceptions[i]); 994 Result << "</Exceptions>"; 995 } 996 997 if (Parts.Returns.size() != 0) { 998 Result << "<ResultDiscussion>"; 999 for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) 1000 visit(Parts.Returns[i]); 1001 Result << "</ResultDiscussion>"; 1002 } 1003 1004 if (DI->CommentDecl->hasAttrs()) { 1005 const AttrVec &Attrs = DI->CommentDecl->getAttrs(); 1006 for (unsigned i = 0, e = Attrs.size(); i != e; i++) { 1007 const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]); 1008 if (!AA) { 1009 if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) { 1010 if (DA->getMessage().empty()) 1011 Result << "<Deprecated/>"; 1012 else { 1013 Result << "<Deprecated>"; 1014 appendToResultWithXMLEscaping(DA->getMessage()); 1015 Result << "</Deprecated>"; 1016 } 1017 } 1018 else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) { 1019 if (UA->getMessage().empty()) 1020 Result << "<Unavailable/>"; 1021 else { 1022 Result << "<Unavailable>"; 1023 appendToResultWithXMLEscaping(UA->getMessage()); 1024 Result << "</Unavailable>"; 1025 } 1026 } 1027 continue; 1028 } 1029 1030 // 'availability' attribute. 1031 Result << "<Availability"; 1032 StringRef Distribution; 1033 if (AA->getPlatform()) { 1034 Distribution = AvailabilityAttr::getPrettyPlatformName( 1035 AA->getPlatform()->getName()); 1036 if (Distribution.empty()) 1037 Distribution = AA->getPlatform()->getName(); 1038 } 1039 Result << " distribution=\"" << Distribution << "\">"; 1040 VersionTuple IntroducedInVersion = AA->getIntroduced(); 1041 if (!IntroducedInVersion.empty()) { 1042 Result << "<IntroducedInVersion>" 1043 << IntroducedInVersion.getAsString() 1044 << "</IntroducedInVersion>"; 1045 } 1046 VersionTuple DeprecatedInVersion = AA->getDeprecated(); 1047 if (!DeprecatedInVersion.empty()) { 1048 Result << "<DeprecatedInVersion>" 1049 << DeprecatedInVersion.getAsString() 1050 << "</DeprecatedInVersion>"; 1051 } 1052 VersionTuple RemovedAfterVersion = AA->getObsoleted(); 1053 if (!RemovedAfterVersion.empty()) { 1054 Result << "<RemovedAfterVersion>" 1055 << RemovedAfterVersion.getAsString() 1056 << "</RemovedAfterVersion>"; 1057 } 1058 StringRef DeprecationSummary = AA->getMessage(); 1059 if (!DeprecationSummary.empty()) { 1060 Result << "<DeprecationSummary>"; 1061 appendToResultWithXMLEscaping(DeprecationSummary); 1062 Result << "</DeprecationSummary>"; 1063 } 1064 if (AA->getUnavailable()) 1065 Result << "<Unavailable/>"; 1066 Result << "</Availability>"; 1067 } 1068 } 1069 1070 { 1071 bool StartTagEmitted = false; 1072 for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { 1073 const Comment *C = Parts.MiscBlocks[i]; 1074 if (FirstParagraphIsBrief && C == Parts.FirstParagraph) 1075 continue; 1076 if (!StartTagEmitted) { 1077 Result << "<Discussion>"; 1078 StartTagEmitted = true; 1079 } 1080 visit(C); 1081 } 1082 if (StartTagEmitted) 1083 Result << "</Discussion>"; 1084 } 1085 1086 Result << RootEndTag; 1087 1088 Result.flush(); 1089} 1090 1091void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) { 1092 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { 1093 const char C = *I; 1094 switch (C) { 1095 case '&': 1096 Result << "&"; 1097 break; 1098 case '<': 1099 Result << "<"; 1100 break; 1101 case '>': 1102 Result << ">"; 1103 break; 1104 case '"': 1105 Result << """; 1106 break; 1107 case '\'': 1108 Result << "'"; 1109 break; 1110 default: 1111 Result << C; 1112 break; 1113 } 1114 } 1115} 1116 1117void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) { 1118 if (S.empty()) 1119 return; 1120 1121 Result << "<![CDATA["; 1122 while (!S.empty()) { 1123 size_t Pos = S.find("]]>"); 1124 if (Pos == 0) { 1125 Result << "]]]]><![CDATA[>"; 1126 S = S.drop_front(3); 1127 continue; 1128 } 1129 if (Pos == StringRef::npos) 1130 Pos = S.size(); 1131 1132 Result << S.substr(0, Pos); 1133 1134 S = S.drop_front(Pos); 1135 } 1136 Result << "]]>"; 1137} 1138 1139CommentToXMLConverter::CommentToXMLConverter() : FormatInMemoryUniqueId(0) {} 1140CommentToXMLConverter::~CommentToXMLConverter() {} 1141 1142void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC, 1143 SmallVectorImpl<char> &HTML, 1144 const ASTContext &Context) { 1145 CommentASTToHTMLConverter Converter(FC, HTML, 1146 Context.getCommentCommandTraits()); 1147 Converter.visit(FC); 1148} 1149 1150void CommentToXMLConverter::convertHTMLTagNodeToText( 1151 const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text, 1152 const ASTContext &Context) { 1153 CommentASTToHTMLConverter Converter(nullptr, Text, 1154 Context.getCommentCommandTraits()); 1155 Converter.visit(HTC); 1156} 1157 1158void CommentToXMLConverter::convertCommentToXML(const FullComment *FC, 1159 SmallVectorImpl<char> &XML, 1160 const ASTContext &Context) { 1161 if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) { 1162 // Create a new format context, or re-create it after some number of 1163 // iterations, so the buffers don't grow too large. 1164 FormatContext.reset(new SimpleFormatContext(Context.getLangOpts())); 1165 } 1166 1167 CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), 1168 Context.getSourceManager(), *FormatContext, 1169 FormatInMemoryUniqueId++); 1170 Converter.visit(FC); 1171} 1172 1173