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/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 | |
22 | using namespace clang; |
23 | using namespace clang::comments; |
24 | using namespace clang::index; |
25 | |
26 | namespace { |
27 | |
28 | /// This comparison will sort parameters with valid index by index, then vararg |
29 | /// parameters, and invalid (unresolved) parameters last. |
30 | class ParamCommandCommentCompareIndex { |
31 | public: |
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. |
57 | class TParamCommandCommentComparePosition { |
58 | public: |
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. |
82 | struct { |
83 | /// Take a full comment apart and initialize members accordingly. |
84 | FullCommentParts(const FullComment *C, |
85 | const CommandTraits &Traits); |
86 | |
87 | const BlockContentComment *; |
88 | const BlockContentComment *; |
89 | const ParagraphComment *; |
90 | SmallVector<const BlockCommandComment *, 4> ; |
91 | SmallVector<const ParamCommandComment *, 8> ; |
92 | SmallVector<const TParamCommandComment *, 4> ; |
93 | llvm::TinyPtrVector<const BlockCommandComment *> ; |
94 | SmallVector<const BlockContentComment *, 8> ; |
95 | }; |
96 | |
97 | FullCommentParts::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 | |
197 | void (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 | |
217 | class : |
218 | public ConstCommentVisitor<CommentASTToHTMLConverter> { |
219 | public: |
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 | |
252 | private: |
253 | const FullComment *; |
254 | /// Output stream for HTML. |
255 | llvm::raw_svector_ostream ; |
256 | |
257 | const CommandTraits &; |
258 | }; |
259 | } // end unnamed namespace |
260 | |
261 | void CommentASTToHTMLConverter::(const TextComment *C) { |
262 | appendToResultWithHTMLEscaping(S: C->getText()); |
263 | } |
264 | |
265 | void 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 | |
309 | void CommentASTToHTMLConverter::( |
310 | const HTMLStartTagComment *C) { |
311 | printHTMLStartTagComment(C, Result); |
312 | } |
313 | |
314 | void CommentASTToHTMLConverter::( |
315 | const HTMLEndTagComment *C) { |
316 | Result << "</" << C->getTagName() << ">" ; |
317 | } |
318 | |
319 | void CommentASTToHTMLConverter::( |
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 | |
332 | void 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 | |
352 | void 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 | |
384 | void 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 | |
415 | void CommentASTToHTMLConverter::( |
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 | |
430 | void CommentASTToHTMLConverter::( |
431 | const VerbatimBlockLineComment *C) { |
432 | llvm_unreachable("should not see this AST node" ); |
433 | } |
434 | |
435 | void CommentASTToHTMLConverter::( |
436 | const VerbatimLineComment *C) { |
437 | Result << "<pre>" ; |
438 | appendToResultWithHTMLEscaping(S: C->getText()); |
439 | Result << "</pre>" ; |
440 | } |
441 | |
442 | void CommentASTToHTMLConverter::(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 | |
487 | void 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 | |
498 | void CommentASTToHTMLConverter::(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 << "&" ; |
504 | break; |
505 | case '<': |
506 | Result << "<" ; |
507 | break; |
508 | case '>': |
509 | Result << ">" ; |
510 | break; |
511 | case '"': |
512 | Result << """ ; |
513 | break; |
514 | case '\'': |
515 | Result << "'" ; |
516 | break; |
517 | case '/': |
518 | Result << "/" ; |
519 | break; |
520 | default: |
521 | Result << C; |
522 | break; |
523 | } |
524 | } |
525 | } |
526 | |
527 | namespace { |
528 | class : |
529 | public ConstCommentVisitor<CommentASTToXMLConverter> { |
530 | public: |
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 Kind); |
549 | |
550 | void visitBlockCommandComment(const BlockCommandComment *C); |
551 | void visitParamCommandComment(const ParamCommandComment *C); |
552 | void visitTParamCommandComment(const TParamCommandComment *C); |
553 | void visitVerbatimBlockComment(const VerbatimBlockComment *C); |
554 | void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); |
555 | void visitVerbatimLineComment(const VerbatimLineComment *C); |
556 | |
557 | void visitFullComment(const FullComment *C); |
558 | |
559 | // Helpers. |
560 | void appendToResultWithXMLEscaping(StringRef S); |
561 | void appendToResultWithCDATAEscaping(StringRef S); |
562 | |
563 | void formatTextOfDeclaration(const DeclInfo *DI, |
564 | SmallString<128> &Declaration); |
565 | |
566 | private: |
567 | const FullComment *; |
568 | |
569 | /// Output stream for XML. |
570 | llvm::raw_svector_ostream ; |
571 | |
572 | const CommandTraits &; |
573 | const SourceManager &; |
574 | }; |
575 | |
576 | void (const DeclInfo *ThisDecl, |
577 | SmallVectorImpl<char> &Str) { |
578 | ASTContext &Context = ThisDecl->CurrentDecl->getASTContext(); |
579 | const LangOptions &LangOpts = Context.getLangOpts(); |
580 | llvm::raw_svector_ostream OS(Str); |
581 | PrintingPolicy PPolicy(LangOpts); |
582 | PPolicy.PolishForDeclaration = true; |
583 | PPolicy.TerseOutput = true; |
584 | PPolicy.ConstantsAsWritten = true; |
585 | ThisDecl->CurrentDecl->print(Out&: OS, Policy: PPolicy, |
586 | /*Indentation*/0, /*PrintInstantiation*/false); |
587 | } |
588 | |
589 | void CommentASTToXMLConverter::( |
590 | const DeclInfo *DI, SmallString<128> &Declaration) { |
591 | // Formatting API expects null terminated input string. |
592 | StringRef StringDecl(Declaration.c_str(), Declaration.size()); |
593 | |
594 | // Formatter specific code. |
595 | unsigned Offset = 0; |
596 | unsigned Length = Declaration.size(); |
597 | |
598 | format::FormatStyle Style = format::getLLVMStyle(); |
599 | Style.FixNamespaceComments = false; |
600 | tooling::Replacements Replaces = |
601 | reformat(Style, Code: StringDecl, Ranges: tooling::Range(Offset, Length), FileName: "xmldecl.xd" ); |
602 | auto FormattedStringDecl = applyAllReplacements(Code: StringDecl, Replaces); |
603 | if (static_cast<bool>(FormattedStringDecl)) { |
604 | Declaration = *FormattedStringDecl; |
605 | } |
606 | } |
607 | |
608 | } // end unnamed namespace |
609 | |
610 | void CommentASTToXMLConverter::(const TextComment *C) { |
611 | appendToResultWithXMLEscaping(S: C->getText()); |
612 | } |
613 | |
614 | void CommentASTToXMLConverter::visitInlineCommandComment( |
615 | const InlineCommandComment *C) { |
616 | // Nothing to render if no arguments supplied. |
617 | if (C->getNumArgs() == 0) |
618 | return; |
619 | |
620 | // Nothing to render if argument is empty. |
621 | StringRef Arg0 = C->getArgText(Idx: 0); |
622 | if (Arg0.empty()) |
623 | return; |
624 | |
625 | switch (C->getRenderKind()) { |
626 | case InlineCommandRenderKind::Normal: |
627 | for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) { |
628 | appendToResultWithXMLEscaping(S: C->getArgText(Idx: i)); |
629 | Result << " " ; |
630 | } |
631 | return; |
632 | case InlineCommandRenderKind::Bold: |
633 | assert(C->getNumArgs() == 1); |
634 | Result << "<bold>" ; |
635 | appendToResultWithXMLEscaping(S: Arg0); |
636 | Result << "</bold>" ; |
637 | return; |
638 | case InlineCommandRenderKind::Monospaced: |
639 | assert(C->getNumArgs() == 1); |
640 | Result << "<monospaced>" ; |
641 | appendToResultWithXMLEscaping(S: Arg0); |
642 | Result << "</monospaced>" ; |
643 | return; |
644 | case InlineCommandRenderKind::Emphasized: |
645 | assert(C->getNumArgs() == 1); |
646 | Result << "<emphasized>" ; |
647 | appendToResultWithXMLEscaping(S: Arg0); |
648 | Result << "</emphasized>" ; |
649 | return; |
650 | case InlineCommandRenderKind::Anchor: |
651 | assert(C->getNumArgs() == 1); |
652 | Result << "<anchor id=\"" << Arg0 << "\"></anchor>" ; |
653 | return; |
654 | } |
655 | } |
656 | |
657 | void CommentASTToXMLConverter::( |
658 | const HTMLStartTagComment *C) { |
659 | Result << "<rawHTML" ; |
660 | if (C->isMalformed()) |
661 | Result << " isMalformed=\"1\"" ; |
662 | Result << ">" ; |
663 | { |
664 | SmallString<32> Tag; |
665 | { |
666 | llvm::raw_svector_ostream TagOS(Tag); |
667 | printHTMLStartTagComment(C, Result&: TagOS); |
668 | } |
669 | appendToResultWithCDATAEscaping(S: Tag); |
670 | } |
671 | Result << "</rawHTML>" ; |
672 | } |
673 | |
674 | void |
675 | CommentASTToXMLConverter::(const HTMLEndTagComment *C) { |
676 | Result << "<rawHTML" ; |
677 | if (C->isMalformed()) |
678 | Result << " isMalformed=\"1\"" ; |
679 | Result << "></" << C->getTagName() << "></rawHTML>" ; |
680 | } |
681 | |
682 | void |
683 | CommentASTToXMLConverter::(const ParagraphComment *C) { |
684 | appendParagraphCommentWithKind(C, Kind: StringRef()); |
685 | } |
686 | |
687 | void CommentASTToXMLConverter::( |
688 | const ParagraphComment *C, |
689 | StringRef ParagraphKind) { |
690 | if (C->isWhitespace()) |
691 | return; |
692 | |
693 | if (ParagraphKind.empty()) |
694 | Result << "<Para>" ; |
695 | else |
696 | Result << "<Para kind=\"" << ParagraphKind << "\">" ; |
697 | |
698 | for (Comment::child_iterator I = C->child_begin(), E = C->child_end(); |
699 | I != E; ++I) { |
700 | visit(*I); |
701 | } |
702 | Result << "</Para>" ; |
703 | } |
704 | |
705 | void CommentASTToXMLConverter::visitBlockCommandComment( |
706 | const BlockCommandComment *C) { |
707 | StringRef ParagraphKind; |
708 | |
709 | switch (C->getCommandID()) { |
710 | case CommandTraits::KCI_attention: |
711 | case CommandTraits::KCI_author: |
712 | case CommandTraits::KCI_authors: |
713 | case CommandTraits::KCI_bug: |
714 | case CommandTraits::KCI_copyright: |
715 | case CommandTraits::KCI_date: |
716 | case CommandTraits::KCI_invariant: |
717 | case CommandTraits::KCI_note: |
718 | case CommandTraits::KCI_post: |
719 | case CommandTraits::KCI_pre: |
720 | case CommandTraits::KCI_remark: |
721 | case CommandTraits::KCI_remarks: |
722 | case CommandTraits::KCI_sa: |
723 | case CommandTraits::KCI_see: |
724 | case CommandTraits::KCI_since: |
725 | case CommandTraits::KCI_todo: |
726 | case CommandTraits::KCI_version: |
727 | case CommandTraits::KCI_warning: |
728 | ParagraphKind = C->getCommandName(Traits); |
729 | break; |
730 | default: |
731 | break; |
732 | } |
733 | |
734 | appendParagraphCommentWithKind(C: C->getParagraph(), ParagraphKind); |
735 | } |
736 | |
737 | void CommentASTToXMLConverter::visitParamCommandComment( |
738 | const ParamCommandComment *C) { |
739 | Result << "<Parameter><Name>" ; |
740 | appendToResultWithXMLEscaping(S: C->isParamIndexValid() |
741 | ? C->getParamName(FC) |
742 | : C->getParamNameAsWritten()); |
743 | Result << "</Name>" ; |
744 | |
745 | if (C->isParamIndexValid()) { |
746 | if (C->isVarArgParam()) |
747 | Result << "<IsVarArg />" ; |
748 | else |
749 | Result << "<Index>" << C->getParamIndex() << "</Index>" ; |
750 | } |
751 | |
752 | Result << "<Direction isExplicit=\"" << C->isDirectionExplicit() << "\">" ; |
753 | switch (C->getDirection()) { |
754 | case ParamCommandPassDirection::In: |
755 | Result << "in" ; |
756 | break; |
757 | case ParamCommandPassDirection::Out: |
758 | Result << "out" ; |
759 | break; |
760 | case ParamCommandPassDirection::InOut: |
761 | Result << "in,out" ; |
762 | break; |
763 | } |
764 | Result << "</Direction><Discussion>" ; |
765 | visit(C->getParagraph()); |
766 | Result << "</Discussion></Parameter>" ; |
767 | } |
768 | |
769 | void CommentASTToXMLConverter::visitTParamCommandComment( |
770 | const TParamCommandComment *C) { |
771 | Result << "<Parameter><Name>" ; |
772 | appendToResultWithXMLEscaping(S: C->isPositionValid() ? C->getParamName(FC) |
773 | : C->getParamNameAsWritten()); |
774 | Result << "</Name>" ; |
775 | |
776 | if (C->isPositionValid() && C->getDepth() == 1) { |
777 | Result << "<Index>" << C->getIndex(Depth: 0) << "</Index>" ; |
778 | } |
779 | |
780 | Result << "<Discussion>" ; |
781 | visit(C->getParagraph()); |
782 | Result << "</Discussion></Parameter>" ; |
783 | } |
784 | |
785 | void CommentASTToXMLConverter::( |
786 | const VerbatimBlockComment *C) { |
787 | unsigned NumLines = C->getNumLines(); |
788 | if (NumLines == 0) |
789 | return; |
790 | |
791 | switch (C->getCommandID()) { |
792 | case CommandTraits::KCI_code: |
793 | Result << "<Verbatim xml:space=\"preserve\" kind=\"code\">" ; |
794 | break; |
795 | default: |
796 | Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">" ; |
797 | break; |
798 | } |
799 | for (unsigned i = 0; i != NumLines; ++i) { |
800 | appendToResultWithXMLEscaping(S: C->getText(LineIdx: i)); |
801 | if (i + 1 != NumLines) |
802 | Result << '\n'; |
803 | } |
804 | Result << "</Verbatim>" ; |
805 | } |
806 | |
807 | void CommentASTToXMLConverter::( |
808 | const VerbatimBlockLineComment *C) { |
809 | llvm_unreachable("should not see this AST node" ); |
810 | } |
811 | |
812 | void CommentASTToXMLConverter::( |
813 | const VerbatimLineComment *C) { |
814 | Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">" ; |
815 | appendToResultWithXMLEscaping(S: C->getText()); |
816 | Result << "</Verbatim>" ; |
817 | } |
818 | |
819 | void CommentASTToXMLConverter::(const FullComment *C) { |
820 | FullCommentParts Parts(C, Traits); |
821 | |
822 | const DeclInfo *DI = C->getDeclInfo(); |
823 | StringRef RootEndTag; |
824 | if (DI) { |
825 | switch (DI->getKind()) { |
826 | case DeclInfo::OtherKind: |
827 | RootEndTag = "</Other>" ; |
828 | Result << "<Other" ; |
829 | break; |
830 | case DeclInfo::FunctionKind: |
831 | RootEndTag = "</Function>" ; |
832 | Result << "<Function" ; |
833 | switch (DI->TemplateKind) { |
834 | case DeclInfo::NotTemplate: |
835 | break; |
836 | case DeclInfo::Template: |
837 | Result << " templateKind=\"template\"" ; |
838 | break; |
839 | case DeclInfo::TemplateSpecialization: |
840 | Result << " templateKind=\"specialization\"" ; |
841 | break; |
842 | case DeclInfo::TemplatePartialSpecialization: |
843 | llvm_unreachable("partial specializations of functions " |
844 | "are not allowed in C++" ); |
845 | } |
846 | if (DI->IsInstanceMethod) |
847 | Result << " isInstanceMethod=\"1\"" ; |
848 | if (DI->IsClassMethod) |
849 | Result << " isClassMethod=\"1\"" ; |
850 | break; |
851 | case DeclInfo::ClassKind: |
852 | RootEndTag = "</Class>" ; |
853 | Result << "<Class" ; |
854 | switch (DI->TemplateKind) { |
855 | case DeclInfo::NotTemplate: |
856 | break; |
857 | case DeclInfo::Template: |
858 | Result << " templateKind=\"template\"" ; |
859 | break; |
860 | case DeclInfo::TemplateSpecialization: |
861 | Result << " templateKind=\"specialization\"" ; |
862 | break; |
863 | case DeclInfo::TemplatePartialSpecialization: |
864 | Result << " templateKind=\"partialSpecialization\"" ; |
865 | break; |
866 | } |
867 | break; |
868 | case DeclInfo::VariableKind: |
869 | RootEndTag = "</Variable>" ; |
870 | Result << "<Variable" ; |
871 | break; |
872 | case DeclInfo::NamespaceKind: |
873 | RootEndTag = "</Namespace>" ; |
874 | Result << "<Namespace" ; |
875 | break; |
876 | case DeclInfo::TypedefKind: |
877 | RootEndTag = "</Typedef>" ; |
878 | Result << "<Typedef" ; |
879 | break; |
880 | case DeclInfo::EnumKind: |
881 | RootEndTag = "</Enum>" ; |
882 | Result << "<Enum" ; |
883 | break; |
884 | } |
885 | |
886 | { |
887 | // Print line and column number. |
888 | SourceLocation Loc = DI->CurrentDecl->getLocation(); |
889 | std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
890 | FileID FID = LocInfo.first; |
891 | unsigned FileOffset = LocInfo.second; |
892 | |
893 | if (FID.isValid()) { |
894 | if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID)) { |
895 | Result << " file=\"" ; |
896 | appendToResultWithXMLEscaping(S: FE->getName()); |
897 | Result << "\"" ; |
898 | } |
899 | Result << " line=\"" << SM.getLineNumber(FID, FilePos: FileOffset) |
900 | << "\" column=\"" << SM.getColumnNumber(FID, FilePos: FileOffset) |
901 | << "\"" ; |
902 | } |
903 | } |
904 | |
905 | // Finish the root tag. |
906 | Result << ">" ; |
907 | |
908 | bool FoundName = false; |
909 | if (const NamedDecl *ND = dyn_cast<NamedDecl>(Val: DI->CommentDecl)) { |
910 | if (DeclarationName DeclName = ND->getDeclName()) { |
911 | Result << "<Name>" ; |
912 | std::string Name = DeclName.getAsString(); |
913 | appendToResultWithXMLEscaping(S: Name); |
914 | FoundName = true; |
915 | Result << "</Name>" ; |
916 | } |
917 | } |
918 | if (!FoundName) |
919 | Result << "<Name><anonymous></Name>" ; |
920 | |
921 | { |
922 | // Print USR. |
923 | SmallString<128> USR; |
924 | generateUSRForDecl(D: DI->CommentDecl, Buf&: USR); |
925 | if (!USR.empty()) { |
926 | Result << "<USR>" ; |
927 | appendToResultWithXMLEscaping(S: USR); |
928 | Result << "</USR>" ; |
929 | } |
930 | } |
931 | } else { |
932 | // No DeclInfo -- just emit some root tag and name tag. |
933 | RootEndTag = "</Other>" ; |
934 | Result << "<Other><Name>unknown</Name>" ; |
935 | } |
936 | |
937 | if (Parts.Headerfile) { |
938 | Result << "<Headerfile>" ; |
939 | visit(Parts.Headerfile); |
940 | Result << "</Headerfile>" ; |
941 | } |
942 | |
943 | { |
944 | // Pretty-print the declaration. |
945 | Result << "<Declaration>" ; |
946 | SmallString<128> Declaration; |
947 | getSourceTextOfDeclaration(ThisDecl: DI, Str&: Declaration); |
948 | formatTextOfDeclaration(DI, Declaration); |
949 | appendToResultWithXMLEscaping(S: Declaration); |
950 | Result << "</Declaration>" ; |
951 | } |
952 | |
953 | bool FirstParagraphIsBrief = false; |
954 | if (Parts.Brief) { |
955 | Result << "<Abstract>" ; |
956 | visit(Parts.Brief); |
957 | Result << "</Abstract>" ; |
958 | } else if (Parts.FirstParagraph) { |
959 | Result << "<Abstract>" ; |
960 | visit(Parts.FirstParagraph); |
961 | Result << "</Abstract>" ; |
962 | FirstParagraphIsBrief = true; |
963 | } |
964 | |
965 | if (Parts.TParams.size() != 0) { |
966 | Result << "<TemplateParameters>" ; |
967 | for (unsigned i = 0, e = Parts.TParams.size(); i != e; ++i) |
968 | visit(Parts.TParams[i]); |
969 | Result << "</TemplateParameters>" ; |
970 | } |
971 | |
972 | if (Parts.Params.size() != 0) { |
973 | Result << "<Parameters>" ; |
974 | for (unsigned i = 0, e = Parts.Params.size(); i != e; ++i) |
975 | visit(Parts.Params[i]); |
976 | Result << "</Parameters>" ; |
977 | } |
978 | |
979 | if (Parts.Exceptions.size() != 0) { |
980 | Result << "<Exceptions>" ; |
981 | for (unsigned i = 0, e = Parts.Exceptions.size(); i != e; ++i) |
982 | visit(Parts.Exceptions[i]); |
983 | Result << "</Exceptions>" ; |
984 | } |
985 | |
986 | if (Parts.Returns.size() != 0) { |
987 | Result << "<ResultDiscussion>" ; |
988 | for (unsigned i = 0, e = Parts.Returns.size(); i != e; ++i) |
989 | visit(Parts.Returns[i]); |
990 | Result << "</ResultDiscussion>" ; |
991 | } |
992 | |
993 | if (DI->CommentDecl->hasAttrs()) { |
994 | const AttrVec &Attrs = DI->CommentDecl->getAttrs(); |
995 | for (unsigned i = 0, e = Attrs.size(); i != e; i++) { |
996 | const AvailabilityAttr *AA = dyn_cast<AvailabilityAttr>(Attrs[i]); |
997 | if (!AA) { |
998 | if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Attrs[i])) { |
999 | if (DA->getMessage().empty()) |
1000 | Result << "<Deprecated/>" ; |
1001 | else { |
1002 | Result << "<Deprecated>" ; |
1003 | appendToResultWithXMLEscaping(S: DA->getMessage()); |
1004 | Result << "</Deprecated>" ; |
1005 | } |
1006 | } |
1007 | else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Attrs[i])) { |
1008 | if (UA->getMessage().empty()) |
1009 | Result << "<Unavailable/>" ; |
1010 | else { |
1011 | Result << "<Unavailable>" ; |
1012 | appendToResultWithXMLEscaping(S: UA->getMessage()); |
1013 | Result << "</Unavailable>" ; |
1014 | } |
1015 | } |
1016 | continue; |
1017 | } |
1018 | |
1019 | // 'availability' attribute. |
1020 | Result << "<Availability" ; |
1021 | StringRef Distribution; |
1022 | if (AA->getPlatform()) { |
1023 | Distribution = AvailabilityAttr::getPrettyPlatformName( |
1024 | AA->getPlatform()->getName()); |
1025 | if (Distribution.empty()) |
1026 | Distribution = AA->getPlatform()->getName(); |
1027 | } |
1028 | Result << " distribution=\"" << Distribution << "\">" ; |
1029 | VersionTuple IntroducedInVersion = AA->getIntroduced(); |
1030 | if (!IntroducedInVersion.empty()) { |
1031 | Result << "<IntroducedInVersion>" |
1032 | << IntroducedInVersion.getAsString() |
1033 | << "</IntroducedInVersion>" ; |
1034 | } |
1035 | VersionTuple DeprecatedInVersion = AA->getDeprecated(); |
1036 | if (!DeprecatedInVersion.empty()) { |
1037 | Result << "<DeprecatedInVersion>" |
1038 | << DeprecatedInVersion.getAsString() |
1039 | << "</DeprecatedInVersion>" ; |
1040 | } |
1041 | VersionTuple RemovedAfterVersion = AA->getObsoleted(); |
1042 | if (!RemovedAfterVersion.empty()) { |
1043 | Result << "<RemovedAfterVersion>" |
1044 | << RemovedAfterVersion.getAsString() |
1045 | << "</RemovedAfterVersion>" ; |
1046 | } |
1047 | StringRef DeprecationSummary = AA->getMessage(); |
1048 | if (!DeprecationSummary.empty()) { |
1049 | Result << "<DeprecationSummary>" ; |
1050 | appendToResultWithXMLEscaping(S: DeprecationSummary); |
1051 | Result << "</DeprecationSummary>" ; |
1052 | } |
1053 | if (AA->getUnavailable()) |
1054 | Result << "<Unavailable/>" ; |
1055 | Result << "</Availability>" ; |
1056 | } |
1057 | } |
1058 | |
1059 | { |
1060 | bool StartTagEmitted = false; |
1061 | for (unsigned i = 0, e = Parts.MiscBlocks.size(); i != e; ++i) { |
1062 | const Comment *C = Parts.MiscBlocks[i]; |
1063 | if (FirstParagraphIsBrief && C == Parts.FirstParagraph) |
1064 | continue; |
1065 | if (!StartTagEmitted) { |
1066 | Result << "<Discussion>" ; |
1067 | StartTagEmitted = true; |
1068 | } |
1069 | visit(C); |
1070 | } |
1071 | if (StartTagEmitted) |
1072 | Result << "</Discussion>" ; |
1073 | } |
1074 | |
1075 | Result << RootEndTag; |
1076 | } |
1077 | |
1078 | void CommentASTToXMLConverter::(StringRef S) { |
1079 | for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { |
1080 | const char C = *I; |
1081 | switch (C) { |
1082 | case '&': |
1083 | Result << "&" ; |
1084 | break; |
1085 | case '<': |
1086 | Result << "<" ; |
1087 | break; |
1088 | case '>': |
1089 | Result << ">" ; |
1090 | break; |
1091 | case '"': |
1092 | Result << """ ; |
1093 | break; |
1094 | case '\'': |
1095 | Result << "'" ; |
1096 | break; |
1097 | default: |
1098 | Result << C; |
1099 | break; |
1100 | } |
1101 | } |
1102 | } |
1103 | |
1104 | void CommentASTToXMLConverter::(StringRef S) { |
1105 | if (S.empty()) |
1106 | return; |
1107 | |
1108 | Result << "<![CDATA[" ; |
1109 | while (!S.empty()) { |
1110 | size_t Pos = S.find(Str: "]]>" ); |
1111 | if (Pos == 0) { |
1112 | Result << "]]]]><![CDATA[>" ; |
1113 | S = S.drop_front(N: 3); |
1114 | continue; |
1115 | } |
1116 | if (Pos == StringRef::npos) |
1117 | Pos = S.size(); |
1118 | |
1119 | Result << S.substr(Start: 0, N: Pos); |
1120 | |
1121 | S = S.drop_front(N: Pos); |
1122 | } |
1123 | Result << "]]>" ; |
1124 | } |
1125 | |
1126 | CommentToXMLConverter::() {} |
1127 | CommentToXMLConverter::() {} |
1128 | |
1129 | void CommentToXMLConverter::(const FullComment *FC, |
1130 | SmallVectorImpl<char> &HTML, |
1131 | const ASTContext &Context) { |
1132 | CommentASTToHTMLConverter Converter(FC, HTML, |
1133 | Context.getCommentCommandTraits()); |
1134 | Converter.visit(FC); |
1135 | } |
1136 | |
1137 | void CommentToXMLConverter::( |
1138 | const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text, |
1139 | const ASTContext &Context) { |
1140 | CommentASTToHTMLConverter Converter(nullptr, Text, |
1141 | Context.getCommentCommandTraits()); |
1142 | Converter.visit(HTC); |
1143 | } |
1144 | |
1145 | void CommentToXMLConverter::(const FullComment *FC, |
1146 | SmallVectorImpl<char> &XML, |
1147 | const ASTContext &Context) { |
1148 | CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), |
1149 | Context.getSourceManager()); |
1150 | Converter.visit(FC); |
1151 | } |
1152 | |