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: |
33 | 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: |
60 | 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(Depth: 0) < RHS->getIndex(Depth: 0); |
76 | |
77 | // Leave all other names in source order. |
78 | return true; |
79 | } |
80 | }; |
81 | |
82 | /// Separate parts of a FullComment. |
83 | struct { |
84 | /// Take a full comment apart and initialize members accordingly. |
85 | FullCommentParts(const FullComment *C, |
86 | const CommandTraits &Traits); |
87 | |
88 | const BlockContentComment *; |
89 | const BlockContentComment *; |
90 | const ParagraphComment *; |
91 | SmallVector<const BlockCommandComment *, 4> ; |
92 | SmallVector<const ParamCommandComment *, 8> ; |
93 | SmallVector<const TParamCommandComment *, 4> ; |
94 | llvm::TinyPtrVector<const BlockCommandComment *> ; |
95 | SmallVector<const BlockContentComment *, 8> ; |
96 | }; |
97 | |
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>(Val: Child); |
112 | if (PC->isWhitespace()) |
113 | break; |
114 | if (!FirstParagraph) |
115 | FirstParagraph = PC; |
116 | |
117 | MiscBlocks.push_back(Elt: PC); |
118 | break; |
119 | } |
120 | |
121 | case CommentKind::BlockCommandComment: { |
122 | const BlockCommandComment *BCC = cast<BlockCommandComment>(Val: Child); |
123 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: 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(Elt: BCC); |
134 | break; |
135 | } |
136 | if (Info->IsThrowsCommand) { |
137 | Exceptions.push_back(NewVal: BCC); |
138 | break; |
139 | } |
140 | MiscBlocks.push_back(Elt: BCC); |
141 | break; |
142 | } |
143 | |
144 | case CommentKind::ParamCommandComment: { |
145 | const ParamCommandComment *PCC = cast<ParamCommandComment>(Val: Child); |
146 | if (!PCC->hasParamName()) |
147 | break; |
148 | |
149 | if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph()) |
150 | break; |
151 | |
152 | Params.push_back(Elt: PCC); |
153 | break; |
154 | } |
155 | |
156 | case CommentKind::TParamCommandComment: { |
157 | const TParamCommandComment *TPCC = cast<TParamCommandComment>(Val: Child); |
158 | if (!TPCC->hasParamName()) |
159 | break; |
160 | |
161 | if (!TPCC->hasNonWhitespaceParagraph()) |
162 | break; |
163 | |
164 | TParams.push_back(Elt: TPCC); |
165 | break; |
166 | } |
167 | |
168 | case CommentKind::VerbatimBlockComment: |
169 | MiscBlocks.push_back(Elt: cast<BlockCommandComment>(Val: Child)); |
170 | break; |
171 | |
172 | case CommentKind::VerbatimLineComment: { |
173 | const VerbatimLineComment *VLC = cast<VerbatimLineComment>(Val: Child); |
174 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: VLC->getCommandID()); |
175 | if (!Info->IsDeclarationCommand) |
176 | MiscBlocks.push_back(Elt: 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(Range&: Params, C: ParamCommandCommentCompareIndex()); |
195 | llvm::stable_sort(Range&: TParams, C: TParamCommandCommentComparePosition()); |
196 | } |
197 | |
198 | void (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(Idx: 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 : |
219 | public ConstCommentVisitor<CommentASTToHTMLConverter> { |
220 | public: |
221 | /// \param Str accumulator for HTML. |
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 *; |
255 | /// Output stream for HTML. |
256 | llvm::raw_svector_ostream ; |
257 | |
258 | const CommandTraits &; |
259 | }; |
260 | } // end unnamed namespace |
261 | |
262 | void CommentASTToHTMLConverter::(const TextComment *C) { |
263 | appendToResultWithHTMLEscaping(S: C->getText()); |
264 | } |
265 | |
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(Idx: 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(S: C->getArgText(Idx: i)); |
281 | Result << " " ; |
282 | } |
283 | return; |
284 | |
285 | case InlineCommandRenderKind::Bold: |
286 | assert(C->getNumArgs() == 1); |
287 | Result << "<b>" ; |
288 | appendToResultWithHTMLEscaping(S: Arg0); |
289 | Result << "</b>" ; |
290 | return; |
291 | case InlineCommandRenderKind::Monospaced: |
292 | assert(C->getNumArgs() == 1); |
293 | Result << "<tt>" ; |
294 | appendToResultWithHTMLEscaping(S: Arg0); |
295 | Result<< "</tt>" ; |
296 | return; |
297 | case InlineCommandRenderKind::Emphasized: |
298 | assert(C->getNumArgs() == 1); |
299 | Result << "<em>" ; |
300 | appendToResultWithHTMLEscaping(S: 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 | |
310 | void CommentASTToHTMLConverter::( |
311 | const HTMLStartTagComment *C) { |
312 | printHTMLStartTagComment(C, Result); |
313 | } |
314 | |
315 | void CommentASTToHTMLConverter::( |
316 | const HTMLEndTagComment *C) { |
317 | Result << "</" << C->getTagName() << ">" ; |
318 | } |
319 | |
320 | void CommentASTToHTMLConverter::( |
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(C: *I); |
329 | } |
330 | Result << "</p>" ; |
331 | } |
332 | |
333 | void CommentASTToHTMLConverter::visitBlockCommandComment( |
334 | const BlockCommandComment *C) { |
335 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: C->getCommandID()); |
336 | if (Info->IsBriefCommand) { |
337 | Result << "<p class=\"para-brief\">" ; |
338 | visitNonStandaloneParagraphComment(C: 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: C->getParagraph()); |
346 | Result << "</p>" ; |
347 | return; |
348 | } |
349 | // We don't know anything about this command. Just render the paragraph. |
350 | visit(C: C->getParagraph()); |
351 | } |
352 | |
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(S: C->getParamNameAsWritten()); |
359 | } else { |
360 | Result << "<dt class=\"param-name-index-" |
361 | << C->getParamIndex() |
362 | << "\">" ; |
363 | appendToResultWithHTMLEscaping(S: C->getParamName(FC)); |
364 | } |
365 | } else { |
366 | Result << "<dt class=\"param-name-index-invalid\">" ; |
367 | appendToResultWithHTMLEscaping(S: 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: C->getParagraph()); |
382 | Result << "</dd>" ; |
383 | } |
384 | |
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(Depth: 0) |
391 | << "\">" ; |
392 | else |
393 | Result << "<dt class=\"tparam-name-index-other\">" ; |
394 | appendToResultWithHTMLEscaping(S: C->getParamName(FC)); |
395 | } else { |
396 | Result << "<dt class=\"tparam-name-index-invalid\">" ; |
397 | appendToResultWithHTMLEscaping(S: 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(Depth: 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: C->getParagraph()); |
413 | Result << "</dd>" ; |
414 | } |
415 | |
416 | void CommentASTToHTMLConverter::( |
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(S: C->getText(LineIdx: i)); |
425 | if (i + 1 != NumLines) |
426 | Result << '\n'; |
427 | } |
428 | Result << "</pre>" ; |
429 | } |
430 | |
431 | void CommentASTToHTMLConverter::( |
432 | const VerbatimBlockLineComment *C) { |
433 | llvm_unreachable("should not see this AST node" ); |
434 | } |
435 | |
436 | void CommentASTToHTMLConverter::( |
437 | const VerbatimLineComment *C) { |
438 | Result << "<pre>" ; |
439 | appendToResultWithHTMLEscaping(S: C->getText()); |
440 | Result << "</pre>" ; |
441 | } |
442 | |
443 | void CommentASTToHTMLConverter::(const FullComment *C) { |
444 | FullCommentParts Parts(C, Traits); |
445 | |
446 | bool FirstParagraphIsBrief = false; |
447 | if (Parts.Headerfile) |
448 | visit(C: Parts.Headerfile); |
449 | if (Parts.Brief) |
450 | visit(C: Parts.Brief); |
451 | else if (Parts.FirstParagraph) { |
452 | Result << "<p class=\"para-brief\">" ; |
453 | visitNonStandaloneParagraphComment(C: 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(C: 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(C: 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(C: Parts.Returns[i]); |
483 | Result << "</div>" ; |
484 | } |
485 | |
486 | } |
487 | |
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(C: *I); |
496 | } |
497 | } |
498 | |
499 | void CommentASTToHTMLConverter::(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 : |
530 | public ConstCommentVisitor<CommentASTToXMLConverter> { |
531 | public: |
532 | /// \param Str accumulator for XML. |
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 *; |
570 | |
571 | /// Output stream for XML. |
572 | llvm::raw_svector_ostream ; |
573 | |
574 | const CommandTraits &; |
575 | const SourceManager &; |
576 | }; |
577 | |
578 | void (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(Out&: OS, Policy: PPolicy, |
588 | /*Indentation*/0, /*PrintInstantiation*/false); |
589 | } |
590 | |
591 | void CommentASTToXMLConverter::( |
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, Code: StringDecl, Ranges: tooling::Range(Offset, Length), FileName: "xmldecl.xd" ); |
604 | auto FormattedStringDecl = applyAllReplacements(Code: StringDecl, Replaces); |
605 | if (static_cast<bool>(FormattedStringDecl)) { |
606 | Declaration = *FormattedStringDecl; |
607 | } |
608 | } |
609 | |
610 | } // end unnamed namespace |
611 | |
612 | void CommentASTToXMLConverter::(const TextComment *C) { |
613 | appendToResultWithXMLEscaping(S: C->getText()); |
614 | } |
615 | |
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(Idx: 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(S: C->getArgText(Idx: i)); |
631 | Result << " " ; |
632 | } |
633 | return; |
634 | case InlineCommandRenderKind::Bold: |
635 | assert(C->getNumArgs() == 1); |
636 | Result << "<bold>" ; |
637 | appendToResultWithXMLEscaping(S: Arg0); |
638 | Result << "</bold>" ; |
639 | return; |
640 | case InlineCommandRenderKind::Monospaced: |
641 | assert(C->getNumArgs() == 1); |
642 | Result << "<monospaced>" ; |
643 | appendToResultWithXMLEscaping(S: Arg0); |
644 | Result << "</monospaced>" ; |
645 | return; |
646 | case InlineCommandRenderKind::Emphasized: |
647 | assert(C->getNumArgs() == 1); |
648 | Result << "<emphasized>" ; |
649 | appendToResultWithXMLEscaping(S: 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 | |
659 | void CommentASTToXMLConverter::( |
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, Result&: TagOS); |
670 | } |
671 | appendToResultWithCDATAEscaping(S: Tag); |
672 | } |
673 | Result << "</rawHTML>" ; |
674 | } |
675 | |
676 | void |
677 | CommentASTToXMLConverter::(const HTMLEndTagComment *C) { |
678 | Result << "<rawHTML" ; |
679 | if (C->isMalformed()) |
680 | Result << " isMalformed=\"1\"" ; |
681 | Result << "></" << C->getTagName() << "></rawHTML>" ; |
682 | } |
683 | |
684 | void CommentASTToXMLConverter::( |
685 | const ParagraphComment *C) { |
686 | appendParagraphCommentWithKind(C, ParagraphKind: StringRef(), PrependBodyText: StringRef()); |
687 | } |
688 | |
689 | void CommentASTToXMLConverter::( |
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(C: *I); |
706 | } |
707 | Result << "</Para>" ; |
708 | } |
709 | |
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(Idx: 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: C->getParagraph(), ParagraphKind, |
747 | PrependBodyText: ExceptionType); |
748 | } |
749 | |
750 | void CommentASTToXMLConverter::visitParamCommandComment( |
751 | const ParamCommandComment *C) { |
752 | Result << "<Parameter><Name>" ; |
753 | appendToResultWithXMLEscaping(S: 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: C->getParagraph()); |
779 | Result << "</Discussion></Parameter>" ; |
780 | } |
781 | |
782 | void CommentASTToXMLConverter::visitTParamCommandComment( |
783 | const TParamCommandComment *C) { |
784 | Result << "<Parameter><Name>" ; |
785 | appendToResultWithXMLEscaping(S: C->isPositionValid() ? C->getParamName(FC) |
786 | : C->getParamNameAsWritten()); |
787 | Result << "</Name>" ; |
788 | |
789 | if (C->isPositionValid() && C->getDepth() == 1) { |
790 | Result << "<Index>" << C->getIndex(Depth: 0) << "</Index>" ; |
791 | } |
792 | |
793 | Result << "<Discussion>" ; |
794 | visit(C: C->getParagraph()); |
795 | Result << "</Discussion></Parameter>" ; |
796 | } |
797 | |
798 | void CommentASTToXMLConverter::( |
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(S: C->getText(LineIdx: i)); |
814 | if (i + 1 != NumLines) |
815 | Result << '\n'; |
816 | } |
817 | Result << "</Verbatim>" ; |
818 | } |
819 | |
820 | void CommentASTToXMLConverter::( |
821 | const VerbatimBlockLineComment *C) { |
822 | llvm_unreachable("should not see this AST node" ); |
823 | } |
824 | |
825 | void CommentASTToXMLConverter::( |
826 | const VerbatimLineComment *C) { |
827 | Result << "<Verbatim xml:space=\"preserve\" kind=\"verbatim\">" ; |
828 | appendToResultWithXMLEscaping(S: C->getText()); |
829 | Result << "</Verbatim>" ; |
830 | } |
831 | |
832 | void CommentASTToXMLConverter::(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(S: FE->getName()); |
910 | Result << "\"" ; |
911 | } |
912 | Result << " line=\"" << SM.getLineNumber(FID, FilePos: FileOffset) |
913 | << "\" column=\"" << SM.getColumnNumber(FID, FilePos: 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>(Val: DI->CommentDecl)) { |
923 | if (DeclarationName DeclName = ND->getDeclName()) { |
924 | Result << "<Name>" ; |
925 | std::string Name = DeclName.getAsString(); |
926 | appendToResultWithXMLEscaping(S: 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(D: DI->CommentDecl, Buf&: USR); |
938 | if (!USR.empty()) { |
939 | Result << "<USR>" ; |
940 | appendToResultWithXMLEscaping(S: 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(C: Parts.Headerfile); |
953 | Result << "</Headerfile>" ; |
954 | } |
955 | |
956 | { |
957 | // Pretty-print the declaration. |
958 | Result << "<Declaration>" ; |
959 | SmallString<128> Declaration; |
960 | getSourceTextOfDeclaration(ThisDecl: DI, Str&: Declaration); |
961 | formatTextOfDeclaration(DI, Declaration); |
962 | appendToResultWithXMLEscaping(S: Declaration); |
963 | Result << "</Declaration>" ; |
964 | } |
965 | |
966 | bool FirstParagraphIsBrief = false; |
967 | if (Parts.Brief) { |
968 | Result << "<Abstract>" ; |
969 | visit(C: Parts.Brief); |
970 | Result << "</Abstract>" ; |
971 | } else if (Parts.FirstParagraph) { |
972 | Result << "<Abstract>" ; |
973 | visit(C: 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(C: 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(C: 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(C: 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(C: 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>(Val: Attrs[i]); |
1010 | if (!AA) { |
1011 | if (const DeprecatedAttr *DA = dyn_cast<DeprecatedAttr>(Val: Attrs[i])) { |
1012 | if (DA->getMessage().empty()) |
1013 | Result << "<Deprecated/>" ; |
1014 | else { |
1015 | Result << "<Deprecated>" ; |
1016 | appendToResultWithXMLEscaping(S: DA->getMessage()); |
1017 | Result << "</Deprecated>" ; |
1018 | } |
1019 | } |
1020 | else if (const UnavailableAttr *UA = dyn_cast<UnavailableAttr>(Val: Attrs[i])) { |
1021 | if (UA->getMessage().empty()) |
1022 | Result << "<Unavailable/>" ; |
1023 | else { |
1024 | Result << "<Unavailable>" ; |
1025 | appendToResultWithXMLEscaping(S: 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 | Platform: 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(S: 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 | |
1096 | void CommentASTToXMLConverter::(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 | |
1122 | void CommentASTToXMLConverter::(StringRef S) { |
1123 | if (S.empty()) |
1124 | return; |
1125 | |
1126 | Result << "<![CDATA[" ; |
1127 | while (!S.empty()) { |
1128 | size_t Pos = S.find(Str: "]]>" ); |
1129 | if (Pos == 0) { |
1130 | Result << "]]]]><![CDATA[>" ; |
1131 | S = S.drop_front(N: 3); |
1132 | continue; |
1133 | } |
1134 | if (Pos == StringRef::npos) |
1135 | Pos = S.size(); |
1136 | |
1137 | Result << S.substr(Start: 0, N: Pos); |
1138 | |
1139 | S = S.drop_front(N: Pos); |
1140 | } |
1141 | Result << "]]>" ; |
1142 | } |
1143 | |
1144 | CommentToXMLConverter::() {} |
1145 | CommentToXMLConverter::() {} |
1146 | |
1147 | void CommentToXMLConverter::(const FullComment *FC, |
1148 | SmallVectorImpl<char> &HTML, |
1149 | const ASTContext &Context) { |
1150 | CommentASTToHTMLConverter Converter(FC, HTML, |
1151 | Context.getCommentCommandTraits()); |
1152 | Converter.visit(C: FC); |
1153 | } |
1154 | |
1155 | void CommentToXMLConverter::( |
1156 | const comments::HTMLTagComment *HTC, SmallVectorImpl<char> &Text, |
1157 | const ASTContext &Context) { |
1158 | CommentASTToHTMLConverter Converter(nullptr, Text, |
1159 | Context.getCommentCommandTraits()); |
1160 | Converter.visit(C: HTC); |
1161 | } |
1162 | |
1163 | void CommentToXMLConverter::(const FullComment *FC, |
1164 | SmallVectorImpl<char> &XML, |
1165 | const ASTContext &Context) { |
1166 | CommentASTToXMLConverter Converter(FC, XML, Context.getCommentCommandTraits(), |
1167 | Context.getSourceManager()); |
1168 | Converter.visit(C: FC); |
1169 | } |
1170 | |