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