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