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