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