CXComment.cpp revision 3e63d332baf0d3b8a5c0b7c2dac2ae85615b1d47
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
129unsigned clang_InlineCommandComment_getNumArgs(CXComment CXC) {
130  const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC);
131  if (!ICC)
132    return 0;
133
134  return ICC->getNumArgs();
135}
136
137CXString clang_InlineCommandComment_getArgText(CXComment CXC,
138                                               unsigned ArgIdx) {
139  const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC);
140  if (!ICC || ArgIdx >= ICC->getNumArgs())
141    return createCXString((const char *) 0);
142
143  return createCXString(ICC->getArgText(ArgIdx), /*DupString=*/ false);
144}
145
146CXString clang_HTMLTagComment_getTagName(CXComment CXC) {
147  const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC);
148  if (!HTC)
149    return createCXString((const char *) 0);
150
151  return createCXString(HTC->getTagName(), /*DupString=*/ false);
152}
153
154unsigned clang_HTMLStartTagComment_isSelfClosing(CXComment CXC) {
155  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
156  if (!HST)
157    return false;
158
159  return HST->isSelfClosing();
160}
161
162unsigned clang_HTMLStartTag_getNumAttrs(CXComment CXC) {
163  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
164  if (!HST)
165    return 0;
166
167  return HST->getNumAttrs();
168}
169
170CXString clang_HTMLStartTag_getAttrName(CXComment CXC, unsigned AttrIdx) {
171  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
172  if (!HST || AttrIdx >= HST->getNumAttrs())
173    return createCXString((const char *) 0);
174
175  return createCXString(HST->getAttr(AttrIdx).Name, /*DupString=*/ false);
176}
177
178CXString clang_HTMLStartTag_getAttrValue(CXComment CXC, unsigned AttrIdx) {
179  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
180  if (!HST || AttrIdx >= HST->getNumAttrs())
181    return createCXString((const char *) 0);
182
183  return createCXString(HST->getAttr(AttrIdx).Value, /*DupString=*/ false);
184}
185
186CXString clang_BlockCommandComment_getCommandName(CXComment CXC) {
187  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
188  if (!BCC)
189    return createCXString((const char *) 0);
190
191  return createCXString(BCC->getCommandName(), /*DupString=*/ false);
192}
193
194unsigned clang_BlockCommandComment_getNumArgs(CXComment CXC) {
195  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
196  if (!BCC)
197    return 0;
198
199  return BCC->getNumArgs();
200}
201
202CXString clang_BlockCommandComment_getArgText(CXComment CXC,
203                                              unsigned ArgIdx) {
204  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
205  if (!BCC || ArgIdx >= BCC->getNumArgs())
206    return createCXString((const char *) 0);
207
208  return createCXString(BCC->getArgText(ArgIdx), /*DupString=*/ false);
209}
210
211CXComment clang_BlockCommandComment_getParagraph(CXComment CXC) {
212  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
213  if (!BCC)
214    return createCXComment(NULL);
215
216  return createCXComment(BCC->getParagraph());
217}
218
219CXString clang_ParamCommandComment_getParamName(CXComment CXC) {
220  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
221  if (!PCC)
222    return createCXString((const char *) 0);
223
224  return createCXString(PCC->getParamName(), /*DupString=*/ false);
225}
226
227unsigned clang_ParamCommandComment_isParamIndexValid(CXComment CXC) {
228  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
229  if (!PCC)
230    return false;
231
232  return PCC->isParamIndexValid();
233}
234
235unsigned clang_ParamCommandComment_getParamIndex(CXComment CXC) {
236  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
237  if (!PCC)
238    return ParamCommandComment::InvalidParamIndex;
239
240  return PCC->getParamIndex();
241}
242
243unsigned clang_ParamCommandComment_isDirectionExplicit(CXComment CXC) {
244  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
245  if (!PCC)
246    return false;
247
248  return PCC->isDirectionExplicit();
249}
250
251enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection(
252                                                            CXComment CXC) {
253  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
254  if (!PCC)
255    return CXCommentParamPassDirection_In;
256
257  switch (PCC->getDirection()) {
258  case ParamCommandComment::In:
259    return CXCommentParamPassDirection_In;
260
261  case ParamCommandComment::Out:
262    return CXCommentParamPassDirection_Out;
263
264  case ParamCommandComment::InOut:
265    return CXCommentParamPassDirection_InOut;
266  }
267  llvm_unreachable("unknown ParamCommandComment::PassDirection");
268}
269
270CXString clang_VerbatimBlockLineComment_getText(CXComment CXC) {
271  const VerbatimBlockLineComment *VBL =
272      getASTNodeAs<VerbatimBlockLineComment>(CXC);
273  if (!VBL)
274    return createCXString((const char *) 0);
275
276  return createCXString(VBL->getText(), /*DupString=*/ false);
277}
278
279CXString clang_VerbatimLineComment_getText(CXComment CXC) {
280  const VerbatimLineComment *VLC = getASTNodeAs<VerbatimLineComment>(CXC);
281  if (!VLC)
282    return createCXString((const char *) 0);
283
284  return createCXString(VLC->getText(), /*DupString=*/ false);
285}
286
287} // end extern "C"
288
289//===----------------------------------------------------------------------===//
290// Helpers for converting comment AST to HTML.
291//===----------------------------------------------------------------------===//
292
293namespace {
294
295class ParamCommandCommentCompareIndex {
296public:
297  bool operator()(const ParamCommandComment *LHS,
298                  const ParamCommandComment *RHS) const {
299    // To sort invalid (unresolved) parameters last, this comparison relies on
300    // invalid indices to be UINT_MAX.
301    return LHS->getParamIndex() < RHS->getParamIndex();
302  }
303};
304
305class CommentASTToHTMLConverter :
306    public ConstCommentVisitor<CommentASTToHTMLConverter> {
307public:
308  /// \param Str accumulator for HTML.
309  CommentASTToHTMLConverter(SmallVectorImpl<char> &Str) : Result(Str) { }
310
311  // Inline content.
312  void visitTextComment(const TextComment *C);
313  void visitInlineCommandComment(const InlineCommandComment *C);
314  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
315  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
316
317  // Block content.
318  void visitParagraphComment(const ParagraphComment *C);
319  void visitBlockCommandComment(const BlockCommandComment *C);
320  void visitParamCommandComment(const ParamCommandComment *C);
321  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
322  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
323  void visitVerbatimLineComment(const VerbatimLineComment *C);
324
325  void visitFullComment(const FullComment *C);
326
327  // Helpers.
328
329  /// Convert a paragraph that is not a block by itself (an argument to some
330  /// command).
331  void visitNonStandaloneParagraphComment(const ParagraphComment *C);
332
333  void appendToResultWithHTMLEscaping(StringRef S);
334
335private:
336  /// Output stream for HTML.
337  llvm::raw_svector_ostream Result;
338};
339} // end unnamed namespace
340
341void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
342  appendToResultWithHTMLEscaping(C->getText());
343}
344
345void CommentASTToHTMLConverter::visitInlineCommandComment(
346                                  const InlineCommandComment *C) {
347  StringRef CommandName = C->getCommandName();
348  bool HasArg0 = C->getNumArgs() > 0 && !C->getArgText(0).empty();
349  StringRef Arg0;
350  if (HasArg0)
351    Arg0 = C->getArgText(0);
352
353  if (CommandName == "b") {
354    if (!HasArg0)
355      return;
356    Result << "<b>" << Arg0 << "</b>";
357    return;
358  }
359  if (CommandName == "c" || CommandName == "p") {
360    if (!HasArg0)
361      return;
362    Result << "<tt>" << Arg0 << "</tt>";
363    return;
364  }
365  if (CommandName == "a" || CommandName == "e" || CommandName == "em") {
366    if (!HasArg0)
367      return;
368    Result << "<em>" << Arg0 << "</em>";
369    return;
370  }
371
372  // We don't recognize this command, so just print its arguments.
373  for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i)
374    Result << C->getArgText(i) << " ";
375}
376
377void CommentASTToHTMLConverter::visitHTMLStartTagComment(
378                                  const HTMLStartTagComment *C) {
379  Result << "<" << C->getTagName();
380
381  if (C->getNumAttrs() != 0) {
382    for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
383      Result << " ";
384      const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
385      Result << Attr.Name;
386      if (!Attr.Value.empty())
387        Result << "=\"" << Attr.Value << "\"";
388    }
389  }
390
391  if (!C->isSelfClosing())
392    Result << ">";
393  else
394    Result << "/>";
395}
396
397void CommentASTToHTMLConverter::visitHTMLEndTagComment(
398                                  const HTMLEndTagComment *C) {
399  Result << "</" << C->getTagName() << ">";
400}
401
402void CommentASTToHTMLConverter::visitParagraphComment(
403                                  const ParagraphComment *C) {
404  if (C->isWhitespace())
405    return;
406
407  Result << "<p>";
408  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
409       I != E; ++I) {
410    visit(*I);
411  }
412  Result << "</p>";
413}
414
415void CommentASTToHTMLConverter::visitBlockCommandComment(
416                                  const BlockCommandComment *C) {
417  StringRef CommandName = C->getCommandName();
418  if (CommandName == "brief" || CommandName == "short") {
419    Result << "<p class=\"para-brief\">";
420    visitNonStandaloneParagraphComment(C->getParagraph());
421    Result << "</p>";
422    return;
423  }
424  if (CommandName == "returns" || CommandName == "return") {
425    Result << "<p class=\"para-returns\">"
426              "<span class=\"word-returns\">Returns</span> ";
427    visitNonStandaloneParagraphComment(C->getParagraph());
428    Result << "</p>";
429    return;
430  }
431  // We don't know anything about this command.  Just render the paragraph.
432  visit(C->getParagraph());
433}
434
435void CommentASTToHTMLConverter::visitParamCommandComment(
436                                  const ParamCommandComment *C) {
437  if (C->isParamIndexValid()) {
438    Result << "<dt class=\"param-name-index-"
439           << C->getParamIndex()
440           << "\">";
441  } else
442    Result << "<dt class=\"param-name-index-invalid\">";
443
444  Result << C->getParamName() << "</dt>";
445
446  if (C->isParamIndexValid()) {
447    Result << "<dd class=\"param-descr-index-"
448           << C->getParamIndex()
449           << "\">";
450  } else
451    Result << "<dd class=\"param-descr-index-invalid\">";
452
453  visitNonStandaloneParagraphComment(C->getParagraph());
454  Result << "</dd>";
455}
456
457void CommentASTToHTMLConverter::visitVerbatimBlockComment(
458                                  const VerbatimBlockComment *C) {
459  unsigned NumLines = C->getNumLines();
460  if (NumLines == 0)
461    return;
462
463  Result << "<pre>";
464  for (unsigned i = 0; i != NumLines; ++i) {
465    appendToResultWithHTMLEscaping(C->getText(i));
466    if (i + 1 != NumLines)
467      Result << '\n';
468  }
469  Result << "</pre>";
470}
471
472void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
473                                  const VerbatimBlockLineComment *C) {
474  llvm_unreachable("should not see this AST node");
475}
476
477void CommentASTToHTMLConverter::visitVerbatimLineComment(
478                                  const VerbatimLineComment *C) {
479  Result << "<pre>";
480  appendToResultWithHTMLEscaping(C->getText());
481  Result << "</pre>";
482}
483
484void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
485  const BlockContentComment *Brief = NULL;
486  const ParagraphComment *FirstParagraph = NULL;
487  const BlockCommandComment *Returns = NULL;
488  SmallVector<const ParamCommandComment *, 8> Params;
489  SmallVector<const BlockContentComment *, 8> MiscBlocks;
490
491  // Extract various blocks into separate variables and vectors above.
492  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
493       I != E; ++I) {
494    const Comment *Child = *I;
495    if (!Child)
496      continue;
497    switch (Child->getCommentKind()) {
498    case Comment::NoCommentKind:
499      continue;
500
501    case Comment::ParagraphCommentKind: {
502      const ParagraphComment *PC = cast<ParagraphComment>(Child);
503      if (PC->isWhitespace())
504        break;
505      if (!FirstParagraph)
506        FirstParagraph = PC;
507
508      MiscBlocks.push_back(PC);
509      break;
510    }
511
512    case Comment::BlockCommandCommentKind: {
513      const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
514      StringRef CommandName = BCC->getCommandName();
515      if (!Brief && (CommandName == "brief" || CommandName == "short")) {
516        Brief = BCC;
517        break;
518      }
519      if (!Returns && (CommandName == "returns" || CommandName == "return")) {
520        Returns = BCC;
521        break;
522      }
523      MiscBlocks.push_back(BCC);
524      break;
525    }
526
527    case Comment::ParamCommandCommentKind: {
528      const ParamCommandComment *PCC = cast<ParamCommandComment>(Child);
529      if (!PCC->hasParamName())
530        break;
531
532      if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph())
533        break;
534
535      Params.push_back(PCC);
536      break;
537    }
538
539    case Comment::VerbatimBlockCommentKind:
540    case Comment::VerbatimLineCommentKind:
541      MiscBlocks.push_back(cast<BlockCommandComment>(Child));
542      break;
543
544    case Comment::TextCommentKind:
545    case Comment::InlineCommandCommentKind:
546    case Comment::HTMLStartTagCommentKind:
547    case Comment::HTMLEndTagCommentKind:
548    case Comment::VerbatimBlockLineCommentKind:
549    case Comment::FullCommentKind:
550      llvm_unreachable("AST node of this kind can't be a child of "
551                       "a FullComment");
552    }
553  }
554
555  // Sort params in order they are declared in the function prototype.
556  // Unresolved parameters are put at the end of the list in the same order
557  // they were seen in the comment.
558  std::stable_sort(Params.begin(), Params.end(),
559                   ParamCommandCommentCompareIndex());
560
561  bool FirstParagraphIsBrief = false;
562  if (Brief)
563    visit(Brief);
564  else if (FirstParagraph) {
565    Result << "<p class=\"para-brief\">";
566    visitNonStandaloneParagraphComment(FirstParagraph);
567    Result << "</p>";
568    FirstParagraphIsBrief = true;
569  }
570
571  for (unsigned i = 0, e = MiscBlocks.size(); i != e; ++i) {
572    const Comment *C = MiscBlocks[i];
573    if (FirstParagraphIsBrief && C == FirstParagraph)
574      continue;
575    visit(C);
576  }
577
578  if (Params.size() != 0) {
579    Result << "<dl>";
580    for (unsigned i = 0, e = Params.size(); i != e; ++i)
581      visit(Params[i]);
582    Result << "</dl>";
583  }
584
585  if (Returns)
586    visit(Returns);
587
588  Result.flush();
589}
590
591void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
592                                  const ParagraphComment *C) {
593  if (!C)
594    return;
595
596  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
597       I != E; ++I) {
598    visit(*I);
599  }
600}
601
602void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
603  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
604    const char C = *I;
605    switch (C) {
606      case '&':
607        Result << "&amp;";
608        break;
609      case '<':
610        Result << "&lt;";
611        break;
612      case '>':
613        Result << "&gt;";
614        break;
615      case '"':
616        Result << "&quot;";
617        break;
618      case '\'':
619        Result << "&#39;";
620        break;
621      case '/':
622        Result << "&#47;";
623        break;
624      default:
625        Result << C;
626        break;
627    }
628  }
629}
630
631extern "C" {
632
633CXString clang_HTMLTagComment_getAsString(CXComment CXC) {
634  const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC);
635  if (!HTC)
636    return createCXString((const char *) 0);
637
638  SmallString<128> HTML;
639  CommentASTToHTMLConverter Converter(HTML);
640  Converter.visit(HTC);
641  return createCXString(HTML.str(), /* DupString = */ true);
642}
643
644CXString clang_FullComment_getAsHTML(CXComment CXC) {
645  const FullComment *FC = getASTNodeAs<FullComment>(CXC);
646  if (!FC)
647    return createCXString((const char *) 0);
648
649  SmallString<1024> HTML;
650  CommentASTToHTMLConverter Converter(HTML);
651  Converter.visit(FC);
652  return createCXString(HTML.str(), /* DupString = */ true);
653}
654
655} // end extern "C"
656
657