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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/lib/Index/CommentToXML.cpp