1 //===--- CommentToXML.cpp - Convert comments to XML representation --------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/Index/CommentToXML.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/Attr.h" 12 #include "clang/AST/Comment.h" 13 #include "clang/AST/CommentVisitor.h" 14 #include "clang/Basic/IdentifierTable.h" 15 #include "clang/Basic/SourceManager.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 CommentKind::None: 107 continue; 108 109 case CommentKind::ParagraphComment: { 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 CommentKind::BlockCommandComment: { 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 CommentKind::ParamCommandComment: { 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 CommentKind::TParamCommandComment: { 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 CommentKind::VerbatimBlockComment: 168 MiscBlocks.push_back(cast<BlockCommandComment>(Child)); 169 break; 170 171 case CommentKind::VerbatimLineComment: { 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 CommentKind::TextComment: 180 case CommentKind::InlineCommandComment: 181 case CommentKind::HTMLStartTagComment: 182 case CommentKind::HTMLEndTagComment: 183 case CommentKind::VerbatimBlockLineComment: 184 case CommentKind::FullComment: 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 llvm::stable_sort(Params, ParamCommandCommentCompareIndex()); 194 llvm::stable_sort(TParams, TParamCommandCommentComparePosition()); 195 } 196 197 void printHTMLStartTagComment(const HTMLStartTagComment *C, 198 llvm::raw_svector_ostream &Result) { 199 Result << "<" << C->getTagName(); 200 201 if (C->getNumAttrs() != 0) { 202 for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) { 203 Result << " "; 204 const HTMLStartTagComment::Attribute &Attr = C->getAttr(i); 205 Result << Attr.Name; 206 if (!Attr.Value.empty()) 207 Result << "=\"" << Attr.Value << "\""; 208 } 209 } 210 211 if (!C->isSelfClosing()) 212 Result << ">"; 213 else 214 Result << "/>"; 215 } 216 217 class CommentASTToHTMLConverter : 218 public ConstCommentVisitor<CommentASTToHTMLConverter> { 219 public: 220 /// \param Str accumulator for HTML. 221 CommentASTToHTMLConverter(const FullComment *FC, 222 SmallVectorImpl<char> &Str, 223 const CommandTraits &Traits) : 224 FC(FC), Result(Str), Traits(Traits) 225 { } 226 227 // Inline content. 228 void visitTextComment(const TextComment *C); 229 void visitInlineCommandComment(const InlineCommandComment *C); 230 void visitHTMLStartTagComment(const HTMLStartTagComment *C); 231 void visitHTMLEndTagComment(const HTMLEndTagComment *C); 232 233 // Block content. 234 void visitParagraphComment(const ParagraphComment *C); 235 void visitBlockCommandComment(const BlockCommandComment *C); 236 void visitParamCommandComment(const ParamCommandComment *C); 237 void visitTParamCommandComment(const TParamCommandComment *C); 238 void visitVerbatimBlockComment(const VerbatimBlockComment *C); 239 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); 240 void visitVerbatimLineComment(const VerbatimLineComment *C); 241 242 void visitFullComment(const FullComment *C); 243 244 // Helpers. 245 246 /// Convert a paragraph that is not a block by itself (an argument to some 247 /// command). 248 void visitNonStandaloneParagraphComment(const ParagraphComment *C); 249 250 void appendToResultWithHTMLEscaping(StringRef S); 251 252 private: 253 const FullComment *FC; 254 /// Output stream for HTML. 255 llvm::raw_svector_ostream Result; 256 257 const CommandTraits &Traits; 258 }; 259 } // end unnamed namespace 260 261 void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) { 262 appendToResultWithHTMLEscaping(C->getText()); 263 } 264 265 void CommentASTToHTMLConverter::visitInlineCommandComment( 266 const InlineCommandComment *C) { 267 // Nothing to render if no arguments supplied. 268 if (C->getNumArgs() == 0) 269 return; 270 271 // Nothing to render if argument is empty. 272 StringRef Arg0 = C->getArgText(0); 273 if (Arg0.empty()) 274 return; 275 276 switch (C->getRenderKind()) { 277 case InlineCommandRenderKind::Normal: 278 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { 279 appendToResultWithHTMLEscaping(C->getArgText(i)); 280 Result << " "; 281 } 282 return; 283 284 case InlineCommandRenderKind::Bold: 285 assert(C->getNumArgs() == 1); 286 Result << "<b>"; 287 appendToResultWithHTMLEscaping(Arg0); 288 Result << "</b>"; 289 return; 290 case InlineCommandRenderKind::Monospaced: 291 assert(C->getNumArgs() == 1); 292 Result << "<tt>"; 293 appendToResultWithHTMLEscaping(Arg0); 294 Result<< "</tt>"; 295 return; 296 case InlineCommandRenderKind::Emphasized: 297 assert(C->getNumArgs() == 1); 298 Result << "<em>"; 299 appendToResultWithHTMLEscaping(Arg0); 300 Result << "</em>"; 301 return; 302 case InlineCommandRenderKind::Anchor: 303 assert(C->getNumArgs() == 1); 304 Result << "<span id=\"" << Arg0 << "\"></span>"; 305 return; 306 } 307 } 308 309 void CommentASTToHTMLConverter::visitHTMLStartTagComment( 310 const HTMLStartTagComment *C) { 311 printHTMLStartTagComment(C, Result); 312 } 313 314 void CommentASTToHTMLConverter::visitHTMLEndTagComment( 315 const HTMLEndTagComment *C) { 316 Result << "</" << C->getTagName() << ">"; 317 } 318 319 void 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 332 void 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 352 void 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 384 void 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 415 void 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 430 void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( 431 const VerbatimBlockLineComment *C) { 432 llvm_unreachable("should not see this AST node"); 433 } 434 435 void CommentASTToHTMLConverter::visitVerbatimLineComment( 436 const VerbatimLineComment *C) { 437 Result << "<pre>"; 438 appendToResultWithHTMLEscaping(C->getText()); 439 Result << "</pre>"; 440 } 441 442 void 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 } 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 FC(FC), Result(Str), Traits(Traits), SM(SM) { } 537 538 // Inline content. 539 void visitTextComment(const TextComment *C); 540 void visitInlineCommandComment(const InlineCommandComment *C); 541 void visitHTMLStartTagComment(const HTMLStartTagComment *C); 542 void visitHTMLEndTagComment(const HTMLEndTagComment *C); 543 544 // Block content. 545 void visitParagraphComment(const ParagraphComment *C); 546 547 void appendParagraphCommentWithKind(const ParagraphComment *C, 548 StringRef ParagraphKind, 549 StringRef PrependBodyText); 550 551 void visitBlockCommandComment(const BlockCommandComment *C); 552 void visitParamCommandComment(const ParamCommandComment *C); 553 void visitTParamCommandComment(const TParamCommandComment *C); 554 void visitVerbatimBlockComment(const VerbatimBlockComment *C); 555 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); 556 void visitVerbatimLineComment(const VerbatimLineComment *C); 557 558 void visitFullComment(const FullComment *C); 559 560 // Helpers. 561 void appendToResultWithXMLEscaping(StringRef S); 562 void appendToResultWithCDATAEscaping(StringRef S); 563 564 void formatTextOfDeclaration(const DeclInfo *DI, 565 SmallString<128> &Declaration); 566 567 private: 568 const FullComment *FC; 569 570 /// Output stream for XML. 571 llvm::raw_svector_ostream Result; 572 573 const CommandTraits &Traits; 574 const SourceManager &SM; 575 }; 576 577 void getSourceTextOfDeclaration(const DeclInfo *ThisDecl, 578 SmallVectorImpl<char> &Str) { 579 ASTContext &Context = ThisDecl->CurrentDecl->getASTContext(); 580 const LangOptions &LangOpts = Context.getLangOpts(); 581 llvm::raw_svector_ostream OS(Str); 582 PrintingPolicy PPolicy(LangOpts); 583 PPolicy.PolishForDeclaration = true; 584 PPolicy.TerseOutput = true; 585 PPolicy.ConstantsAsWritten = true; 586 ThisDecl->CurrentDecl->print(OS, PPolicy, 587 /*Indentation*/0, /*PrintInstantiation*/false); 588 } 589 590 void CommentASTToXMLConverter::formatTextOfDeclaration( 591 const DeclInfo *DI, SmallString<128> &Declaration) { 592 // Formatting API expects null terminated input string. 593 StringRef StringDecl(Declaration.c_str(), Declaration.size()); 594 595 // Formatter specific code. 596 unsigned Offset = 0; 597 unsigned Length = Declaration.size(); 598 599 format::FormatStyle Style = format::getLLVMStyle(); 600 Style.FixNamespaceComments = false; 601 tooling::Replacements Replaces = 602 reformat(Style, StringDecl, tooling::Range(Offset, Length), "xmldecl.xd"); 603 auto FormattedStringDecl = applyAllReplacements(StringDecl, Replaces); 604 if (static_cast<bool>(FormattedStringDecl)) { 605 Declaration = *FormattedStringDecl; 606 } 607 } 608 609 } // end unnamed namespace 610 611 void CommentASTToXMLConverter::visitTextComment(const TextComment *C) { 612 appendToResultWithXMLEscaping(C->getText()); 613 } 614 615 void CommentASTToXMLConverter::visitInlineCommandComment( 616 const InlineCommandComment *C) { 617 // Nothing to render if no arguments supplied. 618 if (C->getNumArgs() == 0) 619 return; 620 621 // Nothing to render if argument is empty. 622 StringRef Arg0 = C->getArgText(0); 623 if (Arg0.empty()) 624 return; 625 626 switch (C->getRenderKind()) { 627 case InlineCommandRenderKind::Normal: 628 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { 629 appendToResultWithXMLEscaping(C->getArgText(i)); 630 Result << " "; 631 } 632 return; 633 case InlineCommandRenderKind::Bold: 634 assert(C->getNumArgs() == 1); 635 Result << "<bold>"; 636 appendToResultWithXMLEscaping(Arg0); 637 Result << "</bold>"; 638 return; 639 case InlineCommandRenderKind::Monospaced: 640 assert(C->getNumArgs() == 1); 641 Result << "<monospaced>"; 642 appendToResultWithXMLEscaping(Arg0); 643 Result << "</monospaced>"; 644 return; 645 case InlineCommandRenderKind::Emphasized: 646 assert(C->getNumArgs() == 1); 647 Result << "<emphasized>"; 648 appendToResultWithXMLEscaping(Arg0); 649 Result << "</emphasized>"; 650 return; 651 case InlineCommandRenderKind::Anchor: 652 assert(C->getNumArgs() == 1); 653 Result << "<anchor id=\"" << Arg0 << "\"></anchor>"; 654 return; 655 } 656 } 657 658 void CommentASTToXMLConverter::visitHTMLStartTagComment( 659 const HTMLStartTagComment *C) { 660 Result << "<rawHTML"; 661 if (C->isMalformed()) 662 Result << " isMalformed=\"1\""; 663 Result << ">"; 664 { 665 SmallString<32> Tag; 666 { 667 llvm::raw_svector_ostream TagOS(Tag); 668 printHTMLStartTagComment(C, TagOS); 669 } 670 appendToResultWithCDATAEscaping(Tag); 671 } 672 Result << "</rawHTML>"; 673 } 674 675 void 676 CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) { 677 Result << "<rawHTML"; 678 if (C->isMalformed()) 679 Result << " isMalformed=\"1\""; 680 Result << "></" << C->getTagName() << "></rawHTML>"; 681 } 682 683 void CommentASTToXMLConverter::visitParagraphComment( 684 const ParagraphComment *C) { 685 appendParagraphCommentWithKind(C, StringRef(), StringRef()); 686 } 687 688 void CommentASTToXMLConverter::appendParagraphCommentWithKind( 689 const ParagraphComment *C, StringRef ParagraphKind, 690 StringRef PrependBodyText) { 691 if (C->isWhitespace() && PrependBodyText.empty()) 692 return; 693 694 if (ParagraphKind.empty()) 695 Result << "<Para>"; 696 else 697 Result << "<Para kind=\"" << ParagraphKind << "\">"; 698 699 if (!PrependBodyText.empty()) 700 Result << PrependBodyText << " "; 701 702 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); I != E; 703 ++I) { 704 visit(*I); 705 } 706 Result << "</Para>"; 707 } 708 709 void CommentASTToXMLConverter::visitBlockCommandComment( 710 const BlockCommandComment *C) { 711 StringRef ParagraphKind; 712 StringRef ExceptionType; 713 714 const unsigned CommandID = C->getCommandID(); 715 const CommandInfo *Info = Traits.getCommandInfo(CommandID); 716 if (Info->IsThrowsCommand && C->getNumArgs() > 0) { 717 ExceptionType = C->getArgText(0); 718 } 719 720 switch (CommandID) { 721 case CommandTraits::KCI_attention: 722 case CommandTraits::KCI_author: 723 case CommandTraits::KCI_authors: 724 case CommandTraits::KCI_bug: 725 case CommandTraits::KCI_copyright: 726 case CommandTraits::KCI_date: 727 case CommandTraits::KCI_invariant: 728 case CommandTraits::KCI_note: 729 case CommandTraits::KCI_post: 730 case CommandTraits::KCI_pre: 731 case CommandTraits::KCI_remark: 732 case CommandTraits::KCI_remarks: 733 case CommandTraits::KCI_sa: 734 case CommandTraits::KCI_see: 735 case CommandTraits::KCI_since: 736 case CommandTraits::KCI_todo: 737 case CommandTraits::KCI_version: 738 case CommandTraits::KCI_warning: 739 ParagraphKind = C->getCommandName(Traits); 740 break; 741 default: 742 break; 743 } 744 745 appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind, 746 ExceptionType); 747 } 748 749 void CommentASTToXMLConverter::visitParamCommandComment( 750 const ParamCommandComment *C) { 751 Result << "<Parameter><Name>"; 752 appendToResultWithXMLEscaping(C->isParamIndexValid() 753 ? C->getParamName(FC) 754 : C->getParamNameAsWritten()); 755 Result << "</Name>"; 756 757 if (C->isParamIndexValid()) { 758 if (C->isVarArgParam()) 759 Result << "<IsVarArg />"; 760 else 761 Result << "<Index>" << C->getParamIndex() << "</Index>"; 762 } 763 764 Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">"; 765 switch (C->getDirection()) { 766 case ParamCommandPassDirection::In: 767 Result << "in"; 768 break; 769 case ParamCommandPassDirection::Out: 770 Result << "out"; 771 break; 772 case ParamCommandPassDirection::InOut: 773 Result << "in,out"; 774 break; 775 } 776 Result << "</Direction><Discussion>"; 777 visit(C->getParagraph()); 778 Result << "</Discussion></Parameter>"; 779 } 780 781 void CommentASTToXMLConverter::visitTParamCommandComment( 782 const TParamCommandComment *C) { 783 Result << "<Parameter><Name>"; 784 appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC) 785 : C->getParamNameAsWritten()); 786 Result << "</Name>"; 787 788 if (C->isPositionValid() && C->getDepth() == 1) { 789 Result << "<Index>" << C->getIndex(0) << "</Index>"; 790 } 791 792 Result << "<Discussion>"; 793 visit(C->getParagraph()); 794 Result << "</Discussion></Parameter>"; 795 } 796 797 void CommentASTToXMLConverter::visitVerbatimBlockComment( 798 const VerbatimBlockComment *C) { 799 unsigned NumLines = C->getNumLines(); 800 if (NumLines == 0) 801 return; 802 803 switch (C->getCommandID()) { 804 case CommandTraits::KCI_code: 805 Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">"; 806 break; 807 default: 808 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; 809 break; 810 } 811 for (unsigned i = 0; i != NumLines; ++i) { 812 appendToResultWithXMLEscaping(C->getText(i)); 813 if (i + 1 != NumLines) 814 Result << '\n'; 815 } 816 Result << "</Verbatim>"; 817 } 818 819 void CommentASTToXMLConverter::visitVerbatimBlockLineComment( 820 const VerbatimBlockLineComment *C) { 821 llvm_unreachable("should not see this AST node"); 822 } 823 824 void CommentASTToXMLConverter::visitVerbatimLineComment( 825 const VerbatimLineComment *C) { 826 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">"; 827 appendToResultWithXMLEscaping(C->getText()); 828 Result << "</Verbatim>"; 829 } 830 831 void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { 832 FullCommentParts Parts(C, Traits); 833 834 const DeclInfo *DI = C->getDeclInfo(); 835 StringRef RootEndTag; 836 if (DI) { 837 switch (DI->getKind()) { 838 case DeclInfo::OtherKind: 839 RootEndTag = "</Other>"; 840 Result << "<Other"; 841 break; 842 case DeclInfo::FunctionKind: 843 RootEndTag = "</Function>"; 844 Result << "<Function"; 845 switch (DI->TemplateKind) { 846 case DeclInfo::NotTemplate: 847 break; 848 case DeclInfo::Template: 849 Result << " templateKind=\"template\""; 850 break; 851 case DeclInfo::TemplateSpecialization: 852 Result << " templateKind=\"specialization\""; 853 break; 854 case DeclInfo::TemplatePartialSpecialization: 855 llvm_unreachable("partial specializations of functions " 856 "are not allowed in C++"); 857 } 858 if (DI->IsInstanceMethod) 859 Result << " isInstanceMethod=\"1\""; 860 if (DI->IsClassMethod) 861 Result << " isClassMethod=\"1\""; 862 break; 863 case DeclInfo::ClassKind: 864 RootEndTag = "</Class>"; 865 Result << "<Class"; 866 switch (DI->TemplateKind) { 867 case DeclInfo::NotTemplate: 868 break; 869 case DeclInfo::Template: 870 Result << " templateKind=\"template\""; 871 break; 872 case DeclInfo::TemplateSpecialization: 873 Result << " templateKind=\"specialization\""; 874 break; 875 case DeclInfo::TemplatePartialSpecialization: 876 Result << " templateKind=\"partialSpecialization\""; 877 break; 878 } 879 break; 880 case DeclInfo::VariableKind: 881 RootEndTag = "</Variable>"; 882 Result << "<Variable"; 883 break; 884 case DeclInfo::NamespaceKind: 885 RootEndTag = "</Namespace>"; 886 Result << "<Namespace"; 887 break; 888 case DeclInfo::TypedefKind: 889 RootEndTag = "</Typedef>"; 890 Result << "<Typedef"; 891 break; 892 case DeclInfo::EnumKind: 893 RootEndTag = "</Enum>"; 894 Result << "<Enum"; 895 break; 896 } 897 898 { 899 // Print line and column number. 900 SourceLocation Loc = DI->CurrentDecl->getLocation(); 901 FileIDAndOffset LocInfo = SM.getDecomposedLoc(Loc); 902 FileID FID = LocInfo.first; 903 unsigned FileOffset = LocInfo.second; 904 905 if (FID.isValid()) { 906 if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID)) { 907 Result << " file=\""; 908 appendToResultWithXMLEscaping(FE->getName()); 909 Result << "\""; 910 } 911 Result << " line=\"" << SM.getLineNumber(FID, FileOffset) 912 << "\" column=\"" << SM.getColumnNumber(FID, FileOffset) 913 << "\""; 914 } 915 } 916 917 // Finish the root tag. 918 Result << ">"; 919 920 bool FoundName = false; 921 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) { 922 if (DeclarationName DeclName = ND->getDeclName()) { 923 Result << "<Name>"; 924 std::string Name = DeclName.getAsString(); 925 appendToResultWithXMLEscaping(Name); 926 FoundName = true; 927 Result << "</Name>"; 928 } 929 } 930 if (!FoundName) 931 Result << "<Name><anonymous></Name>"; 932 933 { 934 // Print USR. 935 SmallString<128> USR; 936 generateUSRForDecl(DI->CommentDecl, USR); 937 if (!USR.empty()) { 938 Result << "<USR>"; 939 appendToResultWithXMLEscaping(USR); 940 Result << "</USR>"; 941 } 942 } 943 } else { 944 // No DeclInfo -- just emit some root tag and name tag. 945 RootEndTag = "</Other>"; 946 Result << "<Other><Name>unknown</Name>"; 947 } 948 949 if (Parts.Headerfile) { 950 Result << "<Headerfile>"; 951 visit(Parts.Headerfile); 952 Result << "</Headerfile>"; 953 } 954 955 { 956 // Pretty-print the declaration. 957 Result << "<Declaration>"; 958 SmallString<128> Declaration; 959 getSourceTextOfDeclaration(DI, Declaration); 960 formatTextOfDeclaration(DI, Declaration); 961 appendToResultWithXMLEscaping(Declaration); 962 Result << "</Declaration>"; 963 } 964 965 bool FirstParagraphIsBrief = false; 966 if (Parts.Brief) { 967 Result << "<Abstract>"; 968 visit(Parts.Brief); 969 Result << "</Abstract>"; 970 } else if (Parts.FirstParagraph) { 971 Result << "<Abstract>"; 972 visit(Parts.FirstParagraph); 973 Result << "</Abstract>"; 974 FirstParagraphIsBrief = true; 975 } 976 977 if (Parts.TParams.size() != 0) { 978 Result << "<TemplateParameters>"; 979 for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) 980 visit(Parts.TParams[i]); 981 Result << "</TemplateParameters>"; 982 } 983 984 if (Parts.Params.size() != 0) { 985 Result << "<Parameters>"; 986 for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) 987 visit(Parts.Params[i]); 988 Result << "</Parameters>"; 989 } 990 991 if (Parts.Exceptions.size() != 0) { 992 Result << "<Exceptions>"; 993 for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i) 994 visit(Parts.Exceptions[i]); 995 Result << "</Exceptions>"; 996 } 997 998 if (Parts.Returns.size() != 0) { 999 Result << "<ResultDiscussion>"; 1000 for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) 1001 visit(Parts.Returns[i]); 1002 Result << "</ResultDiscussion>"; 1003 } 1004 1005 if (DI->CommentDecl->hasAttrs()) { 1006 const AttrVec &Attrs = DI->CommentDecl->getAttrs(); 1007 for (unsigned i = 0, e = Attrs.size(); i != e; i++) { 1008 const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]); 1009 if (!AA) { 1010 if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) { 1011 if (DA->getMessage().empty()) 1012 Result << "<Deprecated/>"; 1013 else { 1014 Result << "<Deprecated>"; 1015 appendToResultWithXMLEscaping(DA->getMessage()); 1016 Result << "</Deprecated>"; 1017 } 1018 } 1019 else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) { 1020 if (UA->getMessage().empty()) 1021 Result << "<Unavailable/>"; 1022 else { 1023 Result << "<Unavailable>"; 1024 appendToResultWithXMLEscaping(UA->getMessage()); 1025 Result << "</Unavailable>"; 1026 } 1027 } 1028 continue; 1029 } 1030 1031 // 'availability' attribute. 1032 Result << "<Availability"; 1033 StringRef Distribution; 1034 if (AA->getPlatform()) { 1035 Distribution = AvailabilityAttr::getPrettyPlatformName( 1036 AA->getPlatform()->getName()); 1037 if (Distribution.empty()) 1038 Distribution = AA->getPlatform()->getName(); 1039 } 1040 Result << " distribution=\"" << Distribution << "\">"; 1041 VersionTuple IntroducedInVersion = AA->getIntroduced(); 1042 if (!IntroducedInVersion.empty()) { 1043 Result << "<IntroducedInVersion>" 1044 << IntroducedInVersion.getAsString() 1045 << "</IntroducedInVersion>"; 1046 } 1047 VersionTuple DeprecatedInVersion = AA->getDeprecated(); 1048 if (!DeprecatedInVersion.empty()) { 1049 Result << "<DeprecatedInVersion>" 1050 << DeprecatedInVersion.getAsString() 1051 << "</DeprecatedInVersion>"; 1052 } 1053 VersionTuple RemovedAfterVersion = AA->getObsoleted(); 1054 if (!RemovedAfterVersion.empty()) { 1055 Result << "<RemovedAfterVersion>" 1056 << RemovedAfterVersion.getAsString() 1057 << "</RemovedAfterVersion>"; 1058 } 1059 StringRef DeprecationSummary = AA->getMessage(); 1060 if (!DeprecationSummary.empty()) { 1061 Result << "<DeprecationSummary>"; 1062 appendToResultWithXMLEscaping(DeprecationSummary); 1063 Result << "</DeprecationSummary>"; 1064 } 1065 if (AA->getUnavailable()) 1066 Result << "<Unavailable/>"; 1067 1068 IdentifierInfo *Environment = AA->getEnvironment(); 1069 if (Environment) { 1070 Result << "<Environment>" << Environment->getName() << "</Environment>"; 1071 } 1072 Result << "</Availability>"; 1073 } 1074 } 1075 1076 { 1077 bool StartTagEmitted = false; 1078 for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { 1079 const Comment *C = Parts.MiscBlocks[i]; 1080 if (FirstParagraphIsBrief && C == Parts.FirstParagraph) 1081 continue; 1082 if (!StartTagEmitted) { 1083 Result << "<Discussion>"; 1084 StartTagEmitted = true; 1085 } 1086 visit(C); 1087 } 1088 if (StartTagEmitted) 1089 Result << "</Discussion>"; 1090 } 1091 1092 Result << RootEndTag; 1093 } 1094 1095 void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) { 1096 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { 1097 const char C = *I; 1098 switch (C) { 1099 case '&': 1100 Result << "&"; 1101 break; 1102 case '<': 1103 Result << "<"; 1104 break; 1105 case '>': 1106 Result << ">"; 1107 break; 1108 case '"': 1109 Result << """; 1110 break; 1111 case '\'': 1112 Result << "'"; 1113 break; 1114 default: 1115 Result << C; 1116 break; 1117 } 1118 } 1119 } 1120 1121 void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) { 1122 if (S.empty()) 1123 return; 1124 1125 Result << "<![CDATA["; 1126 while (!S.empty()) { 1127 size_t Pos = S.find("]]>"); 1128 if (Pos == 0) { 1129 Result << "]]]]><![CDATA[>"; 1130 S = S.drop_front(3); 1131 continue; 1132 } 1133 if (Pos == StringRef::npos) 1134 Pos = S.size(); 1135 1136 Result << S.substr(0, Pos); 1137 1138 S = S.drop_front(Pos); 1139 } 1140 Result << "]]>"; 1141 } 1142 1143 CommentToXMLConverter::CommentToXMLConverter() {} 1144 CommentToXMLConverter::~CommentToXMLConverter() {} 1145 1146 void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC, 1147 SmallVectorImpl<char> &HTML, 1148 const ASTContext &Context) { 1149 CommentASTToHTMLConverter Converter(FC, HTML, 1150 Context.getCommentCommandTraits()); 1151 Converter.visit(FC); 1152 } 1153 1154 void CommentToXMLConverter::convertHTMLTagNodeToText( 1155 const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text, 1156 const ASTContext &Context) { 1157 CommentASTToHTMLConverter Converter(nullptr, Text, 1158 Context.getCommentCommandTraits()); 1159 Converter.visit(HTC); 1160 } 1161 1162 void CommentToXMLConverter::convertCommentToXML(const FullComment *FC, 1163 SmallVectorImpl<char> &XML, 1164 const ASTContext &Context) { 1165 CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), 1166 Context.getSourceManager()); 1167 Converter.visit(FC); 1168 } 1169