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