CXComment.cpp revision b740316a122b5ceaaa7cf50557b1b39af5fbbf5f
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 || !PCC->isParamIndexValid())
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  /// This comparison will sort parameters with valid index by index and
320  /// invalid (unresolved) parameters last.
321  bool operator()(const ParamCommandComment *LHS,
322                  const ParamCommandComment *RHS) const {
323    unsigned LHSIndex = UINT_MAX;
324    unsigned RHSIndex = UINT_MAX;
325    if (LHS->isParamIndexValid())
326      LHSIndex = LHS->getParamIndex();
327    if (RHS->isParamIndexValid())
328      RHSIndex = RHS->getParamIndex();
329
330    return LHSIndex < RHSIndex;
331  }
332};
333
334class CommentASTToHTMLConverter :
335    public ConstCommentVisitor<CommentASTToHTMLConverter> {
336public:
337  /// \param Str accumulator for HTML.
338  CommentASTToHTMLConverter(SmallVectorImpl<char> &Str) : Result(Str) { }
339
340  // Inline content.
341  void visitTextComment(const TextComment *C);
342  void visitInlineCommandComment(const InlineCommandComment *C);
343  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
344  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
345
346  // Block content.
347  void visitParagraphComment(const ParagraphComment *C);
348  void visitBlockCommandComment(const BlockCommandComment *C);
349  void visitParamCommandComment(const ParamCommandComment *C);
350  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
351  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
352  void visitVerbatimLineComment(const VerbatimLineComment *C);
353
354  void visitFullComment(const FullComment *C);
355
356  // Helpers.
357
358  /// Convert a paragraph that is not a block by itself (an argument to some
359  /// command).
360  void visitNonStandaloneParagraphComment(const ParagraphComment *C);
361
362  void appendToResultWithHTMLEscaping(StringRef S);
363
364private:
365  /// Output stream for HTML.
366  llvm::raw_svector_ostream Result;
367};
368} // end unnamed namespace
369
370void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
371  appendToResultWithHTMLEscaping(C->getText());
372}
373
374void CommentASTToHTMLConverter::visitInlineCommandComment(
375                                  const InlineCommandComment *C) {
376  // Nothing to render if no arguments supplied.
377  if (C->getNumArgs() == 0)
378    return;
379
380  // Nothing to render if argument is empty.
381  StringRef Arg0 = C->getArgText(0);
382  if (Arg0.empty())
383    return;
384
385  switch (C->getRenderKind()) {
386  case InlineCommandComment::RenderNormal:
387    for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i)
388      Result << C->getArgText(i) << " ";
389    return;
390
391  case InlineCommandComment::RenderBold:
392    assert(C->getNumArgs() == 1);
393    Result << "<b>" << Arg0 << "</b>";
394    return;
395  case InlineCommandComment::RenderMonospaced:
396    assert(C->getNumArgs() == 1);
397    Result << "<tt>" << Arg0 << "</tt>";
398    return;
399  case InlineCommandComment::RenderEmphasized:
400    assert(C->getNumArgs() == 1);
401    Result << "<em>" << Arg0 << "</em>";
402    return;
403  }
404}
405
406void CommentASTToHTMLConverter::visitHTMLStartTagComment(
407                                  const HTMLStartTagComment *C) {
408  Result << "<" << C->getTagName();
409
410  if (C->getNumAttrs() != 0) {
411    for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
412      Result << " ";
413      const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
414      Result << Attr.Name;
415      if (!Attr.Value.empty())
416        Result << "=\"" << Attr.Value << "\"";
417    }
418  }
419
420  if (!C->isSelfClosing())
421    Result << ">";
422  else
423    Result << "/>";
424}
425
426void CommentASTToHTMLConverter::visitHTMLEndTagComment(
427                                  const HTMLEndTagComment *C) {
428  Result << "</" << C->getTagName() << ">";
429}
430
431void CommentASTToHTMLConverter::visitParagraphComment(
432                                  const ParagraphComment *C) {
433  if (C->isWhitespace())
434    return;
435
436  Result << "<p>";
437  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
438       I != E; ++I) {
439    visit(*I);
440  }
441  Result << "</p>";
442}
443
444void CommentASTToHTMLConverter::visitBlockCommandComment(
445                                  const BlockCommandComment *C) {
446  StringRef CommandName = C->getCommandName();
447  if (CommandName == "brief" || CommandName == "short") {
448    Result << "<p class=\"para-brief\">";
449    visitNonStandaloneParagraphComment(C->getParagraph());
450    Result << "</p>";
451    return;
452  }
453  if (CommandName == "returns" || CommandName == "return" ||
454      CommandName == "result") {
455    Result << "<p class=\"para-returns\">"
456              "<span class=\"word-returns\">Returns</span> ";
457    visitNonStandaloneParagraphComment(C->getParagraph());
458    Result << "</p>";
459    return;
460  }
461  // We don't know anything about this command.  Just render the paragraph.
462  visit(C->getParagraph());
463}
464
465void CommentASTToHTMLConverter::visitParamCommandComment(
466                                  const ParamCommandComment *C) {
467  if (C->isParamIndexValid()) {
468    Result << "<dt class=\"param-name-index-"
469           << C->getParamIndex()
470           << "\">";
471  } else
472    Result << "<dt class=\"param-name-index-invalid\">";
473
474  Result << C->getParamName() << "</dt>";
475
476  if (C->isParamIndexValid()) {
477    Result << "<dd class=\"param-descr-index-"
478           << C->getParamIndex()
479           << "\">";
480  } else
481    Result << "<dd class=\"param-descr-index-invalid\">";
482
483  visitNonStandaloneParagraphComment(C->getParagraph());
484  Result << "</dd>";
485}
486
487void CommentASTToHTMLConverter::visitVerbatimBlockComment(
488                                  const VerbatimBlockComment *C) {
489  unsigned NumLines = C->getNumLines();
490  if (NumLines == 0)
491    return;
492
493  Result << "<pre>";
494  for (unsigned i = 0; i != NumLines; ++i) {
495    appendToResultWithHTMLEscaping(C->getText(i));
496    if (i + 1 != NumLines)
497      Result << '\n';
498  }
499  Result << "</pre>";
500}
501
502void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
503                                  const VerbatimBlockLineComment *C) {
504  llvm_unreachable("should not see this AST node");
505}
506
507void CommentASTToHTMLConverter::visitVerbatimLineComment(
508                                  const VerbatimLineComment *C) {
509  Result << "<pre>";
510  appendToResultWithHTMLEscaping(C->getText());
511  Result << "</pre>";
512}
513
514void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
515  const BlockContentComment *Brief = NULL;
516  const ParagraphComment *FirstParagraph = NULL;
517  const BlockCommandComment *Returns = NULL;
518  SmallVector<const ParamCommandComment *, 8> Params;
519  SmallVector<const BlockContentComment *, 8> MiscBlocks;
520
521  // Extract various blocks into separate variables and vectors above.
522  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
523       I != E; ++I) {
524    const Comment *Child = *I;
525    if (!Child)
526      continue;
527    switch (Child->getCommentKind()) {
528    case Comment::NoCommentKind:
529      continue;
530
531    case Comment::ParagraphCommentKind: {
532      const ParagraphComment *PC = cast<ParagraphComment>(Child);
533      if (PC->isWhitespace())
534        break;
535      if (!FirstParagraph)
536        FirstParagraph = PC;
537
538      MiscBlocks.push_back(PC);
539      break;
540    }
541
542    case Comment::BlockCommandCommentKind: {
543      const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
544      StringRef CommandName = BCC->getCommandName();
545      if (!Brief && (CommandName == "brief" || CommandName == "short")) {
546        Brief = BCC;
547        break;
548      }
549      if (!Returns && (CommandName == "returns" || CommandName == "return")) {
550        Returns = BCC;
551        break;
552      }
553      MiscBlocks.push_back(BCC);
554      break;
555    }
556
557    case Comment::ParamCommandCommentKind: {
558      const ParamCommandComment *PCC = cast<ParamCommandComment>(Child);
559      if (!PCC->hasParamName())
560        break;
561
562      if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph())
563        break;
564
565      Params.push_back(PCC);
566      break;
567    }
568
569    case Comment::VerbatimBlockCommentKind:
570    case Comment::VerbatimLineCommentKind:
571      MiscBlocks.push_back(cast<BlockCommandComment>(Child));
572      break;
573
574    case Comment::TextCommentKind:
575    case Comment::InlineCommandCommentKind:
576    case Comment::HTMLStartTagCommentKind:
577    case Comment::HTMLEndTagCommentKind:
578    case Comment::VerbatimBlockLineCommentKind:
579    case Comment::FullCommentKind:
580      llvm_unreachable("AST node of this kind can't be a child of "
581                       "a FullComment");
582    }
583  }
584
585  // Sort params in order they are declared in the function prototype.
586  // Unresolved parameters are put at the end of the list in the same order
587  // they were seen in the comment.
588  std::stable_sort(Params.begin(), Params.end(),
589                   ParamCommandCommentCompareIndex());
590
591  bool FirstParagraphIsBrief = false;
592  if (Brief)
593    visit(Brief);
594  else if (FirstParagraph) {
595    Result << "<p class=\"para-brief\">";
596    visitNonStandaloneParagraphComment(FirstParagraph);
597    Result << "</p>";
598    FirstParagraphIsBrief = true;
599  }
600
601  for (unsigned i = 0, e = MiscBlocks.size(); i != e; ++i) {
602    const Comment *C = MiscBlocks[i];
603    if (FirstParagraphIsBrief && C == FirstParagraph)
604      continue;
605    visit(C);
606  }
607
608  if (Params.size() != 0) {
609    Result << "<dl>";
610    for (unsigned i = 0, e = Params.size(); i != e; ++i)
611      visit(Params[i]);
612    Result << "</dl>";
613  }
614
615  if (Returns)
616    visit(Returns);
617
618  Result.flush();
619}
620
621void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
622                                  const ParagraphComment *C) {
623  if (!C)
624    return;
625
626  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
627       I != E; ++I) {
628    visit(*I);
629  }
630}
631
632void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
633  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
634    const char C = *I;
635    switch (C) {
636      case '&':
637        Result << "&amp;";
638        break;
639      case '<':
640        Result << "&lt;";
641        break;
642      case '>':
643        Result << "&gt;";
644        break;
645      case '"':
646        Result << "&quot;";
647        break;
648      case '\'':
649        Result << "&#39;";
650        break;
651      case '/':
652        Result << "&#47;";
653        break;
654      default:
655        Result << C;
656        break;
657    }
658  }
659}
660
661extern "C" {
662
663CXString clang_HTMLTagComment_getAsString(CXComment CXC) {
664  const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC);
665  if (!HTC)
666    return createCXString((const char *) 0);
667
668  SmallString<128> HTML;
669  CommentASTToHTMLConverter Converter(HTML);
670  Converter.visit(HTC);
671  return createCXString(HTML.str(), /* DupString = */ true);
672}
673
674CXString clang_FullComment_getAsHTML(CXComment CXC) {
675  const FullComment *FC = getASTNodeAs<FullComment>(CXC);
676  if (!FC)
677    return createCXString((const char *) 0);
678
679  SmallString<1024> HTML;
680  CommentASTToHTMLConverter Converter(HTML);
681  Converter.visit(FC);
682  return createCXString(HTML.str(), /* DupString = */ true);
683}
684
685} // end extern "C"
686
687