1 //===--- CommentToXML.cpp - Convert comments to XML representation --------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/Index/CommentToXML.h"
11 #include "SimpleFormatContext.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/AST/Attr.h"
14 #include "clang/AST/Comment.h"
15 #include "clang/AST/CommentVisitor.h"
16 #include "clang/Format/Format.h"
17 #include "clang/Index/USRGeneration.h"
18 #include "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:
operator ()(const ParamCommandComment * LHS,const ParamCommandComment * RHS) const32   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:
operator ()(const TParamCommandComment * LHS,const TParamCommandComment * RHS) const59   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 
FullCommentParts(const FullComment * C,const CommandTraits & Traits)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   std::stable_sort(Params.begin(), Params.end(),
194                    ParamCommandCommentCompareIndex());
195 
196   std::stable_sort(TParams.begin(), TParams.end(),
197                    TParamCommandCommentComparePosition());
198 }
199 
printHTMLStartTagComment(const HTMLStartTagComment * C,llvm::raw_svector_ostream & Result)200 void printHTMLStartTagComment(const HTMLStartTagComment *C,
201                               llvm::raw_svector_ostream &Result) {
202   Result << "<" << C->getTagName();
203 
204   if (C->getNumAttrs() != 0) {
205     for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
206       Result << " ";
207       const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
208       Result << Attr.Name;
209       if (!Attr.Value.empty())
210         Result << "=\"" << Attr.Value << "\"";
211     }
212   }
213 
214   if (!C->isSelfClosing())
215     Result << ">";
216   else
217     Result << "/>";
218 }
219 
220 class CommentASTToHTMLConverter :
221     public ConstCommentVisitor<CommentASTToHTMLConverter> {
222 public:
223   /// \param Str accumulator for HTML.
CommentASTToHTMLConverter(const FullComment * FC,SmallVectorImpl<char> & Str,const CommandTraits & Traits)224   CommentASTToHTMLConverter(const FullComment *FC,
225                             SmallVectorImpl<char> &Str,
226                             const CommandTraits &Traits) :
227       FC(FC), Result(Str), Traits(Traits)
228   { }
229 
230   // Inline content.
231   void visitTextComment(const TextComment *C);
232   void visitInlineCommandComment(const InlineCommandComment *C);
233   void visitHTMLStartTagComment(const HTMLStartTagComment *C);
234   void visitHTMLEndTagComment(const HTMLEndTagComment *C);
235 
236   // Block content.
237   void visitParagraphComment(const ParagraphComment *C);
238   void visitBlockCommandComment(const BlockCommandComment *C);
239   void visitParamCommandComment(const ParamCommandComment *C);
240   void visitTParamCommandComment(const TParamCommandComment *C);
241   void visitVerbatimBlockComment(const VerbatimBlockComment *C);
242   void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
243   void visitVerbatimLineComment(const VerbatimLineComment *C);
244 
245   void visitFullComment(const FullComment *C);
246 
247   // Helpers.
248 
249   /// Convert a paragraph that is not a block by itself (an argument to some
250   /// command).
251   void visitNonStandaloneParagraphComment(const ParagraphComment *C);
252 
253   void appendToResultWithHTMLEscaping(StringRef S);
254 
255 private:
256   const FullComment *FC;
257   /// Output stream for HTML.
258   llvm::raw_svector_ostream Result;
259 
260   const CommandTraits &Traits;
261 };
262 } // end unnamed namespace
263 
visitTextComment(const TextComment * C)264 void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
265   appendToResultWithHTMLEscaping(C->getText());
266 }
267 
visitInlineCommandComment(const InlineCommandComment * C)268 void CommentASTToHTMLConverter::visitInlineCommandComment(
269                                   const InlineCommandComment *C) {
270   // Nothing to render if no arguments supplied.
271   if (C->getNumArgs() == 0)
272     return;
273 
274   // Nothing to render if argument is empty.
275   StringRef Arg0 = C->getArgText(0);
276   if (Arg0.empty())
277     return;
278 
279   switch (C->getRenderKind()) {
280   case InlineCommandComment::RenderNormal:
281     for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
282       appendToResultWithHTMLEscaping(C->getArgText(i));
283       Result << " ";
284     }
285     return;
286 
287   case InlineCommandComment::RenderBold:
288     assert(C->getNumArgs() == 1);
289     Result << "<b>";
290     appendToResultWithHTMLEscaping(Arg0);
291     Result << "</b>";
292     return;
293   case InlineCommandComment::RenderMonospaced:
294     assert(C->getNumArgs() == 1);
295     Result << "<tt>";
296     appendToResultWithHTMLEscaping(Arg0);
297     Result<< "</tt>";
298     return;
299   case InlineCommandComment::RenderEmphasized:
300     assert(C->getNumArgs() == 1);
301     Result << "<em>";
302     appendToResultWithHTMLEscaping(Arg0);
303     Result << "</em>";
304     return;
305   }
306 }
307 
visitHTMLStartTagComment(const HTMLStartTagComment * C)308 void CommentASTToHTMLConverter::visitHTMLStartTagComment(
309                                   const HTMLStartTagComment *C) {
310   printHTMLStartTagComment(C, Result);
311 }
312 
visitHTMLEndTagComment(const HTMLEndTagComment * C)313 void CommentASTToHTMLConverter::visitHTMLEndTagComment(
314                                   const HTMLEndTagComment *C) {
315   Result << "</" << C->getTagName() << ">";
316 }
317 
visitParagraphComment(const ParagraphComment * C)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 
visitBlockCommandComment(const BlockCommandComment * C)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 
visitParamCommandComment(const ParamCommandComment * C)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 
visitTParamCommandComment(const TParamCommandComment * C)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 
visitVerbatimBlockComment(const VerbatimBlockComment * C)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 
visitVerbatimBlockLineComment(const VerbatimBlockLineComment * C)429 void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
430                                   const VerbatimBlockLineComment *C) {
431   llvm_unreachable("should not see this AST node");
432 }
433 
visitVerbatimLineComment(const VerbatimLineComment * C)434 void CommentASTToHTMLConverter::visitVerbatimLineComment(
435                                   const VerbatimLineComment *C) {
436   Result << "<pre>";
437   appendToResultWithHTMLEscaping(C->getText());
438   Result << "</pre>";
439 }
440 
visitFullComment(const FullComment * C)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   Result.flush();
485 }
486 
visitNonStandaloneParagraphComment(const ParagraphComment * C)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 
appendToResultWithHTMLEscaping(StringRef S)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.
CommentASTToXMLConverter(const FullComment * FC,SmallVectorImpl<char> & Str,const CommandTraits & Traits,const SourceManager & SM,SimpleFormatContext & SFC,unsigned FUID)532   CommentASTToXMLConverter(const FullComment *FC,
533                            SmallVectorImpl<char> &Str,
534                            const CommandTraits &Traits,
535                            const SourceManager &SM,
536                            SimpleFormatContext &SFC,
537                            unsigned FUID) :
538       FC(FC), Result(Str), Traits(Traits), SM(SM),
539       FormatRewriterContext(SFC),
540       FormatInMemoryUniqueId(FUID) { }
541 
542   // Inline content.
543   void visitTextComment(const TextComment *C);
544   void visitInlineCommandComment(const InlineCommandComment *C);
545   void visitHTMLStartTagComment(const HTMLStartTagComment *C);
546   void visitHTMLEndTagComment(const HTMLEndTagComment *C);
547 
548   // Block content.
549   void visitParagraphComment(const ParagraphComment *C);
550 
551   void appendParagraphCommentWithKind(const ParagraphComment *C,
552                                       StringRef Kind);
553 
554   void visitBlockCommandComment(const BlockCommandComment *C);
555   void visitParamCommandComment(const ParamCommandComment *C);
556   void visitTParamCommandComment(const TParamCommandComment *C);
557   void visitVerbatimBlockComment(const VerbatimBlockComment *C);
558   void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
559   void visitVerbatimLineComment(const VerbatimLineComment *C);
560 
561   void visitFullComment(const FullComment *C);
562 
563   // Helpers.
564   void appendToResultWithXMLEscaping(StringRef S);
565   void appendToResultWithCDATAEscaping(StringRef S);
566 
567   void formatTextOfDeclaration(const DeclInfo *DI,
568                                SmallString<128> &Declaration);
569 
570 private:
571   const FullComment *FC;
572 
573   /// Output stream for XML.
574   llvm::raw_svector_ostream Result;
575 
576   const CommandTraits &Traits;
577   const SourceManager &SM;
578   SimpleFormatContext &FormatRewriterContext;
579   unsigned FormatInMemoryUniqueId;
580 };
581 
getSourceTextOfDeclaration(const DeclInfo * ThisDecl,SmallVectorImpl<char> & Str)582 void getSourceTextOfDeclaration(const DeclInfo *ThisDecl,
583                                 SmallVectorImpl<char> &Str) {
584   ASTContext &Context = ThisDecl->CurrentDecl->getASTContext();
585   const LangOptions &LangOpts = Context.getLangOpts();
586   llvm::raw_svector_ostream OS(Str);
587   PrintingPolicy PPolicy(LangOpts);
588   PPolicy.PolishForDeclaration = true;
589   PPolicy.TerseOutput = true;
590   ThisDecl->CurrentDecl->print(OS, PPolicy,
591                                /*Indentation*/0, /*PrintInstantiation*/false);
592 }
593 
formatTextOfDeclaration(const DeclInfo * DI,SmallString<128> & Declaration)594 void CommentASTToXMLConverter::formatTextOfDeclaration(
595     const DeclInfo *DI, SmallString<128> &Declaration) {
596   // FIXME. formatting API expects null terminated input string.
597   // There might be more efficient way of doing this.
598   std::string StringDecl = Declaration.str();
599 
600   // Formatter specific code.
601   // Form a unique in memory buffer name.
602   SmallString<128> filename;
603   filename += "xmldecl";
604   filename += llvm::utostr(FormatInMemoryUniqueId);
605   filename += ".xd";
606   FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl);
607   SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID)
608       .getLocWithOffset(0);
609   unsigned Length = Declaration.size();
610 
611   tooling::Replacements Replace = reformat(
612       format::getLLVMStyle(), FormatRewriterContext.Sources, ID,
613       CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length)));
614   applyAllReplacements(Replace, FormatRewriterContext.Rewrite);
615   Declaration = FormatRewriterContext.getRewrittenText(ID);
616 }
617 
618 } // end unnamed namespace
619 
visitTextComment(const TextComment * C)620 void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
621   appendToResultWithXMLEscaping(C->getText());
622 }
623 
visitInlineCommandComment(const InlineCommandComment * C)624 void CommentASTToXMLConverter::visitInlineCommandComment(
625     const InlineCommandComment *C) {
626   // Nothing to render if no arguments supplied.
627   if (C->getNumArgs() == 0)
628     return;
629 
630   // Nothing to render if argument is empty.
631   StringRef Arg0 = C->getArgText(0);
632   if (Arg0.empty())
633     return;
634 
635   switch (C->getRenderKind()) {
636   case InlineCommandComment::RenderNormal:
637     for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
638       appendToResultWithXMLEscaping(C->getArgText(i));
639       Result << " ";
640     }
641     return;
642   case InlineCommandComment::RenderBold:
643     assert(C->getNumArgs() == 1);
644     Result << "<bold>";
645     appendToResultWithXMLEscaping(Arg0);
646     Result << "</bold>";
647     return;
648   case InlineCommandComment::RenderMonospaced:
649     assert(C->getNumArgs() == 1);
650     Result << "<monospaced>";
651     appendToResultWithXMLEscaping(Arg0);
652     Result << "</monospaced>";
653     return;
654   case InlineCommandComment::RenderEmphasized:
655     assert(C->getNumArgs() == 1);
656     Result << "<emphasized>";
657     appendToResultWithXMLEscaping(Arg0);
658     Result << "</emphasized>";
659     return;
660   }
661 }
662 
visitHTMLStartTagComment(const HTMLStartTagComment * C)663 void CommentASTToXMLConverter::visitHTMLStartTagComment(
664     const HTMLStartTagComment *C) {
665   Result << "<rawHTML";
666   if (C->isMalformed())
667     Result << " isMalformed=\"1\"";
668   Result << ">";
669   {
670     SmallString<32> Tag;
671     {
672       llvm::raw_svector_ostream TagOS(Tag);
673       printHTMLStartTagComment(C, TagOS);
674     }
675     appendToResultWithCDATAEscaping(Tag);
676   }
677   Result << "</rawHTML>";
678 }
679 
680 void
visitHTMLEndTagComment(const HTMLEndTagComment * C)681 CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
682   Result << "<rawHTML";
683   if (C->isMalformed())
684     Result << " isMalformed=\"1\"";
685   Result << ">&lt;/" << C->getTagName() << "&gt;</rawHTML>";
686 }
687 
688 void
visitParagraphComment(const ParagraphComment * C)689 CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
690   appendParagraphCommentWithKind(C, StringRef());
691 }
692 
appendParagraphCommentWithKind(const ParagraphComment * C,StringRef ParagraphKind)693 void CommentASTToXMLConverter::appendParagraphCommentWithKind(
694                                   const ParagraphComment *C,
695                                   StringRef ParagraphKind) {
696   if (C->isWhitespace())
697     return;
698 
699   if (ParagraphKind.empty())
700     Result << "<Para>";
701   else
702     Result << "<Para kind=\"" << ParagraphKind << "\">";
703 
704   for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
705        I != E; ++I) {
706     visit(*I);
707   }
708   Result << "</Para>";
709 }
710 
visitBlockCommandComment(const BlockCommandComment * C)711 void CommentASTToXMLConverter::visitBlockCommandComment(
712     const BlockCommandComment *C) {
713   StringRef ParagraphKind;
714 
715   switch (C->getCommandID()) {
716   case CommandTraits::KCI_attention:
717   case CommandTraits::KCI_author:
718   case CommandTraits::KCI_authors:
719   case CommandTraits::KCI_bug:
720   case CommandTraits::KCI_copyright:
721   case CommandTraits::KCI_date:
722   case CommandTraits::KCI_invariant:
723   case CommandTraits::KCI_note:
724   case CommandTraits::KCI_post:
725   case CommandTraits::KCI_pre:
726   case CommandTraits::KCI_remark:
727   case CommandTraits::KCI_remarks:
728   case CommandTraits::KCI_sa:
729   case CommandTraits::KCI_see:
730   case CommandTraits::KCI_since:
731   case CommandTraits::KCI_todo:
732   case CommandTraits::KCI_version:
733   case CommandTraits::KCI_warning:
734     ParagraphKind = C->getCommandName(Traits);
735   default:
736     break;
737   }
738 
739   appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind);
740 }
741 
visitParamCommandComment(const ParamCommandComment * C)742 void CommentASTToXMLConverter::visitParamCommandComment(
743     const ParamCommandComment *C) {
744   Result << "<Parameter><Name>";
745   appendToResultWithXMLEscaping(C->isParamIndexValid()
746                                     ? C->getParamName(FC)
747                                     : C->getParamNameAsWritten());
748   Result << "</Name>";
749 
750   if (C->isParamIndexValid()) {
751     if (C->isVarArgParam())
752       Result << "<IsVarArg />";
753     else
754       Result << "<Index>" << C->getParamIndex() << "</Index>";
755   }
756 
757   Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">";
758   switch (C->getDirection()) {
759   case ParamCommandComment::In:
760     Result << "in";
761     break;
762   case ParamCommandComment::Out:
763     Result << "out";
764     break;
765   case ParamCommandComment::InOut:
766     Result << "in,out";
767     break;
768   }
769   Result << "</Direction><Discussion>";
770   visit(C->getParagraph());
771   Result << "</Discussion></Parameter>";
772 }
773 
visitTParamCommandComment(const TParamCommandComment * C)774 void CommentASTToXMLConverter::visitTParamCommandComment(
775                                   const TParamCommandComment *C) {
776   Result << "<Parameter><Name>";
777   appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC)
778                                 : C->getParamNameAsWritten());
779   Result << "</Name>";
780 
781   if (C->isPositionValid() && C->getDepth() == 1) {
782     Result << "<Index>" << C->getIndex(0) << "</Index>";
783   }
784 
785   Result << "<Discussion>";
786   visit(C->getParagraph());
787   Result << "</Discussion></Parameter>";
788 }
789 
visitVerbatimBlockComment(const VerbatimBlockComment * C)790 void CommentASTToXMLConverter::visitVerbatimBlockComment(
791                                   const VerbatimBlockComment *C) {
792   unsigned NumLines = C->getNumLines();
793   if (NumLines == 0)
794     return;
795 
796   switch (C->getCommandID()) {
797   case CommandTraits::KCI_code:
798     Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">";
799     break;
800   default:
801     Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
802     break;
803   }
804   for (unsigned i = 0; i != NumLines; ++i) {
805     appendToResultWithXMLEscaping(C->getText(i));
806     if (i + 1 != NumLines)
807       Result << '\n';
808   }
809   Result << "</Verbatim>";
810 }
811 
visitVerbatimBlockLineComment(const VerbatimBlockLineComment * C)812 void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
813                                   const VerbatimBlockLineComment *C) {
814   llvm_unreachable("should not see this AST node");
815 }
816 
visitVerbatimLineComment(const VerbatimLineComment * C)817 void CommentASTToXMLConverter::visitVerbatimLineComment(
818                                   const VerbatimLineComment *C) {
819   Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
820   appendToResultWithXMLEscaping(C->getText());
821   Result << "</Verbatim>";
822 }
823 
visitFullComment(const FullComment * C)824 void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
825   FullCommentParts Parts(C, Traits);
826 
827   const DeclInfo *DI = C->getDeclInfo();
828   StringRef RootEndTag;
829   if (DI) {
830     switch (DI->getKind()) {
831     case DeclInfo::OtherKind:
832       RootEndTag = "</Other>";
833       Result << "<Other";
834       break;
835     case DeclInfo::FunctionKind:
836       RootEndTag = "</Function>";
837       Result << "<Function";
838       switch (DI->TemplateKind) {
839       case DeclInfo::NotTemplate:
840         break;
841       case DeclInfo::Template:
842         Result << " templateKind=\"template\"";
843         break;
844       case DeclInfo::TemplateSpecialization:
845         Result << " templateKind=\"specialization\"";
846         break;
847       case DeclInfo::TemplatePartialSpecialization:
848         llvm_unreachable("partial specializations of functions "
849                          "are not allowed in C++");
850       }
851       if (DI->IsInstanceMethod)
852         Result << " isInstanceMethod=\"1\"";
853       if (DI->IsClassMethod)
854         Result << " isClassMethod=\"1\"";
855       break;
856     case DeclInfo::ClassKind:
857       RootEndTag = "</Class>";
858       Result << "<Class";
859       switch (DI->TemplateKind) {
860       case DeclInfo::NotTemplate:
861         break;
862       case DeclInfo::Template:
863         Result << " templateKind=\"template\"";
864         break;
865       case DeclInfo::TemplateSpecialization:
866         Result << " templateKind=\"specialization\"";
867         break;
868       case DeclInfo::TemplatePartialSpecialization:
869         Result << " templateKind=\"partialSpecialization\"";
870         break;
871       }
872       break;
873     case DeclInfo::VariableKind:
874       RootEndTag = "</Variable>";
875       Result << "<Variable";
876       break;
877     case DeclInfo::NamespaceKind:
878       RootEndTag = "</Namespace>";
879       Result << "<Namespace";
880       break;
881     case DeclInfo::TypedefKind:
882       RootEndTag = "</Typedef>";
883       Result << "<Typedef";
884       break;
885     case DeclInfo::EnumKind:
886       RootEndTag = "</Enum>";
887       Result << "<Enum";
888       break;
889     }
890 
891     {
892       // Print line and column number.
893       SourceLocation Loc = DI->CurrentDecl->getLocation();
894       std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
895       FileID FID = LocInfo.first;
896       unsigned FileOffset = LocInfo.second;
897 
898       if (!FID.isInvalid()) {
899         if (const FileEntry *FE = SM.getFileEntryForID(FID)) {
900           Result << " file=\"";
901           appendToResultWithXMLEscaping(FE->getName());
902           Result << "\"";
903         }
904         Result << " line=\"" << SM.getLineNumber(FID, FileOffset)
905                << "\" column=\"" << SM.getColumnNumber(FID, FileOffset)
906                << "\"";
907       }
908     }
909 
910     // Finish the root tag.
911     Result << ">";
912 
913     bool FoundName = false;
914     if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) {
915       if (DeclarationName DeclName = ND->getDeclName()) {
916         Result << "<Name>";
917         std::string Name = DeclName.getAsString();
918         appendToResultWithXMLEscaping(Name);
919         FoundName = true;
920         Result << "</Name>";
921       }
922     }
923     if (!FoundName)
924       Result << "<Name>&lt;anonymous&gt;</Name>";
925 
926     {
927       // Print USR.
928       SmallString<128> USR;
929       generateUSRForDecl(DI->CommentDecl, USR);
930       if (!USR.empty()) {
931         Result << "<USR>";
932         appendToResultWithXMLEscaping(USR);
933         Result << "</USR>";
934       }
935     }
936   } else {
937     // No DeclInfo -- just emit some root tag and name tag.
938     RootEndTag = "</Other>";
939     Result << "<Other><Name>unknown</Name>";
940   }
941 
942   if (Parts.Headerfile) {
943     Result << "<Headerfile>";
944     visit(Parts.Headerfile);
945     Result << "</Headerfile>";
946   }
947 
948   {
949     // Pretty-print the declaration.
950     Result << "<Declaration>";
951     SmallString<128> Declaration;
952     getSourceTextOfDeclaration(DI, Declaration);
953     formatTextOfDeclaration(DI, Declaration);
954     appendToResultWithXMLEscaping(Declaration);
955     Result << "</Declaration>";
956   }
957 
958   bool FirstParagraphIsBrief = false;
959   if (Parts.Brief) {
960     Result << "<Abstract>";
961     visit(Parts.Brief);
962     Result << "</Abstract>";
963   } else if (Parts.FirstParagraph) {
964     Result << "<Abstract>";
965     visit(Parts.FirstParagraph);
966     Result << "</Abstract>";
967     FirstParagraphIsBrief = true;
968   }
969 
970   if (Parts.TParams.size() != 0) {
971     Result << "<TemplateParameters>";
972     for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
973       visit(Parts.TParams[i]);
974     Result << "</TemplateParameters>";
975   }
976 
977   if (Parts.Params.size() != 0) {
978     Result << "<Parameters>";
979     for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
980       visit(Parts.Params[i]);
981     Result << "</Parameters>";
982   }
983 
984   if (Parts.Exceptions.size() != 0) {
985     Result << "<Exceptions>";
986     for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i)
987       visit(Parts.Exceptions[i]);
988     Result << "</Exceptions>";
989   }
990 
991   if (Parts.Returns.size() != 0) {
992     Result << "<ResultDiscussion>";
993     for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
994       visit(Parts.Returns[i]);
995     Result << "</ResultDiscussion>";
996   }
997 
998   if (DI->CommentDecl->hasAttrs()) {
999     const AttrVec &Attrs = DI->CommentDecl->getAttrs();
1000     for (unsigned i = 0, e = Attrs.size(); i != e; i++) {
1001       const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]);
1002       if (!AA) {
1003         if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) {
1004           if (DA->getMessage().empty())
1005             Result << "<Deprecated/>";
1006           else {
1007             Result << "<Deprecated>";
1008             appendToResultWithXMLEscaping(DA->getMessage());
1009             Result << "</Deprecated>";
1010           }
1011         }
1012         else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) {
1013           if (UA->getMessage().empty())
1014             Result << "<Unavailable/>";
1015           else {
1016             Result << "<Unavailable>";
1017             appendToResultWithXMLEscaping(UA->getMessage());
1018             Result << "</Unavailable>";
1019           }
1020         }
1021         continue;
1022       }
1023 
1024       // 'availability' attribute.
1025       Result << "<Availability";
1026       StringRef Distribution;
1027       if (AA->getPlatform()) {
1028         Distribution = AvailabilityAttr::getPrettyPlatformName(
1029                                         AA->getPlatform()->getName());
1030         if (Distribution.empty())
1031           Distribution = AA->getPlatform()->getName();
1032       }
1033       Result << " distribution=\"" << Distribution << "\">";
1034       VersionTuple IntroducedInVersion = AA->getIntroduced();
1035       if (!IntroducedInVersion.empty()) {
1036         Result << "<IntroducedInVersion>"
1037                << IntroducedInVersion.getAsString()
1038                << "</IntroducedInVersion>";
1039       }
1040       VersionTuple DeprecatedInVersion = AA->getDeprecated();
1041       if (!DeprecatedInVersion.empty()) {
1042         Result << "<DeprecatedInVersion>"
1043                << DeprecatedInVersion.getAsString()
1044                << "</DeprecatedInVersion>";
1045       }
1046       VersionTuple RemovedAfterVersion = AA->getObsoleted();
1047       if (!RemovedAfterVersion.empty()) {
1048         Result << "<RemovedAfterVersion>"
1049                << RemovedAfterVersion.getAsString()
1050                << "</RemovedAfterVersion>";
1051       }
1052       StringRef DeprecationSummary = AA->getMessage();
1053       if (!DeprecationSummary.empty()) {
1054         Result << "<DeprecationSummary>";
1055         appendToResultWithXMLEscaping(DeprecationSummary);
1056         Result << "</DeprecationSummary>";
1057       }
1058       if (AA->getUnavailable())
1059         Result << "<Unavailable/>";
1060       Result << "</Availability>";
1061     }
1062   }
1063 
1064   {
1065     bool StartTagEmitted = false;
1066     for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
1067       const Comment *C = Parts.MiscBlocks[i];
1068       if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
1069         continue;
1070       if (!StartTagEmitted) {
1071         Result << "<Discussion>";
1072         StartTagEmitted = true;
1073       }
1074       visit(C);
1075     }
1076     if (StartTagEmitted)
1077       Result << "</Discussion>";
1078   }
1079 
1080   Result << RootEndTag;
1081 
1082   Result.flush();
1083 }
1084 
appendToResultWithXMLEscaping(StringRef S)1085 void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) {
1086   for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
1087     const char C = *I;
1088     switch (C) {
1089     case '&':
1090       Result << "&amp;";
1091       break;
1092     case '<':
1093       Result << "&lt;";
1094       break;
1095     case '>':
1096       Result << "&gt;";
1097       break;
1098     case '"':
1099       Result << "&quot;";
1100       break;
1101     case '\'':
1102       Result << "&apos;";
1103       break;
1104     default:
1105       Result << C;
1106       break;
1107     }
1108   }
1109 }
1110 
appendToResultWithCDATAEscaping(StringRef S)1111 void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) {
1112   if (S.empty())
1113     return;
1114 
1115   Result << "<![CDATA[";
1116   while (!S.empty()) {
1117     size_t Pos = S.find("]]>");
1118     if (Pos == 0) {
1119       Result << "]]]]><![CDATA[>";
1120       S = S.drop_front(3);
1121       continue;
1122     }
1123     if (Pos == StringRef::npos)
1124       Pos = S.size();
1125 
1126     Result << S.substr(0, Pos);
1127 
1128     S = S.drop_front(Pos);
1129   }
1130   Result << "]]>";
1131 }
1132 
CommentToXMLConverter()1133 CommentToXMLConverter::CommentToXMLConverter() : FormatInMemoryUniqueId(0) {}
~CommentToXMLConverter()1134 CommentToXMLConverter::~CommentToXMLConverter() {}
1135 
convertCommentToHTML(const FullComment * FC,SmallVectorImpl<char> & HTML,const ASTContext & Context)1136 void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC,
1137                                                  SmallVectorImpl<char> &HTML,
1138                                                  const ASTContext &Context) {
1139   CommentASTToHTMLConverter Converter(FC, HTML,
1140                                       Context.getCommentCommandTraits());
1141   Converter.visit(FC);
1142 }
1143 
convertHTMLTagNodeToText(const comments::HTMLTagComment * HTC,SmallVectorImpl<char> & Text,const ASTContext & Context)1144 void CommentToXMLConverter::convertHTMLTagNodeToText(
1145     const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text,
1146     const ASTContext &Context) {
1147   CommentASTToHTMLConverter Converter(nullptr, Text,
1148                                       Context.getCommentCommandTraits());
1149   Converter.visit(HTC);
1150 }
1151 
convertCommentToXML(const FullComment * FC,SmallVectorImpl<char> & XML,const ASTContext & Context)1152 void CommentToXMLConverter::convertCommentToXML(const FullComment *FC,
1153                                                 SmallVectorImpl<char> &XML,
1154                                                 const ASTContext &Context) {
1155   if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) {
1156     // Create a new format context, or re-create it after some number of
1157     // iterations, so the buffers don't grow too large.
1158     FormatContext.reset(new SimpleFormatContext(Context.getLangOpts()));
1159   }
1160 
1161   CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(),
1162                                      Context.getSourceManager(), *FormatContext,
1163                                      FormatInMemoryUniqueId++);
1164   Converter.visit(FC);
1165 }
1166 
1167