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