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