CXComment.cpp revision 2c6b00e7126399210b759f4da53c680bee01a706
1//===- CXComment.cpp - libclang APIs for manipulating CXComments ----------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file defines all libclang APIs related to walking comment AST. 11// 12//===----------------------------------------------------------------------===// 13 14#include "clang-c/Index.h" 15#include "CXString.h" 16#include "CXComment.h" 17 18#include "clang/AST/CommentVisitor.h" 19 20#include "llvm/Support/ErrorHandling.h" 21#include "llvm/Support/raw_ostream.h" 22 23using namespace clang; 24using namespace clang::cxstring; 25using namespace clang::comments; 26using namespace clang::cxcomment; 27 28extern "C" { 29 30enum CXCommentKind clang_Comment_getKind(CXComment CXC) { 31 const Comment *C = getASTNode(CXC); 32 if (!C) 33 return CXComment_Null; 34 35 switch (C->getCommentKind()) { 36 case Comment::NoCommentKind: 37 return CXComment_Null; 38 39 case Comment::TextCommentKind: 40 return CXComment_Text; 41 42 case Comment::InlineCommandCommentKind: 43 return CXComment_InlineCommand; 44 45 case Comment::HTMLStartTagCommentKind: 46 return CXComment_HTMLStartTag; 47 48 case Comment::HTMLEndTagCommentKind: 49 return CXComment_HTMLEndTag; 50 51 case Comment::ParagraphCommentKind: 52 return CXComment_Paragraph; 53 54 case Comment::BlockCommandCommentKind: 55 return CXComment_BlockCommand; 56 57 case Comment::ParamCommandCommentKind: 58 return CXComment_ParamCommand; 59 60 case Comment::VerbatimBlockCommentKind: 61 return CXComment_VerbatimBlockCommand; 62 63 case Comment::VerbatimBlockLineCommentKind: 64 return CXComment_VerbatimBlockLine; 65 66 case Comment::VerbatimLineCommentKind: 67 return CXComment_VerbatimLine; 68 69 case Comment::FullCommentKind: 70 return CXComment_FullComment; 71 } 72 llvm_unreachable("unknown CommentKind"); 73} 74 75unsigned clang_Comment_getNumChildren(CXComment CXC) { 76 const Comment *C = getASTNode(CXC); 77 if (!C) 78 return 0; 79 80 return C->child_count(); 81} 82 83CXComment clang_Comment_getChild(CXComment CXC, unsigned ChildIdx) { 84 const Comment *C = getASTNode(CXC); 85 if (!C || ChildIdx >= C->child_count()) 86 return createCXComment(NULL); 87 88 return createCXComment(*(C->child_begin() + ChildIdx)); 89} 90 91unsigned clang_Comment_isWhitespace(CXComment CXC) { 92 const Comment *C = getASTNode(CXC); 93 if (!C) 94 return false; 95 96 if (const TextComment *TC = dyn_cast<TextComment>(C)) 97 return TC->isWhitespace(); 98 99 if (const ParagraphComment *PC = dyn_cast<ParagraphComment>(C)) 100 return PC->isWhitespace(); 101 102 return false; 103} 104 105unsigned clang_InlineContentComment_hasTrailingNewline(CXComment CXC) { 106 const InlineContentComment *ICC = getASTNodeAs<InlineContentComment>(CXC); 107 if (!ICC) 108 return false; 109 110 return ICC->hasTrailingNewline(); 111} 112 113CXString clang_TextComment_getText(CXComment CXC) { 114 const TextComment *TC = getASTNodeAs<TextComment>(CXC); 115 if (!TC) 116 return createCXString((const char *) 0); 117 118 return createCXString(TC->getText(), /*DupString=*/ false); 119} 120 121CXString clang_InlineCommandComment_getCommandName(CXComment CXC) { 122 const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC); 123 if (!ICC) 124 return createCXString((const char *) 0); 125 126 return createCXString(ICC->getCommandName(), /*DupString=*/ false); 127} 128 129enum CXCommentInlineCommandRenderKind 130clang_InlineCommandComment_getRenderKind(CXComment CXC) { 131 const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC); 132 if (!ICC) 133 return CXCommentInlineCommandRenderKind_Normal; 134 135 switch (ICC->getRenderKind()) { 136 case InlineCommandComment::RenderNormal: 137 return CXCommentInlineCommandRenderKind_Normal; 138 139 case InlineCommandComment::RenderBold: 140 return CXCommentInlineCommandRenderKind_Bold; 141 142 case InlineCommandComment::RenderMonospaced: 143 return CXCommentInlineCommandRenderKind_Monospaced; 144 145 case InlineCommandComment::RenderEmphasized: 146 return CXCommentInlineCommandRenderKind_Emphasized; 147 } 148 llvm_unreachable("unknown InlineCommandComment::RenderKind"); 149} 150 151unsigned clang_InlineCommandComment_getNumArgs(CXComment CXC) { 152 const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC); 153 if (!ICC) 154 return 0; 155 156 return ICC->getNumArgs(); 157} 158 159CXString clang_InlineCommandComment_getArgText(CXComment CXC, 160 unsigned ArgIdx) { 161 const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC); 162 if (!ICC || ArgIdx >= ICC->getNumArgs()) 163 return createCXString((const char *) 0); 164 165 return createCXString(ICC->getArgText(ArgIdx), /*DupString=*/ false); 166} 167 168CXString clang_HTMLTagComment_getTagName(CXComment CXC) { 169 const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC); 170 if (!HTC) 171 return createCXString((const char *) 0); 172 173 return createCXString(HTC->getTagName(), /*DupString=*/ false); 174} 175 176unsigned clang_HTMLStartTagComment_isSelfClosing(CXComment CXC) { 177 const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC); 178 if (!HST) 179 return false; 180 181 return HST->isSelfClosing(); 182} 183 184unsigned clang_HTMLStartTag_getNumAttrs(CXComment CXC) { 185 const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC); 186 if (!HST) 187 return 0; 188 189 return HST->getNumAttrs(); 190} 191 192CXString clang_HTMLStartTag_getAttrName(CXComment CXC, unsigned AttrIdx) { 193 const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC); 194 if (!HST || AttrIdx >= HST->getNumAttrs()) 195 return createCXString((const char *) 0); 196 197 return createCXString(HST->getAttr(AttrIdx).Name, /*DupString=*/ false); 198} 199 200CXString clang_HTMLStartTag_getAttrValue(CXComment CXC, unsigned AttrIdx) { 201 const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC); 202 if (!HST || AttrIdx >= HST->getNumAttrs()) 203 return createCXString((const char *) 0); 204 205 return createCXString(HST->getAttr(AttrIdx).Value, /*DupString=*/ false); 206} 207 208CXString clang_BlockCommandComment_getCommandName(CXComment CXC) { 209 const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC); 210 if (!BCC) 211 return createCXString((const char *) 0); 212 213 return createCXString(BCC->getCommandName(), /*DupString=*/ false); 214} 215 216unsigned clang_BlockCommandComment_getNumArgs(CXComment CXC) { 217 const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC); 218 if (!BCC) 219 return 0; 220 221 return BCC->getNumArgs(); 222} 223 224CXString clang_BlockCommandComment_getArgText(CXComment CXC, 225 unsigned ArgIdx) { 226 const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC); 227 if (!BCC || ArgIdx >= BCC->getNumArgs()) 228 return createCXString((const char *) 0); 229 230 return createCXString(BCC->getArgText(ArgIdx), /*DupString=*/ false); 231} 232 233CXComment clang_BlockCommandComment_getParagraph(CXComment CXC) { 234 const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC); 235 if (!BCC) 236 return createCXComment(NULL); 237 238 return createCXComment(BCC->getParagraph()); 239} 240 241CXString clang_ParamCommandComment_getParamName(CXComment CXC) { 242 const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC); 243 if (!PCC || !PCC->hasParamName()) 244 return createCXString((const char *) 0); 245 246 return createCXString(PCC->getParamName(), /*DupString=*/ false); 247} 248 249unsigned clang_ParamCommandComment_isParamIndexValid(CXComment CXC) { 250 const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC); 251 if (!PCC) 252 return false; 253 254 return PCC->isParamIndexValid(); 255} 256 257unsigned clang_ParamCommandComment_getParamIndex(CXComment CXC) { 258 const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC); 259 if (!PCC) 260 return ParamCommandComment::InvalidParamIndex; 261 262 return PCC->getParamIndex(); 263} 264 265unsigned clang_ParamCommandComment_isDirectionExplicit(CXComment CXC) { 266 const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC); 267 if (!PCC) 268 return false; 269 270 return PCC->isDirectionExplicit(); 271} 272 273enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection( 274 CXComment CXC) { 275 const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC); 276 if (!PCC) 277 return CXCommentParamPassDirection_In; 278 279 switch (PCC->getDirection()) { 280 case ParamCommandComment::In: 281 return CXCommentParamPassDirection_In; 282 283 case ParamCommandComment::Out: 284 return CXCommentParamPassDirection_Out; 285 286 case ParamCommandComment::InOut: 287 return CXCommentParamPassDirection_InOut; 288 } 289 llvm_unreachable("unknown ParamCommandComment::PassDirection"); 290} 291 292CXString clang_VerbatimBlockLineComment_getText(CXComment CXC) { 293 const VerbatimBlockLineComment *VBL = 294 getASTNodeAs<VerbatimBlockLineComment>(CXC); 295 if (!VBL) 296 return createCXString((const char *) 0); 297 298 return createCXString(VBL->getText(), /*DupString=*/ false); 299} 300 301CXString clang_VerbatimLineComment_getText(CXComment CXC) { 302 const VerbatimLineComment *VLC = getASTNodeAs<VerbatimLineComment>(CXC); 303 if (!VLC) 304 return createCXString((const char *) 0); 305 306 return createCXString(VLC->getText(), /*DupString=*/ false); 307} 308 309} // end extern "C" 310 311//===----------------------------------------------------------------------===// 312// Helpers for converting comment AST to HTML. 313//===----------------------------------------------------------------------===// 314 315namespace { 316 317class ParamCommandCommentCompareIndex { 318public: 319 bool operator()(const ParamCommandComment *LHS, 320 const ParamCommandComment *RHS) const { 321 // To sort invalid (unresolved) parameters last, this comparison relies on 322 // invalid indices to be UINT_MAX. 323 return LHS->getParamIndex() < RHS->getParamIndex(); 324 } 325}; 326 327class CommentASTToHTMLConverter : 328 public ConstCommentVisitor<CommentASTToHTMLConverter> { 329public: 330 /// \param Str accumulator for HTML. 331 CommentASTToHTMLConverter(SmallVectorImpl<char> &Str) : Result(Str) { } 332 333 // Inline content. 334 void visitTextComment(const TextComment *C); 335 void visitInlineCommandComment(const InlineCommandComment *C); 336 void visitHTMLStartTagComment(const HTMLStartTagComment *C); 337 void visitHTMLEndTagComment(const HTMLEndTagComment *C); 338 339 // Block content. 340 void visitParagraphComment(const ParagraphComment *C); 341 void visitBlockCommandComment(const BlockCommandComment *C); 342 void visitParamCommandComment(const ParamCommandComment *C); 343 void visitVerbatimBlockComment(const VerbatimBlockComment *C); 344 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); 345 void visitVerbatimLineComment(const VerbatimLineComment *C); 346 347 void visitFullComment(const FullComment *C); 348 349 // Helpers. 350 351 /// Convert a paragraph that is not a block by itself (an argument to some 352 /// command). 353 void visitNonStandaloneParagraphComment(const ParagraphComment *C); 354 355 void appendToResultWithHTMLEscaping(StringRef S); 356 357private: 358 /// Output stream for HTML. 359 llvm::raw_svector_ostream Result; 360}; 361} // end unnamed namespace 362 363void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) { 364 appendToResultWithHTMLEscaping(C->getText()); 365} 366 367void CommentASTToHTMLConverter::visitInlineCommandComment( 368 const InlineCommandComment *C) { 369 // Nothing to render if no arguments supplied. 370 if (C->getNumArgs() == 0) 371 return; 372 373 // Nothing to render if argument is empty. 374 StringRef Arg0 = C->getArgText(0); 375 if (Arg0.empty()) 376 return; 377 378 switch (C->getRenderKind()) { 379 case InlineCommandComment::RenderNormal: 380 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) 381 Result << C->getArgText(i) << " "; 382 return; 383 384 case InlineCommandComment::RenderBold: 385 assert(C->getNumArgs() == 1); 386 Result << "<b>" << Arg0 << "</b>"; 387 return; 388 case InlineCommandComment::RenderMonospaced: 389 assert(C->getNumArgs() == 1); 390 Result << "<tt>" << Arg0 << "</tt>"; 391 return; 392 case InlineCommandComment::RenderEmphasized: 393 assert(C->getNumArgs() == 1); 394 Result << "<em>" << Arg0 << "</em>"; 395 return; 396 } 397} 398 399void CommentASTToHTMLConverter::visitHTMLStartTagComment( 400 const HTMLStartTagComment *C) { 401 Result << "<" << C->getTagName(); 402 403 if (C->getNumAttrs() != 0) { 404 for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) { 405 Result << " "; 406 const HTMLStartTagComment::Attribute &Attr = C->getAttr(i); 407 Result << Attr.Name; 408 if (!Attr.Value.empty()) 409 Result << "=\"" << Attr.Value << "\""; 410 } 411 } 412 413 if (!C->isSelfClosing()) 414 Result << ">"; 415 else 416 Result << "/>"; 417} 418 419void CommentASTToHTMLConverter::visitHTMLEndTagComment( 420 const HTMLEndTagComment *C) { 421 Result << "</" << C->getTagName() << ">"; 422} 423 424void CommentASTToHTMLConverter::visitParagraphComment( 425 const ParagraphComment *C) { 426 if (C->isWhitespace()) 427 return; 428 429 Result << "<p>"; 430 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 431 I != E; ++I) { 432 visit(*I); 433 } 434 Result << "</p>"; 435} 436 437void CommentASTToHTMLConverter::visitBlockCommandComment( 438 const BlockCommandComment *C) { 439 StringRef CommandName = C->getCommandName(); 440 if (CommandName == "brief" || CommandName == "short") { 441 Result << "<p class=\"para-brief\">"; 442 visitNonStandaloneParagraphComment(C->getParagraph()); 443 Result << "</p>"; 444 return; 445 } 446 if (CommandName == "returns" || CommandName == "return" || 447 CommandName == "result") { 448 Result << "<p class=\"para-returns\">" 449 "<span class=\"word-returns\">Returns</span> "; 450 visitNonStandaloneParagraphComment(C->getParagraph()); 451 Result << "</p>"; 452 return; 453 } 454 // We don't know anything about this command. Just render the paragraph. 455 visit(C->getParagraph()); 456} 457 458void CommentASTToHTMLConverter::visitParamCommandComment( 459 const ParamCommandComment *C) { 460 if (C->isParamIndexValid()) { 461 Result << "<dt class=\"param-name-index-" 462 << C->getParamIndex() 463 << "\">"; 464 } else 465 Result << "<dt class=\"param-name-index-invalid\">"; 466 467 Result << C->getParamName() << "</dt>"; 468 469 if (C->isParamIndexValid()) { 470 Result << "<dd class=\"param-descr-index-" 471 << C->getParamIndex() 472 << "\">"; 473 } else 474 Result << "<dd class=\"param-descr-index-invalid\">"; 475 476 visitNonStandaloneParagraphComment(C->getParagraph()); 477 Result << "</dd>"; 478} 479 480void CommentASTToHTMLConverter::visitVerbatimBlockComment( 481 const VerbatimBlockComment *C) { 482 unsigned NumLines = C->getNumLines(); 483 if (NumLines == 0) 484 return; 485 486 Result << "<pre>"; 487 for (unsigned i = 0; i != NumLines; ++i) { 488 appendToResultWithHTMLEscaping(C->getText(i)); 489 if (i + 1 != NumLines) 490 Result << '\n'; 491 } 492 Result << "</pre>"; 493} 494 495void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( 496 const VerbatimBlockLineComment *C) { 497 llvm_unreachable("should not see this AST node"); 498} 499 500void CommentASTToHTMLConverter::visitVerbatimLineComment( 501 const VerbatimLineComment *C) { 502 Result << "<pre>"; 503 appendToResultWithHTMLEscaping(C->getText()); 504 Result << "</pre>"; 505} 506 507void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { 508 const BlockContentComment *Brief = NULL; 509 const ParagraphComment *FirstParagraph = NULL; 510 const BlockCommandComment *Returns = NULL; 511 SmallVector<const ParamCommandComment *, 8> Params; 512 SmallVector<const BlockContentComment *, 8> MiscBlocks; 513 514 // Extract various blocks into separate variables and vectors above. 515 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 516 I != E; ++I) { 517 const Comment *Child = *I; 518 if (!Child) 519 continue; 520 switch (Child->getCommentKind()) { 521 case Comment::NoCommentKind: 522 continue; 523 524 case Comment::ParagraphCommentKind: { 525 const ParagraphComment *PC = cast<ParagraphComment>(Child); 526 if (PC->isWhitespace()) 527 break; 528 if (!FirstParagraph) 529 FirstParagraph = PC; 530 531 MiscBlocks.push_back(PC); 532 break; 533 } 534 535 case Comment::BlockCommandCommentKind: { 536 const BlockCommandComment *BCC = cast<BlockCommandComment>(Child); 537 StringRef CommandName = BCC->getCommandName(); 538 if (!Brief && (CommandName == "brief" || CommandName == "short")) { 539 Brief = BCC; 540 break; 541 } 542 if (!Returns && (CommandName == "returns" || CommandName == "return")) { 543 Returns = BCC; 544 break; 545 } 546 MiscBlocks.push_back(BCC); 547 break; 548 } 549 550 case Comment::ParamCommandCommentKind: { 551 const ParamCommandComment *PCC = cast<ParamCommandComment>(Child); 552 if (!PCC->hasParamName()) 553 break; 554 555 if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) 556 break; 557 558 Params.push_back(PCC); 559 break; 560 } 561 562 case Comment::VerbatimBlockCommentKind: 563 case Comment::VerbatimLineCommentKind: 564 MiscBlocks.push_back(cast<BlockCommandComment>(Child)); 565 break; 566 567 case Comment::TextCommentKind: 568 case Comment::InlineCommandCommentKind: 569 case Comment::HTMLStartTagCommentKind: 570 case Comment::HTMLEndTagCommentKind: 571 case Comment::VerbatimBlockLineCommentKind: 572 case Comment::FullCommentKind: 573 llvm_unreachable("AST node of this kind can't be a child of " 574 "a FullComment"); 575 } 576 } 577 578 // Sort params in order they are declared in the function prototype. 579 // Unresolved parameters are put at the end of the list in the same order 580 // they were seen in the comment. 581 std::stable_sort(Params.begin(), Params.end(), 582 ParamCommandCommentCompareIndex()); 583 584 bool FirstParagraphIsBrief = false; 585 if (Brief) 586 visit(Brief); 587 else if (FirstParagraph) { 588 Result << "<p class=\"para-brief\">"; 589 visitNonStandaloneParagraphComment(FirstParagraph); 590 Result << "</p>"; 591 FirstParagraphIsBrief = true; 592 } 593 594 for (unsigned i = 0, e = MiscBlocks.size(); i != e; ++i) { 595 const Comment *C = MiscBlocks[i]; 596 if (FirstParagraphIsBrief && C == FirstParagraph) 597 continue; 598 visit(C); 599 } 600 601 if (Params.size() != 0) { 602 Result << "<dl>"; 603 for (unsigned i = 0, e = Params.size(); i != e; ++i) 604 visit(Params[i]); 605 Result << "</dl>"; 606 } 607 608 if (Returns) 609 visit(Returns); 610 611 Result.flush(); 612} 613 614void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment( 615 const ParagraphComment *C) { 616 if (!C) 617 return; 618 619 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 620 I != E; ++I) { 621 visit(*I); 622 } 623} 624 625void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) { 626 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { 627 const char C = *I; 628 switch (C) { 629 case '&': 630 Result << "&"; 631 break; 632 case '<': 633 Result << "<"; 634 break; 635 case '>': 636 Result << ">"; 637 break; 638 case '"': 639 Result << """; 640 break; 641 case '\'': 642 Result << "'"; 643 break; 644 case '/': 645 Result << "/"; 646 break; 647 default: 648 Result << C; 649 break; 650 } 651 } 652} 653 654extern "C" { 655 656CXString clang_HTMLTagComment_getAsString(CXComment CXC) { 657 const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC); 658 if (!HTC) 659 return createCXString((const char *) 0); 660 661 SmallString<128> HTML; 662 CommentASTToHTMLConverter Converter(HTML); 663 Converter.visit(HTC); 664 return createCXString(HTML.str(), /* DupString = */ true); 665} 666 667CXString clang_FullComment_getAsHTML(CXComment CXC) { 668 const FullComment *FC = getASTNodeAs<FullComment>(CXC); 669 if (!FC) 670 return createCXString((const char *) 0); 671 672 SmallString<1024> HTML; 673 CommentASTToHTMLConverter Converter(HTML); 674 Converter.visit(FC); 675 return createCXString(HTML.str(), /* DupString = */ true); 676} 677 678} // end extern "C" 679 680