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