1//===--- CommentToXML.cpp - Convert comments to XML representation --------===//
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#include "clang/Index/CommentToXML.h"
11#include "SimpleFormatContext.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/Attr.h"
14#include "clang/AST/Comment.h"
15#include "clang/AST/CommentVisitor.h"
16#include "clang/Format/Format.h"
17#include "clang/Index/USRGeneration.h"
18#include "clang/Lex/Lexer.h"
19#include "llvm/ADT/StringExtras.h"
20#include "llvm/ADT/TinyPtrVector.h"
21#include "llvm/Support/raw_ostream.h"
22
23using namespace clang;
24using namespace clang::comments;
25using namespace clang::index;
26
27namespace {
28
29/// This comparison will sort parameters with valid index by index, then vararg
30/// parameters, and invalid (unresolved) parameters last.
31class ParamCommandCommentCompareIndex {
32public:
33  bool operator()(const ParamCommandComment *LHS,
34                  const ParamCommandComment *RHS) const {
35    unsigned LHSIndex = UINT_MAX;
36    unsigned RHSIndex = UINT_MAX;
37
38    if (LHS->isParamIndexValid()) {
39      if (LHS->isVarArgParam())
40        LHSIndex = UINT_MAX - 1;
41      else
42        LHSIndex = LHS->getParamIndex();
43    }
44    if (RHS->isParamIndexValid()) {
45      if (RHS->isVarArgParam())
46        RHSIndex = UINT_MAX - 1;
47      else
48        RHSIndex = RHS->getParamIndex();
49    }
50    return LHSIndex < RHSIndex;
51  }
52};
53
54/// This comparison will sort template parameters in the following order:
55/// \li real template parameters (depth = 1) in index order;
56/// \li all other names (depth > 1);
57/// \li unresolved names.
58class TParamCommandCommentComparePosition {
59public:
60  bool operator()(const TParamCommandComment *LHS,
61                  const TParamCommandComment *RHS) const {
62    // Sort unresolved names last.
63    if (!LHS->isPositionValid())
64      return false;
65    if (!RHS->isPositionValid())
66      return true;
67
68    if (LHS->getDepth() > 1)
69      return false;
70    if (RHS->getDepth() > 1)
71      return true;
72
73    // Sort template parameters in index order.
74    if (LHS->getDepth() == 1 && RHS->getDepth() == 1)
75      return LHS->getIndex(0) < RHS->getIndex(0);
76
77    // Leave all other names in source order.
78    return true;
79  }
80};
81
82/// Separate parts of a FullComment.
83struct FullCommentParts {
84  /// Take a full comment apart and initialize members accordingly.
85  FullCommentParts(const FullComment *C,
86                   const CommandTraits &Traits);
87
88  const BlockContentComment *Brief;
89  const BlockContentComment *Headerfile;
90  const ParagraphComment *FirstParagraph;
91  SmallVector<const BlockCommandComment *, 4> Returns;
92  SmallVector<const ParamCommandComment *, 8> Params;
93  SmallVector<const TParamCommandComment *, 4> TParams;
94  llvm::TinyPtrVector<const BlockCommandComment *> Exceptions;
95  SmallVector<const BlockContentComment *, 8> MiscBlocks;
96};
97
98FullCommentParts::FullCommentParts(const FullComment *C,
99                                   const CommandTraits &Traits) :
100    Brief(nullptr), Headerfile(nullptr), FirstParagraph(nullptr) {
101  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
102       I != E; ++I) {
103    const Comment *Child = *I;
104    if (!Child)
105      continue;
106    switch (Child->getCommentKind()) {
107    case Comment::NoCommentKind:
108      continue;
109
110    case Comment::ParagraphCommentKind: {
111      const ParagraphComment *PC = cast<ParagraphComment>(Child);
112      if (PC->isWhitespace())
113        break;
114      if (!FirstParagraph)
115        FirstParagraph = PC;
116
117      MiscBlocks.push_back(PC);
118      break;
119    }
120
121    case Comment::BlockCommandCommentKind: {
122      const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
123      const CommandInfo *Info = Traits.getCommandInfo(BCC->getCommandID());
124      if (!Brief && Info->IsBriefCommand) {
125        Brief = BCC;
126        break;
127      }
128      if (!Headerfile && Info->IsHeaderfileCommand) {
129        Headerfile = BCC;
130        break;
131      }
132      if (Info->IsReturnsCommand) {
133        Returns.push_back(BCC);
134        break;
135      }
136      if (Info->IsThrowsCommand) {
137        Exceptions.push_back(BCC);
138        break;
139      }
140      MiscBlocks.push_back(BCC);
141      break;
142    }
143
144    case Comment::ParamCommandCommentKind: {
145      const ParamCommandComment *PCC = cast<ParamCommandComment>(Child);
146      if (!PCC->hasParamName())
147        break;
148
149      if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph())
150        break;
151
152      Params.push_back(PCC);
153      break;
154    }
155
156    case Comment::TParamCommandCommentKind: {
157      const TParamCommandComment *TPCC = cast<TParamCommandComment>(Child);
158      if (!TPCC->hasParamName())
159        break;
160
161      if (!TPCC->hasNonWhitespaceParagraph())
162        break;
163
164      TParams.push_back(TPCC);
165      break;
166    }
167
168    case Comment::VerbatimBlockCommentKind:
169      MiscBlocks.push_back(cast<BlockCommandComment>(Child));
170      break;
171
172    case Comment::VerbatimLineCommentKind: {
173      const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Child);
174      const CommandInfo *Info = Traits.getCommandInfo(VLC->getCommandID());
175      if (!Info->IsDeclarationCommand)
176        MiscBlocks.push_back(VLC);
177      break;
178    }
179
180    case Comment::TextCommentKind:
181    case Comment::InlineCommandCommentKind:
182    case Comment::HTMLStartTagCommentKind:
183    case Comment::HTMLEndTagCommentKind:
184    case Comment::VerbatimBlockLineCommentKind:
185    case Comment::FullCommentKind:
186      llvm_unreachable("AST node of this kind can't be a child of "
187                       "a FullComment");
188    }
189  }
190
191  // Sort params in order they are declared in the function prototype.
192  // Unresolved parameters are put at the end of the list in the same order
193  // they were seen in the comment.
194  std::stable_sort(Params.begin(), Params.end(),
195                   ParamCommandCommentCompareIndex());
196
197  std::stable_sort(TParams.begin(), TParams.end(),
198                   TParamCommandCommentComparePosition());
199}
200
201void printHTMLStartTagComment(const HTMLStartTagComment *C,
202                              llvm::raw_svector_ostream &Result) {
203  Result << "<" << C->getTagName();
204
205  if (C->getNumAttrs() != 0) {
206    for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
207      Result << " ";
208      const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
209      Result << Attr.Name;
210      if (!Attr.Value.empty())
211        Result << "=\"" << Attr.Value << "\"";
212    }
213  }
214
215  if (!C->isSelfClosing())
216    Result << ">";
217  else
218    Result << "/>";
219}
220
221class CommentASTToHTMLConverter :
222    public ConstCommentVisitor<CommentASTToHTMLConverter> {
223public:
224  /// \param Str accumulator for HTML.
225  CommentASTToHTMLConverter(const FullComment *FC,
226                            SmallVectorImpl<char> &Str,
227                            const CommandTraits &Traits) :
228      FC(FC), Result(Str), Traits(Traits)
229  { }
230
231  // Inline content.
232  void visitTextComment(const TextComment *C);
233  void visitInlineCommandComment(const InlineCommandComment *C);
234  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
235  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
236
237  // Block content.
238  void visitParagraphComment(const ParagraphComment *C);
239  void visitBlockCommandComment(const BlockCommandComment *C);
240  void visitParamCommandComment(const ParamCommandComment *C);
241  void visitTParamCommandComment(const TParamCommandComment *C);
242  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
243  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
244  void visitVerbatimLineComment(const VerbatimLineComment *C);
245
246  void visitFullComment(const FullComment *C);
247
248  // Helpers.
249
250  /// Convert a paragraph that is not a block by itself (an argument to some
251  /// command).
252  void visitNonStandaloneParagraphComment(const ParagraphComment *C);
253
254  void appendToResultWithHTMLEscaping(StringRef S);
255
256private:
257  const FullComment *FC;
258  /// Output stream for HTML.
259  llvm::raw_svector_ostream Result;
260
261  const CommandTraits &Traits;
262};
263} // end unnamed namespace
264
265void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
266  appendToResultWithHTMLEscaping(C->getText());
267}
268
269void CommentASTToHTMLConverter::visitInlineCommandComment(
270                                  const InlineCommandComment *C) {
271  // Nothing to render if no arguments supplied.
272  if (C->getNumArgs() == 0)
273    return;
274
275  // Nothing to render if argument is empty.
276  StringRef Arg0 = C->getArgText(0);
277  if (Arg0.empty())
278    return;
279
280  switch (C->getRenderKind()) {
281  case InlineCommandComment::RenderNormal:
282    for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
283      appendToResultWithHTMLEscaping(C->getArgText(i));
284      Result << " ";
285    }
286    return;
287
288  case InlineCommandComment::RenderBold:
289    assert(C->getNumArgs() == 1);
290    Result << "<b>";
291    appendToResultWithHTMLEscaping(Arg0);
292    Result << "</b>";
293    return;
294  case InlineCommandComment::RenderMonospaced:
295    assert(C->getNumArgs() == 1);
296    Result << "<tt>";
297    appendToResultWithHTMLEscaping(Arg0);
298    Result<< "</tt>";
299    return;
300  case InlineCommandComment::RenderEmphasized:
301    assert(C->getNumArgs() == 1);
302    Result << "<em>";
303    appendToResultWithHTMLEscaping(Arg0);
304    Result << "</em>";
305    return;
306  }
307}
308
309void CommentASTToHTMLConverter::visitHTMLStartTagComment(
310                                  const HTMLStartTagComment *C) {
311  printHTMLStartTagComment(C, Result);
312}
313
314void CommentASTToHTMLConverter::visitHTMLEndTagComment(
315                                  const HTMLEndTagComment *C) {
316  Result << "</" << C->getTagName() << ">";
317}
318
319void CommentASTToHTMLConverter::visitParagraphComment(
320                                  const ParagraphComment *C) {
321  if (C->isWhitespace())
322    return;
323
324  Result << "<p>";
325  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
326       I != E; ++I) {
327    visit(*I);
328  }
329  Result << "</p>";
330}
331
332void CommentASTToHTMLConverter::visitBlockCommandComment(
333                                  const BlockCommandComment *C) {
334  const CommandInfo *Info = Traits.getCommandInfo(C->getCommandID());
335  if (Info->IsBriefCommand) {
336    Result << "<p class=\"para-brief\">";
337    visitNonStandaloneParagraphComment(C->getParagraph());
338    Result << "</p>";
339    return;
340  }
341  if (Info->IsReturnsCommand) {
342    Result << "<p class=\"para-returns\">"
343              "<span class=\"word-returns\">Returns</span> ";
344    visitNonStandaloneParagraphComment(C->getParagraph());
345    Result << "</p>";
346    return;
347  }
348  // We don't know anything about this command.  Just render the paragraph.
349  visit(C->getParagraph());
350}
351
352void CommentASTToHTMLConverter::visitParamCommandComment(
353                                  const ParamCommandComment *C) {
354  if (C->isParamIndexValid()) {
355    if (C->isVarArgParam()) {
356      Result << "<dt class=\"param-name-index-vararg\">";
357      appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
358    } else {
359      Result << "<dt class=\"param-name-index-"
360             << C->getParamIndex()
361             << "\">";
362      appendToResultWithHTMLEscaping(C->getParamName(FC));
363    }
364  } else {
365    Result << "<dt class=\"param-name-index-invalid\">";
366    appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
367  }
368  Result << "</dt>";
369
370  if (C->isParamIndexValid()) {
371    if (C->isVarArgParam())
372      Result << "<dd class=\"param-descr-index-vararg\">";
373    else
374      Result << "<dd class=\"param-descr-index-"
375             << C->getParamIndex()
376             << "\">";
377  } else
378    Result << "<dd class=\"param-descr-index-invalid\">";
379
380  visitNonStandaloneParagraphComment(C->getParagraph());
381  Result << "</dd>";
382}
383
384void CommentASTToHTMLConverter::visitTParamCommandComment(
385                                  const TParamCommandComment *C) {
386  if (C->isPositionValid()) {
387    if (C->getDepth() == 1)
388      Result << "<dt class=\"tparam-name-index-"
389             << C->getIndex(0)
390             << "\">";
391    else
392      Result << "<dt class=\"tparam-name-index-other\">";
393    appendToResultWithHTMLEscaping(C->getParamName(FC));
394  } else {
395    Result << "<dt class=\"tparam-name-index-invalid\">";
396    appendToResultWithHTMLEscaping(C->getParamNameAsWritten());
397  }
398
399  Result << "</dt>";
400
401  if (C->isPositionValid()) {
402    if (C->getDepth() == 1)
403      Result << "<dd class=\"tparam-descr-index-"
404             << C->getIndex(0)
405             << "\">";
406    else
407      Result << "<dd class=\"tparam-descr-index-other\">";
408  } else
409    Result << "<dd class=\"tparam-descr-index-invalid\">";
410
411  visitNonStandaloneParagraphComment(C->getParagraph());
412  Result << "</dd>";
413}
414
415void CommentASTToHTMLConverter::visitVerbatimBlockComment(
416                                  const VerbatimBlockComment *C) {
417  unsigned NumLines = C->getNumLines();
418  if (NumLines == 0)
419    return;
420
421  Result << "<pre>";
422  for (unsigned i = 0; i != NumLines; ++i) {
423    appendToResultWithHTMLEscaping(C->getText(i));
424    if (i + 1 != NumLines)
425      Result << '\n';
426  }
427  Result << "</pre>";
428}
429
430void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
431                                  const VerbatimBlockLineComment *C) {
432  llvm_unreachable("should not see this AST node");
433}
434
435void CommentASTToHTMLConverter::visitVerbatimLineComment(
436                                  const VerbatimLineComment *C) {
437  Result << "<pre>";
438  appendToResultWithHTMLEscaping(C->getText());
439  Result << "</pre>";
440}
441
442void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
443  FullCommentParts Parts(C, Traits);
444
445  bool FirstParagraphIsBrief = false;
446  if (Parts.Headerfile)
447    visit(Parts.Headerfile);
448  if (Parts.Brief)
449    visit(Parts.Brief);
450  else if (Parts.FirstParagraph) {
451    Result << "<p class=\"para-brief\">";
452    visitNonStandaloneParagraphComment(Parts.FirstParagraph);
453    Result << "</p>";
454    FirstParagraphIsBrief = true;
455  }
456
457  for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
458    const Comment *C = Parts.MiscBlocks[i];
459    if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
460      continue;
461    visit(C);
462  }
463
464  if (Parts.TParams.size() != 0) {
465    Result << "<dl>";
466    for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
467      visit(Parts.TParams[i]);
468    Result << "</dl>";
469  }
470
471  if (Parts.Params.size() != 0) {
472    Result << "<dl>";
473    for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
474      visit(Parts.Params[i]);
475    Result << "</dl>";
476  }
477
478  if (Parts.Returns.size() != 0) {
479    Result << "<div class=\"result-discussion\">";
480    for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
481      visit(Parts.Returns[i]);
482    Result << "</div>";
483  }
484
485  Result.flush();
486}
487
488void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
489                                  const ParagraphComment *C) {
490  if (!C)
491    return;
492
493  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
494       I != E; ++I) {
495    visit(*I);
496  }
497}
498
499void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
500  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
501    const char C = *I;
502    switch (C) {
503    case '&':
504      Result << "&amp;";
505      break;
506    case '<':
507      Result << "&lt;";
508      break;
509    case '>':
510      Result << "&gt;";
511      break;
512    case '"':
513      Result << "&quot;";
514      break;
515    case '\'':
516      Result << "&#39;";
517      break;
518    case '/':
519      Result << "&#47;";
520      break;
521    default:
522      Result << C;
523      break;
524    }
525  }
526}
527
528namespace {
529class CommentASTToXMLConverter :
530    public ConstCommentVisitor<CommentASTToXMLConverter> {
531public:
532  /// \param Str accumulator for XML.
533  CommentASTToXMLConverter(const FullComment *FC,
534                           SmallVectorImpl<char> &Str,
535                           const CommandTraits &Traits,
536                           const SourceManager &SM,
537                           SimpleFormatContext &SFC,
538                           unsigned FUID) :
539      FC(FC), Result(Str), Traits(Traits), SM(SM),
540      FormatRewriterContext(SFC),
541      FormatInMemoryUniqueId(FUID) { }
542
543  // Inline content.
544  void visitTextComment(const TextComment *C);
545  void visitInlineCommandComment(const InlineCommandComment *C);
546  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
547  void visitHTMLEndTagComment(const HTMLEndTagComment *C);
548
549  // Block content.
550  void visitParagraphComment(const ParagraphComment *C);
551
552  void appendParagraphCommentWithKind(const ParagraphComment *C,
553                                      StringRef Kind);
554
555  void visitBlockCommandComment(const BlockCommandComment *C);
556  void visitParamCommandComment(const ParamCommandComment *C);
557  void visitTParamCommandComment(const TParamCommandComment *C);
558  void visitVerbatimBlockComment(const VerbatimBlockComment *C);
559  void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
560  void visitVerbatimLineComment(const VerbatimLineComment *C);
561
562  void visitFullComment(const FullComment *C);
563
564  // Helpers.
565  void appendToResultWithXMLEscaping(StringRef S);
566  void appendToResultWithCDATAEscaping(StringRef S);
567
568  void formatTextOfDeclaration(const DeclInfo *DI,
569                               SmallString<128> &Declaration);
570
571private:
572  const FullComment *FC;
573
574  /// Output stream for XML.
575  llvm::raw_svector_ostream Result;
576
577  const CommandTraits &Traits;
578  const SourceManager &SM;
579  SimpleFormatContext &FormatRewriterContext;
580  unsigned FormatInMemoryUniqueId;
581};
582
583void getSourceTextOfDeclaration(const DeclInfo *ThisDecl,
584                                SmallVectorImpl<char> &Str) {
585  ASTContext &Context = ThisDecl->CurrentDecl->getASTContext();
586  const LangOptions &LangOpts = Context.getLangOpts();
587  llvm::raw_svector_ostream OS(Str);
588  PrintingPolicy PPolicy(LangOpts);
589  PPolicy.PolishForDeclaration = true;
590  PPolicy.TerseOutput = true;
591  ThisDecl->CurrentDecl->print(OS, PPolicy,
592                               /*Indentation*/0, /*PrintInstantiation*/false);
593}
594
595void CommentASTToXMLConverter::formatTextOfDeclaration(
596    const DeclInfo *DI, SmallString<128> &Declaration) {
597  // FIXME. formatting API expects null terminated input string.
598  // There might be more efficient way of doing this.
599  std::string StringDecl = Declaration.str();
600
601  // Formatter specific code.
602  // Form a unique in memory buffer name.
603  SmallString<128> filename;
604  filename += "xmldecl";
605  filename += llvm::utostr(FormatInMemoryUniqueId);
606  filename += ".xd";
607  FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl);
608  SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID)
609      .getLocWithOffset(0);
610  unsigned Length = Declaration.size();
611
612  std::vector<CharSourceRange> Ranges(
613      1, CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length)));
614  ASTContext &Context = DI->CurrentDecl->getASTContext();
615  const LangOptions &LangOpts = Context.getLangOpts();
616  Lexer Lex(ID, FormatRewriterContext.Sources.getBuffer(ID),
617            FormatRewriterContext.Sources, LangOpts);
618  tooling::Replacements Replace = reformat(
619      format::getLLVMStyle(), Lex, FormatRewriterContext.Sources, Ranges);
620  applyAllReplacements(Replace, FormatRewriterContext.Rewrite);
621  Declaration = FormatRewriterContext.getRewrittenText(ID);
622}
623
624} // end unnamed namespace
625
626void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
627  appendToResultWithXMLEscaping(C->getText());
628}
629
630void CommentASTToXMLConverter::visitInlineCommandComment(
631    const InlineCommandComment *C) {
632  // Nothing to render if no arguments supplied.
633  if (C->getNumArgs() == 0)
634    return;
635
636  // Nothing to render if argument is empty.
637  StringRef Arg0 = C->getArgText(0);
638  if (Arg0.empty())
639    return;
640
641  switch (C->getRenderKind()) {
642  case InlineCommandComment::RenderNormal:
643    for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
644      appendToResultWithXMLEscaping(C->getArgText(i));
645      Result << " ";
646    }
647    return;
648  case InlineCommandComment::RenderBold:
649    assert(C->getNumArgs() == 1);
650    Result << "<bold>";
651    appendToResultWithXMLEscaping(Arg0);
652    Result << "</bold>";
653    return;
654  case InlineCommandComment::RenderMonospaced:
655    assert(C->getNumArgs() == 1);
656    Result << "<monospaced>";
657    appendToResultWithXMLEscaping(Arg0);
658    Result << "</monospaced>";
659    return;
660  case InlineCommandComment::RenderEmphasized:
661    assert(C->getNumArgs() == 1);
662    Result << "<emphasized>";
663    appendToResultWithXMLEscaping(Arg0);
664    Result << "</emphasized>";
665    return;
666  }
667}
668
669void CommentASTToXMLConverter::visitHTMLStartTagComment(
670    const HTMLStartTagComment *C) {
671  Result << "<rawHTML";
672  if (C->isMalformed())
673    Result << " isMalformed=\"1\"";
674  Result << ">";
675  {
676    SmallString<32> Tag;
677    {
678      llvm::raw_svector_ostream TagOS(Tag);
679      printHTMLStartTagComment(C, TagOS);
680    }
681    appendToResultWithCDATAEscaping(Tag);
682  }
683  Result << "</rawHTML>";
684}
685
686void
687CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
688  Result << "<rawHTML";
689  if (C->isMalformed())
690    Result << " isMalformed=\"1\"";
691  Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
692}
693
694void
695CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
696  appendParagraphCommentWithKind(C, StringRef());
697}
698
699void CommentASTToXMLConverter::appendParagraphCommentWithKind(
700                                  const ParagraphComment *C,
701                                  StringRef ParagraphKind) {
702  if (C->isWhitespace())
703    return;
704
705  if (ParagraphKind.empty())
706    Result << "<Para>";
707  else
708    Result << "<Para kind=\"" << ParagraphKind << "\">";
709
710  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
711       I != E; ++I) {
712    visit(*I);
713  }
714  Result << "</Para>";
715}
716
717void CommentASTToXMLConverter::visitBlockCommandComment(
718    const BlockCommandComment *C) {
719  StringRef ParagraphKind;
720
721  switch (C->getCommandID()) {
722  case CommandTraits::KCI_attention:
723  case CommandTraits::KCI_author:
724  case CommandTraits::KCI_authors:
725  case CommandTraits::KCI_bug:
726  case CommandTraits::KCI_copyright:
727  case CommandTraits::KCI_date:
728  case CommandTraits::KCI_invariant:
729  case CommandTraits::KCI_note:
730  case CommandTraits::KCI_post:
731  case CommandTraits::KCI_pre:
732  case CommandTraits::KCI_remark:
733  case CommandTraits::KCI_remarks:
734  case CommandTraits::KCI_sa:
735  case CommandTraits::KCI_see:
736  case CommandTraits::KCI_since:
737  case CommandTraits::KCI_todo:
738  case CommandTraits::KCI_version:
739  case CommandTraits::KCI_warning:
740    ParagraphKind = C->getCommandName(Traits);
741  default:
742    break;
743  }
744
745  appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind);
746}
747
748void CommentASTToXMLConverter::visitParamCommandComment(
749    const ParamCommandComment *C) {
750  Result << "<Parameter><Name>";
751  appendToResultWithXMLEscaping(C->isParamIndexValid()
752                                    ? C->getParamName(FC)
753                                    : C->getParamNameAsWritten());
754  Result << "</Name>";
755
756  if (C->isParamIndexValid()) {
757    if (C->isVarArgParam())
758      Result << "<IsVarArg />";
759    else
760      Result << "<Index>" << C->getParamIndex() << "</Index>";
761  }
762
763  Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">";
764  switch (C->getDirection()) {
765  case ParamCommandComment::In:
766    Result << "in";
767    break;
768  case ParamCommandComment::Out:
769    Result << "out";
770    break;
771  case ParamCommandComment::InOut:
772    Result << "in,out";
773    break;
774  }
775  Result << "</Direction><Discussion>";
776  visit(C->getParagraph());
777  Result << "</Discussion></Parameter>";
778}
779
780void CommentASTToXMLConverter::visitTParamCommandComment(
781                                  const TParamCommandComment *C) {
782  Result << "<Parameter><Name>";
783  appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC)
784                                : C->getParamNameAsWritten());
785  Result << "</Name>";
786
787  if (C->isPositionValid() && C->getDepth() == 1) {
788    Result << "<Index>" << C->getIndex(0) << "</Index>";
789  }
790
791  Result << "<Discussion>";
792  visit(C->getParagraph());
793  Result << "</Discussion></Parameter>";
794}
795
796void CommentASTToXMLConverter::visitVerbatimBlockComment(
797                                  const VerbatimBlockComment *C) {
798  unsigned NumLines = C->getNumLines();
799  if (NumLines == 0)
800    return;
801
802  switch (C->getCommandID()) {
803  case CommandTraits::KCI_code:
804    Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">";
805    break;
806  default:
807    Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
808    break;
809  }
810  for (unsigned i = 0; i != NumLines; ++i) {
811    appendToResultWithXMLEscaping(C->getText(i));
812    if (i + 1 != NumLines)
813      Result << '\n';
814  }
815  Result << "</Verbatim>";
816}
817
818void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
819                                  const VerbatimBlockLineComment *C) {
820  llvm_unreachable("should not see this AST node");
821}
822
823void CommentASTToXMLConverter::visitVerbatimLineComment(
824                                  const VerbatimLineComment *C) {
825  Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
826  appendToResultWithXMLEscaping(C->getText());
827  Result << "</Verbatim>";
828}
829
830void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
831  FullCommentParts Parts(C, Traits);
832
833  const DeclInfo *DI = C->getDeclInfo();
834  StringRef RootEndTag;
835  if (DI) {
836    switch (DI->getKind()) {
837    case DeclInfo::OtherKind:
838      RootEndTag = "</Other>";
839      Result << "<Other";
840      break;
841    case DeclInfo::FunctionKind:
842      RootEndTag = "</Function>";
843      Result << "<Function";
844      switch (DI->TemplateKind) {
845      case DeclInfo::NotTemplate:
846        break;
847      case DeclInfo::Template:
848        Result << " templateKind=\"template\"";
849        break;
850      case DeclInfo::TemplateSpecialization:
851        Result << " templateKind=\"specialization\"";
852        break;
853      case DeclInfo::TemplatePartialSpecialization:
854        llvm_unreachable("partial specializations of functions "
855                         "are not allowed in C++");
856      }
857      if (DI->IsInstanceMethod)
858        Result << " isInstanceMethod=\"1\"";
859      if (DI->IsClassMethod)
860        Result << " isClassMethod=\"1\"";
861      break;
862    case DeclInfo::ClassKind:
863      RootEndTag = "</Class>";
864      Result << "<Class";
865      switch (DI->TemplateKind) {
866      case DeclInfo::NotTemplate:
867        break;
868      case DeclInfo::Template:
869        Result << " templateKind=\"template\"";
870        break;
871      case DeclInfo::TemplateSpecialization:
872        Result << " templateKind=\"specialization\"";
873        break;
874      case DeclInfo::TemplatePartialSpecialization:
875        Result << " templateKind=\"partialSpecialization\"";
876        break;
877      }
878      break;
879    case DeclInfo::VariableKind:
880      RootEndTag = "</Variable>";
881      Result << "<Variable";
882      break;
883    case DeclInfo::NamespaceKind:
884      RootEndTag = "</Namespace>";
885      Result << "<Namespace";
886      break;
887    case DeclInfo::TypedefKind:
888      RootEndTag = "</Typedef>";
889      Result << "<Typedef";
890      break;
891    case DeclInfo::EnumKind:
892      RootEndTag = "</Enum>";
893      Result << "<Enum";
894      break;
895    }
896
897    {
898      // Print line and column number.
899      SourceLocation Loc = DI->CurrentDecl->getLocation();
900      std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
901      FileID FID = LocInfo.first;
902      unsigned FileOffset = LocInfo.second;
903
904      if (!FID.isInvalid()) {
905        if (const FileEntry *FE = SM.getFileEntryForID(FID)) {
906          Result << " file=\"";
907          appendToResultWithXMLEscaping(FE->getName());
908          Result << "\"";
909        }
910        Result << " line=\"" << SM.getLineNumber(FID, FileOffset)
911               << "\" column=\"" << SM.getColumnNumber(FID, FileOffset)
912               << "\"";
913      }
914    }
915
916    // Finish the root tag.
917    Result << ">";
918
919    bool FoundName = false;
920    if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) {
921      if (DeclarationName DeclName = ND->getDeclName()) {
922        Result << "<Name>";
923        std::string Name = DeclName.getAsString();
924        appendToResultWithXMLEscaping(Name);
925        FoundName = true;
926        Result << "</Name>";
927      }
928    }
929    if (!FoundName)
930      Result << "<Name>&lt;anonymous&gt;</Name>";
931
932    {
933      // Print USR.
934      SmallString<128> USR;
935      generateUSRForDecl(DI->CommentDecl, USR);
936      if (!USR.empty()) {
937        Result << "<USR>";
938        appendToResultWithXMLEscaping(USR);
939        Result << "</USR>";
940      }
941    }
942  } else {
943    // No DeclInfo -- just emit some root tag and name tag.
944    RootEndTag = "</Other>";
945    Result << "<Other><Name>unknown</Name>";
946  }
947
948  if (Parts.Headerfile) {
949    Result << "<Headerfile>";
950    visit(Parts.Headerfile);
951    Result << "</Headerfile>";
952  }
953
954  {
955    // Pretty-print the declaration.
956    Result << "<Declaration>";
957    SmallString<128> Declaration;
958    getSourceTextOfDeclaration(DI, Declaration);
959    formatTextOfDeclaration(DI, Declaration);
960    appendToResultWithXMLEscaping(Declaration);
961    Result << "</Declaration>";
962  }
963
964  bool FirstParagraphIsBrief = false;
965  if (Parts.Brief) {
966    Result << "<Abstract>";
967    visit(Parts.Brief);
968    Result << "</Abstract>";
969  } else if (Parts.FirstParagraph) {
970    Result << "<Abstract>";
971    visit(Parts.FirstParagraph);
972    Result << "</Abstract>";
973    FirstParagraphIsBrief = true;
974  }
975
976  if (Parts.TParams.size() != 0) {
977    Result << "<TemplateParameters>";
978    for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
979      visit(Parts.TParams[i]);
980    Result << "</TemplateParameters>";
981  }
982
983  if (Parts.Params.size() != 0) {
984    Result << "<Parameters>";
985    for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
986      visit(Parts.Params[i]);
987    Result << "</Parameters>";
988  }
989
990  if (Parts.Exceptions.size() != 0) {
991    Result << "<Exceptions>";
992    for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i)
993      visit(Parts.Exceptions[i]);
994    Result << "</Exceptions>";
995  }
996
997  if (Parts.Returns.size() != 0) {
998    Result << "<ResultDiscussion>";
999    for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
1000      visit(Parts.Returns[i]);
1001    Result << "</ResultDiscussion>";
1002  }
1003
1004  if (DI->CommentDecl->hasAttrs()) {
1005    const AttrVec &Attrs = DI->CommentDecl->getAttrs();
1006    for (unsigned i = 0, e = Attrs.size(); i != e; i++) {
1007      const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]);
1008      if (!AA) {
1009        if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) {
1010          if (DA->getMessage().empty())
1011            Result << "<Deprecated/>";
1012          else {
1013            Result << "<Deprecated>";
1014            appendToResultWithXMLEscaping(DA->getMessage());
1015            Result << "</Deprecated>";
1016          }
1017        }
1018        else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) {
1019          if (UA->getMessage().empty())
1020            Result << "<Unavailable/>";
1021          else {
1022            Result << "<Unavailable>";
1023            appendToResultWithXMLEscaping(UA->getMessage());
1024            Result << "</Unavailable>";
1025          }
1026        }
1027        continue;
1028      }
1029
1030      // 'availability' attribute.
1031      Result << "<Availability";
1032      StringRef Distribution;
1033      if (AA->getPlatform()) {
1034        Distribution = AvailabilityAttr::getPrettyPlatformName(
1035                                        AA->getPlatform()->getName());
1036        if (Distribution.empty())
1037          Distribution = AA->getPlatform()->getName();
1038      }
1039      Result << " distribution=\"" << Distribution << "\">";
1040      VersionTuple IntroducedInVersion = AA->getIntroduced();
1041      if (!IntroducedInVersion.empty()) {
1042        Result << "<IntroducedInVersion>"
1043               << IntroducedInVersion.getAsString()
1044               << "</IntroducedInVersion>";
1045      }
1046      VersionTuple DeprecatedInVersion = AA->getDeprecated();
1047      if (!DeprecatedInVersion.empty()) {
1048        Result << "<DeprecatedInVersion>"
1049               << DeprecatedInVersion.getAsString()
1050               << "</DeprecatedInVersion>";
1051      }
1052      VersionTuple RemovedAfterVersion = AA->getObsoleted();
1053      if (!RemovedAfterVersion.empty()) {
1054        Result << "<RemovedAfterVersion>"
1055               << RemovedAfterVersion.getAsString()
1056               << "</RemovedAfterVersion>";
1057      }
1058      StringRef DeprecationSummary = AA->getMessage();
1059      if (!DeprecationSummary.empty()) {
1060        Result << "<DeprecationSummary>";
1061        appendToResultWithXMLEscaping(DeprecationSummary);
1062        Result << "</DeprecationSummary>";
1063      }
1064      if (AA->getUnavailable())
1065        Result << "<Unavailable/>";
1066      Result << "</Availability>";
1067    }
1068  }
1069
1070  {
1071    bool StartTagEmitted = false;
1072    for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
1073      const Comment *C = Parts.MiscBlocks[i];
1074      if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
1075        continue;
1076      if (!StartTagEmitted) {
1077        Result << "<Discussion>";
1078        StartTagEmitted = true;
1079      }
1080      visit(C);
1081    }
1082    if (StartTagEmitted)
1083      Result << "</Discussion>";
1084  }
1085
1086  Result << RootEndTag;
1087
1088  Result.flush();
1089}
1090
1091void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) {
1092  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
1093    const char C = *I;
1094    switch (C) {
1095    case '&':
1096      Result << "&amp;";
1097      break;
1098    case '<':
1099      Result << "&lt;";
1100      break;
1101    case '>':
1102      Result << "&gt;";
1103      break;
1104    case '"':
1105      Result << "&quot;";
1106      break;
1107    case '\'':
1108      Result << "&apos;";
1109      break;
1110    default:
1111      Result << C;
1112      break;
1113    }
1114  }
1115}
1116
1117void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) {
1118  if (S.empty())
1119    return;
1120
1121  Result << "<![CDATA[";
1122  while (!S.empty()) {
1123    size_t Pos = S.find("]]>");
1124    if (Pos == 0) {
1125      Result << "]]]]><![CDATA[>";
1126      S = S.drop_front(3);
1127      continue;
1128    }
1129    if (Pos == StringRef::npos)
1130      Pos = S.size();
1131
1132    Result << S.substr(0, Pos);
1133
1134    S = S.drop_front(Pos);
1135  }
1136  Result << "]]>";
1137}
1138
1139CommentToXMLConverter::CommentToXMLConverter() : FormatInMemoryUniqueId(0) {}
1140CommentToXMLConverter::~CommentToXMLConverter() {}
1141
1142void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC,
1143                                                 SmallVectorImpl<char> &HTML,
1144                                                 const ASTContext &Context) {
1145  CommentASTToHTMLConverter Converter(FC, HTML,
1146                                      Context.getCommentCommandTraits());
1147  Converter.visit(FC);
1148}
1149
1150void CommentToXMLConverter::convertHTMLTagNodeToText(
1151    const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text,
1152    const ASTContext &Context) {
1153  CommentASTToHTMLConverter Converter(nullptr, Text,
1154                                      Context.getCommentCommandTraits());
1155  Converter.visit(HTC);
1156}
1157
1158void CommentToXMLConverter::convertCommentToXML(const FullComment *FC,
1159                                                SmallVectorImpl<char> &XML,
1160                                                const ASTContext &Context) {
1161  if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) {
1162    // Create a new format context, or re-create it after some number of
1163    // iterations, so the buffers don't grow too large.
1164    FormatContext.reset(new SimpleFormatContext(Context.getLangOpts()));
1165  }
1166
1167  CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(),
1168                                     Context.getSourceManager(), *FormatContext,
1169                                     FormatInMemoryUniqueId++);
1170  Converter.visit(FC);
1171}
1172
1173