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