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