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