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