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