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