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