xref: /freebsd/contrib/llvm-project/clang/lib/Index/CommentToXML.cpp (revision 7a65641922f404b84e9a249d48593de84d8e8d17)
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  
22  using namespace clang;
23  using namespace clang::comments;
24  using namespace clang::index;
25  
26  namespace {
27  
28  /// This comparison will sort parameters with valid index by index, then vararg
29  /// parameters, and invalid (unresolved) parameters last.
30  class ParamCommandCommentCompareIndex {
31  public:
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.
57  class TParamCommandCommentComparePosition {
58  public:
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.
82  struct 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  
97  FullCommentParts::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 Comment::NoCommentKind:
107        continue;
108  
109      case Comment::ParagraphCommentKind: {
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 Comment::BlockCommandCommentKind: {
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 Comment::ParamCommandCommentKind: {
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 Comment::TParamCommandCommentKind: {
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 Comment::VerbatimBlockCommentKind:
168        MiscBlocks.push_back(cast<BlockCommandComment>(Child));
169        break;
170  
171      case Comment::VerbatimLineCommentKind: {
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 Comment::TextCommentKind:
180      case Comment::InlineCommandCommentKind:
181      case Comment::HTMLStartTagCommentKind:
182      case Comment::HTMLEndTagCommentKind:
183      case Comment::VerbatimBlockLineCommentKind:
184      case Comment::FullCommentKind:
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  
197  void 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  
217  class CommentASTToHTMLConverter :
218      public ConstCommentVisitor<CommentASTToHTMLConverter> {
219  public:
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  
252  private:
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  
261  void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
262    appendToResultWithHTMLEscaping(C->getText());
263  }
264  
265  void 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 InlineCommandComment::RenderNormal:
278      for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
279        appendToResultWithHTMLEscaping(C->getArgText(i));
280        Result << " ";
281      }
282      return;
283  
284    case InlineCommandComment::RenderBold:
285      assert(C->getNumArgs() == 1);
286      Result << "<b>";
287      appendToResultWithHTMLEscaping(Arg0);
288      Result << "</b>";
289      return;
290    case InlineCommandComment::RenderMonospaced:
291      assert(C->getNumArgs() == 1);
292      Result << "<tt>";
293      appendToResultWithHTMLEscaping(Arg0);
294      Result<< "</tt>";
295      return;
296    case InlineCommandComment::RenderEmphasized:
297      assert(C->getNumArgs() == 1);
298      Result << "<em>";
299      appendToResultWithHTMLEscaping(Arg0);
300      Result << "</em>";
301      return;
302    case InlineCommandComment::RenderAnchor:
303      assert(C->getNumArgs() == 1);
304      Result << "<span id=\"" << Arg0 << "\"></span>";
305      return;
306    }
307  }
308  
309  void CommentASTToHTMLConverter::visitHTMLStartTagComment(
310                                    const HTMLStartTagComment *C) {
311    printHTMLStartTagComment(C, Result);
312  }
313  
314  void CommentASTToHTMLConverter::visitHTMLEndTagComment(
315                                    const HTMLEndTagComment *C) {
316    Result << "</" << C->getTagName() << ">";
317  }
318  
319  void 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  
332  void 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  
352  void 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  
384  void 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  
415  void 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  
430  void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
431                                    const VerbatimBlockLineComment *C) {
432    llvm_unreachable("should not see this AST node");
433  }
434  
435  void CommentASTToHTMLConverter::visitVerbatimLineComment(
436                                    const VerbatimLineComment *C) {
437    Result << "<pre>";
438    appendToResultWithHTMLEscaping(C->getText());
439    Result << "</pre>";
440  }
441  
442  void 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  
487  void 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  
498  void 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  
527  namespace {
528  class CommentASTToXMLConverter :
529      public ConstCommentVisitor<CommentASTToXMLConverter> {
530  public:
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  
566  private:
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  
576  void 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  
589  void 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  
610  void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
611    appendToResultWithXMLEscaping(C->getText());
612  }
613  
614  void 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 InlineCommandComment::RenderNormal:
627      for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
628        appendToResultWithXMLEscaping(C->getArgText(i));
629        Result << " ";
630      }
631      return;
632    case InlineCommandComment::RenderBold:
633      assert(C->getNumArgs() == 1);
634      Result << "<bold>";
635      appendToResultWithXMLEscaping(Arg0);
636      Result << "</bold>";
637      return;
638    case InlineCommandComment::RenderMonospaced:
639      assert(C->getNumArgs() == 1);
640      Result << "<monospaced>";
641      appendToResultWithXMLEscaping(Arg0);
642      Result << "</monospaced>";
643      return;
644    case InlineCommandComment::RenderEmphasized:
645      assert(C->getNumArgs() == 1);
646      Result << "<emphasized>";
647      appendToResultWithXMLEscaping(Arg0);
648      Result << "</emphasized>";
649      return;
650    case InlineCommandComment::RenderAnchor:
651      assert(C->getNumArgs() == 1);
652      Result << "<anchor id=\"" << Arg0 << "\"></anchor>";
653      return;
654    }
655  }
656  
657  void 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  
674  void
675  CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
676    Result << "<rawHTML";
677    if (C->isMalformed())
678      Result << " isMalformed=\"1\"";
679    Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
680  }
681  
682  void
683  CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
684    appendParagraphCommentWithKind(C, StringRef());
685  }
686  
687  void 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  
705  void 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  
737  void 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 ParamCommandComment::In:
755      Result << "in";
756      break;
757    case ParamCommandComment::Out:
758      Result << "out";
759      break;
760    case ParamCommandComment::InOut:
761      Result << "in,out";
762      break;
763    }
764    Result << "</Direction><Discussion>";
765    visit(C->getParagraph());
766    Result << "</Discussion></Parameter>";
767  }
768  
769  void 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  
785  void 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  
807  void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
808                                    const VerbatimBlockLineComment *C) {
809    llvm_unreachable("should not see this AST node");
810  }
811  
812  void CommentASTToXMLConverter::visitVerbatimLineComment(
813                                    const VerbatimLineComment *C) {
814    Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
815    appendToResultWithXMLEscaping(C->getText());
816    Result << "</Verbatim>";
817  }
818  
819  void 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 (const FileEntry *FE = SM.getFileEntryForID(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  
1078  void 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  
1104  void 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  
1126  CommentToXMLConverter::CommentToXMLConverter() {}
1127  CommentToXMLConverter::~CommentToXMLConverter() {}
1128  
1129  void 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  
1137  void 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  
1145  void 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