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