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 }
485
visitNonStandaloneParagraphComment(const ParagraphComment * C)486 void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
487 const ParagraphComment *C) {
488 if (!C)
489 return;
490
491 for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
492 I != E; ++I) {
493 visit(*I);
494 }
495 }
496
appendToResultWithHTMLEscaping(StringRef S)497 void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
498 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
499 const char C = *I;
500 switch (C) {
501 case '&':
502 Result << "&";
503 break;
504 case '<':
505 Result << "<";
506 break;
507 case '>':
508 Result << ">";
509 break;
510 case '"':
511 Result << """;
512 break;
513 case '\'':
514 Result << "'";
515 break;
516 case '/':
517 Result << "/";
518 break;
519 default:
520 Result << C;
521 break;
522 }
523 }
524 }
525
526 namespace {
527 class CommentASTToXMLConverter :
528 public ConstCommentVisitor<CommentASTToXMLConverter> {
529 public:
530 /// \param Str accumulator for XML.
CommentASTToXMLConverter(const FullComment * FC,SmallVectorImpl<char> & Str,const CommandTraits & Traits,const SourceManager & SM,SimpleFormatContext & SFC,unsigned FUID)531 CommentASTToXMLConverter(const FullComment *FC,
532 SmallVectorImpl<char> &Str,
533 const CommandTraits &Traits,
534 const SourceManager &SM,
535 SimpleFormatContext &SFC,
536 unsigned FUID) :
537 FC(FC), Result(Str), Traits(Traits), SM(SM),
538 FormatRewriterContext(SFC),
539 FormatInMemoryUniqueId(FUID) { }
540
541 // Inline content.
542 void visitTextComment(const TextComment *C);
543 void visitInlineCommandComment(const InlineCommandComment *C);
544 void visitHTMLStartTagComment(const HTMLStartTagComment *C);
545 void visitHTMLEndTagComment(const HTMLEndTagComment *C);
546
547 // Block content.
548 void visitParagraphComment(const ParagraphComment *C);
549
550 void appendParagraphCommentWithKind(const ParagraphComment *C,
551 StringRef Kind);
552
553 void visitBlockCommandComment(const BlockCommandComment *C);
554 void visitParamCommandComment(const ParamCommandComment *C);
555 void visitTParamCommandComment(const TParamCommandComment *C);
556 void visitVerbatimBlockComment(const VerbatimBlockComment *C);
557 void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C);
558 void visitVerbatimLineComment(const VerbatimLineComment *C);
559
560 void visitFullComment(const FullComment *C);
561
562 // Helpers.
563 void appendToResultWithXMLEscaping(StringRef S);
564 void appendToResultWithCDATAEscaping(StringRef S);
565
566 void formatTextOfDeclaration(const DeclInfo *DI,
567 SmallString<128> &Declaration);
568
569 private:
570 const FullComment *FC;
571
572 /// Output stream for XML.
573 llvm::raw_svector_ostream Result;
574
575 const CommandTraits &Traits;
576 const SourceManager &SM;
577 SimpleFormatContext &FormatRewriterContext;
578 unsigned FormatInMemoryUniqueId;
579 };
580
getSourceTextOfDeclaration(const DeclInfo * ThisDecl,SmallVectorImpl<char> & Str)581 void getSourceTextOfDeclaration(const DeclInfo *ThisDecl,
582 SmallVectorImpl<char> &Str) {
583 ASTContext &Context = ThisDecl->CurrentDecl->getASTContext();
584 const LangOptions &LangOpts = Context.getLangOpts();
585 llvm::raw_svector_ostream OS(Str);
586 PrintingPolicy PPolicy(LangOpts);
587 PPolicy.PolishForDeclaration = true;
588 PPolicy.TerseOutput = true;
589 ThisDecl->CurrentDecl->print(OS, PPolicy,
590 /*Indentation*/0, /*PrintInstantiation*/false);
591 }
592
formatTextOfDeclaration(const DeclInfo * DI,SmallString<128> & Declaration)593 void CommentASTToXMLConverter::formatTextOfDeclaration(
594 const DeclInfo *DI, SmallString<128> &Declaration) {
595 // Formatting API expects null terminated input string.
596 StringRef StringDecl(Declaration.c_str(), Declaration.size());
597
598 // Formatter specific code.
599 // Form a unique in memory buffer name.
600 SmallString<128> filename;
601 filename += "xmldecl";
602 filename += llvm::utostr(FormatInMemoryUniqueId);
603 filename += ".xd";
604 FileID ID = FormatRewriterContext.createInMemoryFile(filename, StringDecl);
605 SourceLocation Start = FormatRewriterContext.Sources.getLocForStartOfFile(ID)
606 .getLocWithOffset(0);
607 unsigned Length = Declaration.size();
608
609 tooling::Replacements Replace = reformat(
610 format::getLLVMStyle(), FormatRewriterContext.Sources, ID,
611 CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length)));
612 applyAllReplacements(Replace, FormatRewriterContext.Rewrite);
613 Declaration = FormatRewriterContext.getRewrittenText(ID);
614 }
615
616 } // end unnamed namespace
617
visitTextComment(const TextComment * C)618 void CommentASTToXMLConverter::visitTextComment(const TextComment *C) {
619 appendToResultWithXMLEscaping(C->getText());
620 }
621
visitInlineCommandComment(const InlineCommandComment * C)622 void CommentASTToXMLConverter::visitInlineCommandComment(
623 const InlineCommandComment *C) {
624 // Nothing to render if no arguments supplied.
625 if (C->getNumArgs() == 0)
626 return;
627
628 // Nothing to render if argument is empty.
629 StringRef Arg0 = C->getArgText(0);
630 if (Arg0.empty())
631 return;
632
633 switch (C->getRenderKind()) {
634 case InlineCommandComment::RenderNormal:
635 for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
636 appendToResultWithXMLEscaping(C->getArgText(i));
637 Result << " ";
638 }
639 return;
640 case InlineCommandComment::RenderBold:
641 assert(C->getNumArgs() == 1);
642 Result << "<bold>";
643 appendToResultWithXMLEscaping(Arg0);
644 Result << "</bold>";
645 return;
646 case InlineCommandComment::RenderMonospaced:
647 assert(C->getNumArgs() == 1);
648 Result << "<monospaced>";
649 appendToResultWithXMLEscaping(Arg0);
650 Result << "</monospaced>";
651 return;
652 case InlineCommandComment::RenderEmphasized:
653 assert(C->getNumArgs() == 1);
654 Result << "<emphasized>";
655 appendToResultWithXMLEscaping(Arg0);
656 Result << "</emphasized>";
657 return;
658 }
659 }
660
visitHTMLStartTagComment(const HTMLStartTagComment * C)661 void CommentASTToXMLConverter::visitHTMLStartTagComment(
662 const HTMLStartTagComment *C) {
663 Result << "<rawHTML";
664 if (C->isMalformed())
665 Result << " isMalformed=\"1\"";
666 Result << ">";
667 {
668 SmallString<32> Tag;
669 {
670 llvm::raw_svector_ostream TagOS(Tag);
671 printHTMLStartTagComment(C, TagOS);
672 }
673 appendToResultWithCDATAEscaping(Tag);
674 }
675 Result << "</rawHTML>";
676 }
677
678 void
visitHTMLEndTagComment(const HTMLEndTagComment * C)679 CommentASTToXMLConverter::visitHTMLEndTagComment(const HTMLEndTagComment *C) {
680 Result << "<rawHTML";
681 if (C->isMalformed())
682 Result << " isMalformed=\"1\"";
683 Result << "></" << C->getTagName() << "></rawHTML>";
684 }
685
686 void
visitParagraphComment(const ParagraphComment * C)687 CommentASTToXMLConverter::visitParagraphComment(const ParagraphComment *C) {
688 appendParagraphCommentWithKind(C, StringRef());
689 }
690
appendParagraphCommentWithKind(const ParagraphComment * C,StringRef ParagraphKind)691 void CommentASTToXMLConverter::appendParagraphCommentWithKind(
692 const ParagraphComment *C,
693 StringRef ParagraphKind) {
694 if (C->isWhitespace())
695 return;
696
697 if (ParagraphKind.empty())
698 Result << "<Para>";
699 else
700 Result << "<Para kind=\"" << ParagraphKind << "\">";
701
702 for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
703 I != E; ++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
713 switch (C->getCommandID()) {
714 case CommandTraits::KCI_attention:
715 case CommandTraits::KCI_author:
716 case CommandTraits::KCI_authors:
717 case CommandTraits::KCI_bug:
718 case CommandTraits::KCI_copyright:
719 case CommandTraits::KCI_date:
720 case CommandTraits::KCI_invariant:
721 case CommandTraits::KCI_note:
722 case CommandTraits::KCI_post:
723 case CommandTraits::KCI_pre:
724 case CommandTraits::KCI_remark:
725 case CommandTraits::KCI_remarks:
726 case CommandTraits::KCI_sa:
727 case CommandTraits::KCI_see:
728 case CommandTraits::KCI_since:
729 case CommandTraits::KCI_todo:
730 case CommandTraits::KCI_version:
731 case CommandTraits::KCI_warning:
732 ParagraphKind = C->getCommandName(Traits);
733 default:
734 break;
735 }
736
737 appendParagraphCommentWithKind(C->getParagraph(), ParagraphKind);
738 }
739
visitParamCommandComment(const ParamCommandComment * C)740 void CommentASTToXMLConverter::visitParamCommandComment(
741 const ParamCommandComment *C) {
742 Result << "<Parameter><Name>";
743 appendToResultWithXMLEscaping(C->isParamIndexValid()
744 ? C->getParamName(FC)
745 : C->getParamNameAsWritten());
746 Result << "</Name>";
747
748 if (C->isParamIndexValid()) {
749 if (C->isVarArgParam())
750 Result << "<IsVarArg />";
751 else
752 Result << "<Index>" << C->getParamIndex() << "</Index>";
753 }
754
755 Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">";
756 switch (C->getDirection()) {
757 case ParamCommandComment::In:
758 Result << "in";
759 break;
760 case ParamCommandComment::Out:
761 Result << "out";
762 break;
763 case ParamCommandComment::InOut:
764 Result << "in,out";
765 break;
766 }
767 Result << "</Direction><Discussion>";
768 visit(C->getParagraph());
769 Result << "</Discussion></Parameter>";
770 }
771
visitTParamCommandComment(const TParamCommandComment * C)772 void CommentASTToXMLConverter::visitTParamCommandComment(
773 const TParamCommandComment *C) {
774 Result << "<Parameter><Name>";
775 appendToResultWithXMLEscaping(C->isPositionValid() ? C->getParamName(FC)
776 : C->getParamNameAsWritten());
777 Result << "</Name>";
778
779 if (C->isPositionValid() && C->getDepth() == 1) {
780 Result << "<Index>" << C->getIndex(0) << "</Index>";
781 }
782
783 Result << "<Discussion>";
784 visit(C->getParagraph());
785 Result << "</Discussion></Parameter>";
786 }
787
visitVerbatimBlockComment(const VerbatimBlockComment * C)788 void CommentASTToXMLConverter::visitVerbatimBlockComment(
789 const VerbatimBlockComment *C) {
790 unsigned NumLines = C->getNumLines();
791 if (NumLines == 0)
792 return;
793
794 switch (C->getCommandID()) {
795 case CommandTraits::KCI_code:
796 Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">";
797 break;
798 default:
799 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
800 break;
801 }
802 for (unsigned i = 0; i != NumLines; ++i) {
803 appendToResultWithXMLEscaping(C->getText(i));
804 if (i + 1 != NumLines)
805 Result << '\n';
806 }
807 Result << "</Verbatim>";
808 }
809
visitVerbatimBlockLineComment(const VerbatimBlockLineComment * C)810 void CommentASTToXMLConverter::visitVerbatimBlockLineComment(
811 const VerbatimBlockLineComment *C) {
812 llvm_unreachable("should not see this AST node");
813 }
814
visitVerbatimLineComment(const VerbatimLineComment * C)815 void CommentASTToXMLConverter::visitVerbatimLineComment(
816 const VerbatimLineComment *C) {
817 Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">";
818 appendToResultWithXMLEscaping(C->getText());
819 Result << "</Verbatim>";
820 }
821
visitFullComment(const FullComment * C)822 void CommentASTToXMLConverter::visitFullComment(const FullComment *C) {
823 FullCommentParts Parts(C, Traits);
824
825 const DeclInfo *DI = C->getDeclInfo();
826 StringRef RootEndTag;
827 if (DI) {
828 switch (DI->getKind()) {
829 case DeclInfo::OtherKind:
830 RootEndTag = "</Other>";
831 Result << "<Other";
832 break;
833 case DeclInfo::FunctionKind:
834 RootEndTag = "</Function>";
835 Result << "<Function";
836 switch (DI->TemplateKind) {
837 case DeclInfo::NotTemplate:
838 break;
839 case DeclInfo::Template:
840 Result << " templateKind=\"template\"";
841 break;
842 case DeclInfo::TemplateSpecialization:
843 Result << " templateKind=\"specialization\"";
844 break;
845 case DeclInfo::TemplatePartialSpecialization:
846 llvm_unreachable("partial specializations of functions "
847 "are not allowed in C++");
848 }
849 if (DI->IsInstanceMethod)
850 Result << " isInstanceMethod=\"1\"";
851 if (DI->IsClassMethod)
852 Result << " isClassMethod=\"1\"";
853 break;
854 case DeclInfo::ClassKind:
855 RootEndTag = "</Class>";
856 Result << "<Class";
857 switch (DI->TemplateKind) {
858 case DeclInfo::NotTemplate:
859 break;
860 case DeclInfo::Template:
861 Result << " templateKind=\"template\"";
862 break;
863 case DeclInfo::TemplateSpecialization:
864 Result << " templateKind=\"specialization\"";
865 break;
866 case DeclInfo::TemplatePartialSpecialization:
867 Result << " templateKind=\"partialSpecialization\"";
868 break;
869 }
870 break;
871 case DeclInfo::VariableKind:
872 RootEndTag = "</Variable>";
873 Result << "<Variable";
874 break;
875 case DeclInfo::NamespaceKind:
876 RootEndTag = "</Namespace>";
877 Result << "<Namespace";
878 break;
879 case DeclInfo::TypedefKind:
880 RootEndTag = "</Typedef>";
881 Result << "<Typedef";
882 break;
883 case DeclInfo::EnumKind:
884 RootEndTag = "</Enum>";
885 Result << "<Enum";
886 break;
887 }
888
889 {
890 // Print line and column number.
891 SourceLocation Loc = DI->CurrentDecl->getLocation();
892 std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
893 FileID FID = LocInfo.first;
894 unsigned FileOffset = LocInfo.second;
895
896 if (FID.isValid()) {
897 if (const FileEntry *FE = SM.getFileEntryForID(FID)) {
898 Result << " file=\"";
899 appendToResultWithXMLEscaping(FE->getName());
900 Result << "\"";
901 }
902 Result << " line=\"" << SM.getLineNumber(FID, FileOffset)
903 << "\" column=\"" << SM.getColumnNumber(FID, FileOffset)
904 << "\"";
905 }
906 }
907
908 // Finish the root tag.
909 Result << ">";
910
911 bool FoundName = false;
912 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DI->CommentDecl)) {
913 if (DeclarationName DeclName = ND->getDeclName()) {
914 Result << "<Name>";
915 std::string Name = DeclName.getAsString();
916 appendToResultWithXMLEscaping(Name);
917 FoundName = true;
918 Result << "</Name>";
919 }
920 }
921 if (!FoundName)
922 Result << "<Name><anonymous></Name>";
923
924 {
925 // Print USR.
926 SmallString<128> USR;
927 generateUSRForDecl(DI->CommentDecl, USR);
928 if (!USR.empty()) {
929 Result << "<USR>";
930 appendToResultWithXMLEscaping(USR);
931 Result << "</USR>";
932 }
933 }
934 } else {
935 // No DeclInfo -- just emit some root tag and name tag.
936 RootEndTag = "</Other>";
937 Result << "<Other><Name>unknown</Name>";
938 }
939
940 if (Parts.Headerfile) {
941 Result << "<Headerfile>";
942 visit(Parts.Headerfile);
943 Result << "</Headerfile>";
944 }
945
946 {
947 // Pretty-print the declaration.
948 Result << "<Declaration>";
949 SmallString<128> Declaration;
950 getSourceTextOfDeclaration(DI, Declaration);
951 formatTextOfDeclaration(DI, Declaration);
952 appendToResultWithXMLEscaping(Declaration);
953 Result << "</Declaration>";
954 }
955
956 bool FirstParagraphIsBrief = false;
957 if (Parts.Brief) {
958 Result << "<Abstract>";
959 visit(Parts.Brief);
960 Result << "</Abstract>";
961 } else if (Parts.FirstParagraph) {
962 Result << "<Abstract>";
963 visit(Parts.FirstParagraph);
964 Result << "</Abstract>";
965 FirstParagraphIsBrief = true;
966 }
967
968 if (Parts.TParams.size() != 0) {
969 Result << "<TemplateParameters>";
970 for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i)
971 visit(Parts.TParams[i]);
972 Result << "</TemplateParameters>";
973 }
974
975 if (Parts.Params.size() != 0) {
976 Result << "<Parameters>";
977 for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i)
978 visit(Parts.Params[i]);
979 Result << "</Parameters>";
980 }
981
982 if (Parts.Exceptions.size() != 0) {
983 Result << "<Exceptions>";
984 for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i)
985 visit(Parts.Exceptions[i]);
986 Result << "</Exceptions>";
987 }
988
989 if (Parts.Returns.size() != 0) {
990 Result << "<ResultDiscussion>";
991 for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i)
992 visit(Parts.Returns[i]);
993 Result << "</ResultDiscussion>";
994 }
995
996 if (DI->CommentDecl->hasAttrs()) {
997 const AttrVec &Attrs = DI->CommentDecl->getAttrs();
998 for (unsigned i = 0, e = Attrs.size(); i != e; i++) {
999 const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]);
1000 if (!AA) {
1001 if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) {
1002 if (DA->getMessage().empty())
1003 Result << "<Deprecated/>";
1004 else {
1005 Result << "<Deprecated>";
1006 appendToResultWithXMLEscaping(DA->getMessage());
1007 Result << "</Deprecated>";
1008 }
1009 }
1010 else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) {
1011 if (UA->getMessage().empty())
1012 Result << "<Unavailable/>";
1013 else {
1014 Result << "<Unavailable>";
1015 appendToResultWithXMLEscaping(UA->getMessage());
1016 Result << "</Unavailable>";
1017 }
1018 }
1019 continue;
1020 }
1021
1022 // 'availability' attribute.
1023 Result << "<Availability";
1024 StringRef Distribution;
1025 if (AA->getPlatform()) {
1026 Distribution = AvailabilityAttr::getPrettyPlatformName(
1027 AA->getPlatform()->getName());
1028 if (Distribution.empty())
1029 Distribution = AA->getPlatform()->getName();
1030 }
1031 Result << " distribution=\"" << Distribution << "\">";
1032 VersionTuple IntroducedInVersion = AA->getIntroduced();
1033 if (!IntroducedInVersion.empty()) {
1034 Result << "<IntroducedInVersion>"
1035 << IntroducedInVersion.getAsString()
1036 << "</IntroducedInVersion>";
1037 }
1038 VersionTuple DeprecatedInVersion = AA->getDeprecated();
1039 if (!DeprecatedInVersion.empty()) {
1040 Result << "<DeprecatedInVersion>"
1041 << DeprecatedInVersion.getAsString()
1042 << "</DeprecatedInVersion>";
1043 }
1044 VersionTuple RemovedAfterVersion = AA->getObsoleted();
1045 if (!RemovedAfterVersion.empty()) {
1046 Result << "<RemovedAfterVersion>"
1047 << RemovedAfterVersion.getAsString()
1048 << "</RemovedAfterVersion>";
1049 }
1050 StringRef DeprecationSummary = AA->getMessage();
1051 if (!DeprecationSummary.empty()) {
1052 Result << "<DeprecationSummary>";
1053 appendToResultWithXMLEscaping(DeprecationSummary);
1054 Result << "</DeprecationSummary>";
1055 }
1056 if (AA->getUnavailable())
1057 Result << "<Unavailable/>";
1058 Result << "</Availability>";
1059 }
1060 }
1061
1062 {
1063 bool StartTagEmitted = false;
1064 for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) {
1065 const Comment *C = Parts.MiscBlocks[i];
1066 if (FirstParagraphIsBrief && C == Parts.FirstParagraph)
1067 continue;
1068 if (!StartTagEmitted) {
1069 Result << "<Discussion>";
1070 StartTagEmitted = true;
1071 }
1072 visit(C);
1073 }
1074 if (StartTagEmitted)
1075 Result << "</Discussion>";
1076 }
1077
1078 Result << RootEndTag;
1079 }
1080
appendToResultWithXMLEscaping(StringRef S)1081 void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) {
1082 for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
1083 const char C = *I;
1084 switch (C) {
1085 case '&':
1086 Result << "&";
1087 break;
1088 case '<':
1089 Result << "<";
1090 break;
1091 case '>':
1092 Result << ">";
1093 break;
1094 case '"':
1095 Result << """;
1096 break;
1097 case '\'':
1098 Result << "'";
1099 break;
1100 default:
1101 Result << C;
1102 break;
1103 }
1104 }
1105 }
1106
appendToResultWithCDATAEscaping(StringRef S)1107 void CommentASTToXMLConverter::appendToResultWithCDATAEscaping(StringRef S) {
1108 if (S.empty())
1109 return;
1110
1111 Result << "<![CDATA[";
1112 while (!S.empty()) {
1113 size_t Pos = S.find("]]>");
1114 if (Pos == 0) {
1115 Result << "]]]]><![CDATA[>";
1116 S = S.drop_front(3);
1117 continue;
1118 }
1119 if (Pos == StringRef::npos)
1120 Pos = S.size();
1121
1122 Result << S.substr(0, Pos);
1123
1124 S = S.drop_front(Pos);
1125 }
1126 Result << "]]>";
1127 }
1128
CommentToXMLConverter()1129 CommentToXMLConverter::CommentToXMLConverter() : FormatInMemoryUniqueId(0) {}
~CommentToXMLConverter()1130 CommentToXMLConverter::~CommentToXMLConverter() {}
1131
convertCommentToHTML(const FullComment * FC,SmallVectorImpl<char> & HTML,const ASTContext & Context)1132 void CommentToXMLConverter::convertCommentToHTML(const FullComment *FC,
1133 SmallVectorImpl<char> &HTML,
1134 const ASTContext &Context) {
1135 CommentASTToHTMLConverter Converter(FC, HTML,
1136 Context.getCommentCommandTraits());
1137 Converter.visit(FC);
1138 }
1139
convertHTMLTagNodeToText(const comments::HTMLTagComment * HTC,SmallVectorImpl<char> & Text,const ASTContext & Context)1140 void CommentToXMLConverter::convertHTMLTagNodeToText(
1141 const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text,
1142 const ASTContext &Context) {
1143 CommentASTToHTMLConverter Converter(nullptr, Text,
1144 Context.getCommentCommandTraits());
1145 Converter.visit(HTC);
1146 }
1147
convertCommentToXML(const FullComment * FC,SmallVectorImpl<char> & XML,const ASTContext & Context)1148 void CommentToXMLConverter::convertCommentToXML(const FullComment *FC,
1149 SmallVectorImpl<char> &XML,
1150 const ASTContext &Context) {
1151 if (!FormatContext || (FormatInMemoryUniqueId % 1000) == 0) {
1152 // Create a new format context, or re-create it after some number of
1153 // iterations, so the buffers don't grow too large.
1154 FormatContext.reset(new SimpleFormatContext(Context.getLangOpts()));
1155 }
1156
1157 CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(),
1158 Context.getSourceManager(), *FormatContext,
1159 FormatInMemoryUniqueId++);
1160 Converter.visit(FC);
1161 }
1162
1163