CXComment.cpp revision 1f8c529a2a938a5055f440aa063ea0b49eafae98
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 Result << "<p class=\"para-returns\">" 448 "<span class=\"word-returns\">Returns</span> "; 449 visitNonStandaloneParagraphComment(C->getParagraph()); 450 Result << "</p>"; 451 return; 452 } 453 // We don't know anything about this command. Just render the paragraph. 454 visit(C->getParagraph()); 455} 456 457void CommentASTToHTMLConverter::visitParamCommandComment( 458 const ParamCommandComment *C) { 459 if (C->isParamIndexValid()) { 460 Result << "<dt class=\"param-name-index-" 461 << C->getParamIndex() 462 << "\">"; 463 } else 464 Result << "<dt class=\"param-name-index-invalid\">"; 465 466 Result << C->getParamName() << "</dt>"; 467 468 if (C->isParamIndexValid()) { 469 Result << "<dd class=\"param-descr-index-" 470 << C->getParamIndex() 471 << "\">"; 472 } else 473 Result << "<dd class=\"param-descr-index-invalid\">"; 474 475 visitNonStandaloneParagraphComment(C->getParagraph()); 476 Result << "</dd>"; 477} 478 479void CommentASTToHTMLConverter::visitVerbatimBlockComment( 480 const VerbatimBlockComment *C) { 481 unsigned NumLines = C->getNumLines(); 482 if (NumLines == 0) 483 return; 484 485 Result << "<pre>"; 486 for (unsigned i = 0; i != NumLines; ++i) { 487 appendToResultWithHTMLEscaping(C->getText(i)); 488 if (i + 1 != NumLines) 489 Result << '\n'; 490 } 491 Result << "</pre>"; 492} 493 494void CommentASTToHTMLConverter::visitVerbatimBlockLineComment( 495 const VerbatimBlockLineComment *C) { 496 llvm_unreachable("should not see this AST node"); 497} 498 499void CommentASTToHTMLConverter::visitVerbatimLineComment( 500 const VerbatimLineComment *C) { 501 Result << "<pre>"; 502 appendToResultWithHTMLEscaping(C->getText()); 503 Result << "</pre>"; 504} 505 506void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) { 507 const BlockContentComment *Brief = NULL; 508 const ParagraphComment *FirstParagraph = NULL; 509 const BlockCommandComment *Returns = NULL; 510 SmallVector<const ParamCommandComment *, 8> Params; 511 SmallVector<const BlockContentComment *, 8> MiscBlocks; 512 513 // Extract various blocks into separate variables and vectors above. 514 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 515 I != E; ++I) { 516 const Comment *Child = *I; 517 if (!Child) 518 continue; 519 switch (Child->getCommentKind()) { 520 case Comment::NoCommentKind: 521 continue; 522 523 case Comment::ParagraphCommentKind: { 524 const ParagraphComment *PC = cast<ParagraphComment>(Child); 525 if (PC->isWhitespace()) 526 break; 527 if (!FirstParagraph) 528 FirstParagraph = PC; 529 530 MiscBlocks.push_back(PC); 531 break; 532 } 533 534 case Comment::BlockCommandCommentKind: { 535 const BlockCommandComment *BCC = cast<BlockCommandComment>(Child); 536 StringRef CommandName = BCC->getCommandName(); 537 if (!Brief && (CommandName == "brief" || CommandName == "short")) { 538 Brief = BCC; 539 break; 540 } 541 if (!Returns && (CommandName == "returns" || CommandName == "return")) { 542 Returns = BCC; 543 break; 544 } 545 MiscBlocks.push_back(BCC); 546 break; 547 } 548 549 case Comment::ParamCommandCommentKind: { 550 const ParamCommandComment *PCC = cast<ParamCommandComment>(Child); 551 if (!PCC->hasParamName()) 552 break; 553 554 if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) 555 break; 556 557 Params.push_back(PCC); 558 break; 559 } 560 561 case Comment::VerbatimBlockCommentKind: 562 case Comment::VerbatimLineCommentKind: 563 MiscBlocks.push_back(cast<BlockCommandComment>(Child)); 564 break; 565 566 case Comment::TextCommentKind: 567 case Comment::InlineCommandCommentKind: 568 case Comment::HTMLStartTagCommentKind: 569 case Comment::HTMLEndTagCommentKind: 570 case Comment::VerbatimBlockLineCommentKind: 571 case Comment::FullCommentKind: 572 llvm_unreachable("AST node of this kind can't be a child of " 573 "a FullComment"); 574 } 575 } 576 577 // Sort params in order they are declared in the function prototype. 578 // Unresolved parameters are put at the end of the list in the same order 579 // they were seen in the comment. 580 std::stable_sort(Params.begin(), Params.end(), 581 ParamCommandCommentCompareIndex()); 582 583 bool FirstParagraphIsBrief = false; 584 if (Brief) 585 visit(Brief); 586 else if (FirstParagraph) { 587 Result << "<p class=\"para-brief\">"; 588 visitNonStandaloneParagraphComment(FirstParagraph); 589 Result << "</p>"; 590 FirstParagraphIsBrief = true; 591 } 592 593 for (unsigned i = 0, e = MiscBlocks.size(); i != e; ++i) { 594 const Comment *C = MiscBlocks[i]; 595 if (FirstParagraphIsBrief && C == FirstParagraph) 596 continue; 597 visit(C); 598 } 599 600 if (Params.size() != 0) { 601 Result << "<dl>"; 602 for (unsigned i = 0, e = Params.size(); i != e; ++i) 603 visit(Params[i]); 604 Result << "</dl>"; 605 } 606 607 if (Returns) 608 visit(Returns); 609 610 Result.flush(); 611} 612 613void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment( 614 const ParagraphComment *C) { 615 if (!C) 616 return; 617 618 for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); 619 I != E; ++I) { 620 visit(*I); 621 } 622} 623 624void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) { 625 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { 626 const char C = *I; 627 switch (C) { 628 case '&': 629 Result << "&"; 630 break; 631 case '<': 632 Result << "<"; 633 break; 634 case '>': 635 Result << ">"; 636 break; 637 case '"': 638 Result << """; 639 break; 640 case '\'': 641 Result << "'"; 642 break; 643 case '/': 644 Result << "/"; 645 break; 646 default: 647 Result << C; 648 break; 649 } 650 } 651} 652 653extern "C" { 654 655CXString clang_HTMLTagComment_getAsString(CXComment CXC) { 656 const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC); 657 if (!HTC) 658 return createCXString((const char *) 0); 659 660 SmallString<128> HTML; 661 CommentASTToHTMLConverter Converter(HTML); 662 Converter.visit(HTC); 663 return createCXString(HTML.str(), /* DupString = */ true); 664} 665 666CXString clang_FullComment_getAsHTML(CXComment CXC) { 667 const FullComment *FC = getASTNodeAs<FullComment>(CXC); 668 if (!FC) 669 return createCXString((const char *) 0); 670 671 SmallString<1024> HTML; 672 CommentASTToHTMLConverter Converter(HTML); 673 Converter.visit(FC); 674 return createCXString(HTML.str(), /* DupString = */ true); 675} 676 677} // end extern "C" 678 679