clang-tools 24.0.0git
SymbolDocumentationTests.cpp
Go to the documentation of this file.
1//===-- SymbolDocumentationTests.cpp --------------------------------------===//
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//===----------------------------------------------------------------------===//
9
10#include "support/Markup.h"
11#include "clang/Basic/CommentOptions.h"
12#include "llvm/ADT/StringRef.h"
13#include "llvm/Support/raw_ostream.h"
14#include "gtest/gtest.h"
15
16namespace clang {
17namespace clangd {
18
19TEST(SymbolDocumentation, DetailedDocToMarkup) {
20
21 CommentOptions CommentOpts;
22
23 struct Case {
24 llvm::StringRef Documentation;
25 llvm::StringRef ExpectedRenderEscapedMarkdown;
26 llvm::StringRef ExpectedRenderMarkdown;
27 llvm::StringRef ExpectedRenderPlainText;
28 } Cases[] = {
29 {
30 "brief\n\nfoo bar",
31 "foo bar",
32 "foo bar",
33 "foo bar",
34 },
35 {
36 "brief\n\nfoo\nbar\n",
37 "foo bar",
38 "foo\nbar",
39 "foo bar",
40 },
41 {
42 "brief\n\nfoo\n\nbar\n",
43 "foo\n\nbar",
44 "foo\n\nbar",
45 "foo\n\nbar",
46 },
47 {
48 "brief\n\nfoo \\p bar baz",
49 "foo `bar` baz",
50 "foo `bar` baz",
51 "foo bar baz",
52 },
53 {
54 "brief\n\nfoo \\e bar baz",
55 "foo \\*bar\\* baz",
56 "foo *bar* baz",
57 "foo *bar* baz",
58 },
59 {
60 "brief\n\nfoo \\b bar baz",
61 "foo \\*\\*bar\\*\\* baz",
62 "foo **bar** baz",
63 "foo **bar** baz",
64 },
65 {
66 "brief\n\nfoo \\ref bar baz",
67 "foo \\*\\*\\\\ref\\*\\* `bar` baz",
68 "foo **\\ref** `bar` baz",
69 "foo **\\ref** bar baz",
70 },
71 {
72 "brief\n\nfoo @ref bar baz",
73 "foo \\*\\*@ref\\*\\* `bar` baz",
74 "foo **@ref** `bar` baz",
75 "foo **@ref** bar baz",
76 },
77 {
78 "\\brief this is a \\n\nbrief description",
79 "",
80 "",
81 "",
82 },
83 {
84 "brief\n\n\\throw exception foo",
85 "\\*\\*\\\\throw\\*\\* `exception` foo",
86 "**\\throw** `exception` foo",
87 "**\\throw** exception foo",
88 },
89 {
90 R"(\brief this is a brief description
91
92\li item 1
93\li item 2
94\arg item 3)",
95 R"(- item 1
96
97- item 2
98
99- item 3)",
100 R"(- item 1
101
102- item 2
103
104- item 3)",
105 R"(- item 1
106
107- item 2
108
109- item 3)",
110 },
111 {
112 "brief\n\n\\defgroup mygroup this is a group\nthis is not a group "
113 "description",
114 "\\*\\*@defgroup\\*\\* `mygroup this is a group`\n\nthis is not a "
115 "group "
116 "description",
117 "**@defgroup** `mygroup this is a group`\n\nthis is not a group "
118 "description",
119 "**@defgroup** `mygroup this is a group`\n\nthis is not a group "
120 "description",
121 },
122 {
123 R"(brief
124
125\verbatim
126this is a
127verbatim block containing
128some verbatim text
129\endverbatim)",
130 R"(\*\*@verbatim\*\*
131
132```
133this is a
134verbatim block containing
135some verbatim text
136```
137
138\*\*@endverbatim\*\*)",
139 R"(**@verbatim**
140
141```
142this is a
143verbatim block containing
144some verbatim text
145```
146
147**@endverbatim**)",
148 R"(**@verbatim**
149
150this is a
151verbatim block containing
152some verbatim text
153
154**@endverbatim**)",
155 },
156 {
157 "brief\n\n@param foo this is a parameter\n@param bar this is another "
158 "parameter",
159 "",
160 "",
161 "",
162 },
163 {
164 R"(@brief brief docs
165
166@param foo this is a parameter
167
168\brief another brief?
170\details these are details
171
172More description
173documentation)",
174 R"(\*\*\\brief\*\* another brief?
175
176these are details
177
178More description documentation)",
179 R"(**\brief** another brief?
180
181these are details
182
183More description
184documentation)",
185 R"(**\brief** another brief?
186
187these are details
188
189More description documentation)",
190 },
191 {
192 R"(brief
193
194<b>this is a bold text</b>
195normal text<i>this is an italic text</i>
196<code>this is a code block</code>)",
197 R"(<b>this is a bold text</b> normal text<i>this is an italic text</i>
198<code>this is a code block</code>)",
199 R"(<b>this is a bold text</b>
200normal text<i>this is an italic text</i>
201<code>this is a code block</code>)",
202 "<b>this is a bold text</b> normal text<i>this is an italic text</i> "
203 "<code>this is a code block</code>",
204 },
205 {"brief\n\n@note This is a note",
206 R"(\*\*Note:\*\*
207This is a note)",
208 R"(**Note:**
209This is a note)",
210 R"(**Note:**
211This is a note)"},
212 {R"(brief
213
214Paragraph 1
215@note This is a note
216
217Paragraph 2)",
218 R"(Paragraph 1
219
220\*\*Note:\*\*
221This is a note
222
223Paragraph 2)",
224 R"(Paragraph 1
225
226**Note:**
227This is a note
228
229Paragraph 2)",
230 R"(Paragraph 1
231
232**Note:**
233This is a note
234
235Paragraph 2)"},
236 {"brief\n\n@warning This is a warning",
237 R"(\*\*Warning:\*\*
238This is a warning)",
239 R"(**Warning:**
240This is a warning)",
241 R"(**Warning:**
242This is a warning)"},
243 {R"(brief
244
245Paragraph 1
246@warning This is a warning
247
248Paragraph 2)",
249 R"(Paragraph 1
250
251\*\*Warning:\*\*
252This is a warning
253
254Paragraph 2)",
255 R"(Paragraph 1
256
257**Warning:**
258This is a warning
259
260Paragraph 2)",
261 R"(Paragraph 1
262
263**Warning:**
264This is a warning
265
266Paragraph 2)"},
267 {R"(@note this is not treated as brief
268
269@brief this is the brief
270
271Another paragraph)",
272 R"(\*\*Note:\*\*
273this is not treated as brief
274
275Another paragraph)",
276 R"(**Note:**
277this is not treated as brief
278
279Another paragraph)",
280 R"(**Note:**
281this is not treated as brief
282
283Another paragraph)"},
284 {R"(
285@brief Some brief
286)",
287 "", "", ""},
288 {R"(
289Some brief
290)",
291 "", "", ""},
292 };
293 for (const auto &C : Cases) {
294 markup::Document Doc;
295 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
296
297 SymbolDoc.detailedDocToMarkup(Doc);
298
299 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
300 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
301 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
302 }
303}
304
305TEST(SymbolDocumentation, RetvalCommand) {
306
307 CommentOptions CommentOpts;
308
309 struct Case {
310 llvm::StringRef Documentation;
311 llvm::StringRef ExpectedRenderEscapedMarkdown;
312 llvm::StringRef ExpectedRenderMarkdown;
313 llvm::StringRef ExpectedRenderPlainText;
314 } Cases[] = {
315 {"@retval", "", "", ""},
316 {R"(@retval MyReturnVal
317@retval MyOtherReturnVal)",
318 R"(- `MyReturnVal`
319- `MyOtherReturnVal`)",
320 R"(- `MyReturnVal`
321- `MyOtherReturnVal`)",
322 R"(- MyReturnVal
323- MyOtherReturnVal)"},
324 {R"(@retval MyReturnVal if foo
325@retval MyOtherReturnVal if bar)",
326 R"(- `MyReturnVal` - if foo
327- `MyOtherReturnVal` - if bar)",
328 R"(- `MyReturnVal` - if foo
329- `MyOtherReturnVal` - if bar)",
330 R"(- MyReturnVal - if foo
331- MyOtherReturnVal - if bar)"},
332 };
333 for (const auto &C : Cases) {
334 markup::Document Doc;
335 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
336
337 SymbolDoc.retvalsToMarkup(Doc);
338
339 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
340 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
341 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
342 }
343}
344
345TEST(SymbolDocumentation, DoxygenCodeBlocks) {
346 CommentOptions CommentOpts;
347
348 struct Case {
349 llvm::StringRef Documentation;
350 llvm::StringRef ExpectedRenderEscapedMarkdown;
351 llvm::StringRef ExpectedRenderMarkdown;
352 llvm::StringRef ExpectedRenderPlainText;
353 } Cases[] = {
354 {R"(@code
355int code() { return 0; }
356@endcode
357@code{.cpp}
358int code_lang() { return 0; }
359@endcode
360@code{.c++}
361int code_lang_plus() { return 0; }
362@endcode
363@code{.py}
364class A:
365 pass
366@endcode
367@code{nolang}
368class B:
369 pass
370@endcode)",
371 R"(```
372int code() { return 0; }
373```
374
375```cpp
376int code_lang() { return 0; }
377```
378
379```c++
380int code_lang_plus() { return 0; }
381```
382
383```py
384class A:
385 pass
386```
387
388```nolang
389class B:
390 pass
391```)",
392 R"(```
393int code() { return 0; }
394```
395
396```cpp
397int code_lang() { return 0; }
398```
399
400```c++
401int code_lang_plus() { return 0; }
402```
403
404```py
405class A:
406 pass
407```
408
409```nolang
410class B:
411 pass
412```)",
413 R"(int code() { return 0; }
414
415int code_lang() { return 0; }
416
417int code_lang_plus() { return 0; }
418
419class A:
420 pass
421
422class B:
423 pass)"},
424 };
425 for (const auto &C : Cases) {
426 markup::Document Doc;
427 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
428
429 SymbolDoc.detailedDocToMarkup(Doc);
430
431 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
432 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
433 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
434 }
435}
436
437TEST(SymbolDocumentation, MarkdownCodeBlocks) {
438 CommentOptions CommentOpts;
439
440 struct Case {
441 llvm::StringRef Documentation;
442 llvm::StringRef ExpectedRenderEscapedMarkdown;
443 llvm::StringRef ExpectedRenderMarkdown;
444 llvm::StringRef ExpectedRenderPlainText;
445 } Cases[] = {
446 {R"(```
447int backticks() { return 0; }
448```
449```cpp
450int backticks_lang() { return 0; }
451```
452```c++
453int backticks_lang_plus() { return 0; }
454```
455~~~
456int tilde() { return 0; }
457~~~
458~~~~~~~~~~~~~~~~~~~~~~~~
459int tilde_many() { return 0; }
460~~~~~~~~~~~~~~~~~~~~~~~~
461~~~~~~~~~~~~~~~~~~~~~~~~{.c++}
462int tilde_many_lang() { return 0; }
463~~~~~~~~~~~~~~~~~~~~~~~~
464```py
465class A:
466 pass
467```
468```python
469class B:
470 pass
471```
472~~~{.python}
473class C:
474 pass
475~~~
476)",
477 R"(```
478int backticks() { return 0; }
479```
480
481```cpp
482int backticks_lang() { return 0; }
483```
484
485```c++
486int backticks_lang_plus() { return 0; }
487```
488
489```
490int tilde() { return 0; }
491```
492
493```
494int tilde_many() { return 0; }
495```
496
497```c++
498int tilde_many_lang() { return 0; }
499```
500
501```py
502class A:
503 pass
504```
505
506```python
507class B:
508 pass
509```
510
511```python
512class C:
513 pass
514```)",
515 R"(```
516int backticks() { return 0; }
517```
518
519```cpp
520int backticks_lang() { return 0; }
521```
522
523```c++
524int backticks_lang_plus() { return 0; }
525```
526
527```
528int tilde() { return 0; }
529```
530
531```
532int tilde_many() { return 0; }
533```
534
535```c++
536int tilde_many_lang() { return 0; }
537```
538
539```py
540class A:
541 pass
542```
543
544```python
545class B:
546 pass
547```
548
549```python
550class C:
551 pass
552```)",
553 R"(int backticks() { return 0; }
554
555int backticks_lang() { return 0; }
556
557int backticks_lang_plus() { return 0; }
558
559int tilde() { return 0; }
560
561int tilde_many() { return 0; }
562
563int tilde_many_lang() { return 0; }
564
565class A:
566 pass
567
568class B:
569 pass
570
571class C:
572 pass)"},
573 {R"(```
574// this code block is missing end backticks
575
576)",
577 R"(```
578// this code block is missing end backticks
579```)",
580 R"(```
581// this code block is missing end backticks
582```)",
583 R"(// this code block is missing end backticks)"},
584 };
585 for (const auto &C : Cases) {
586 markup::Document Doc;
587 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
588
589 SymbolDoc.detailedDocToMarkup(Doc);
590
591 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
592 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
593 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
594 }
595}
596
597TEST(SymbolDocumentation, MarkdownCodeBlocksSeparation) {
598 CommentOptions CommentOpts;
599
600 struct Case {
601 llvm::StringRef Documentation;
602 llvm::StringRef ExpectedRenderEscapedMarkdown;
603 llvm::StringRef ExpectedRenderMarkdown;
604 llvm::StringRef ExpectedRenderPlainText;
605 } Cases[] = {
606 {R"(@note Show that code blocks are correctly separated
607```
608/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
609
610/// With preprocessing, the code block is correctly separated from the @note paragraph.
611/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
612int function() { return 0; }
613```)",
614 R"(\*\*Note:\*\*
615Show that code blocks are correctly separated
616
617```
618/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
619
620/// With preprocessing, the code block is correctly separated from the @note paragraph.
621/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
622int function() { return 0; }
623```)",
624 R"(**Note:**
625Show that code blocks are correctly separated
626
627```
628/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
629
630/// With preprocessing, the code block is correctly separated from the @note paragraph.
631/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
632int function() { return 0; }
633```)",
634 R"(**Note:**
635Show that code blocks are correctly separated
636
637/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
638
639/// With preprocessing, the code block is correctly separated from the @note paragraph.
640/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
641int function() { return 0; })"},
642 {R"(@note Show that code blocks are correctly separated
643~~~~~~~~~
644/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
645
646/// With preprocessing, the code block is correctly separated from the @note paragraph.
647/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
648int function() { return 0; }
649~~~~~~~~~)",
650 R"(\*\*Note:\*\*
651Show that code blocks are correctly separated
652
653```
654/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
655
656/// With preprocessing, the code block is correctly separated from the @note paragraph.
657/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
658int function() { return 0; }
659```)",
660 R"(**Note:**
661Show that code blocks are correctly separated
662
663```
664/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
665
666/// With preprocessing, the code block is correctly separated from the @note paragraph.
667/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
668int function() { return 0; }
669```)",
670 R"(**Note:**
671Show that code blocks are correctly separated
672
673/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
674
675/// With preprocessing, the code block is correctly separated from the @note paragraph.
676/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
677int function() { return 0; })"},
678 };
679 for (const auto &C : Cases) {
680 markup::Document Doc;
681 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
682
683 SymbolDoc.detailedDocToMarkup(Doc);
684
685 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
686 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
687 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
688 }
689}
690
691TEST(SymbolDocumentation, MarkdownCodeSpans) {
692 CommentOptions CommentOpts;
693
694 struct Case {
695 llvm::StringRef Documentation;
696 llvm::StringRef ExpectedRenderEscapedMarkdown;
697 llvm::StringRef ExpectedRenderMarkdown;
698 llvm::StringRef ExpectedRenderPlainText;
699 } Cases[] = {
700 {R"(`this is a code span with @p and \c inside`)",
701 R"(\`this is a code span with @p and \\c inside\`)",
702 R"(`this is a code span with @p and \c inside`)",
703 R"(`this is a code span with @p and \c inside`)"},
704 {R"(<escaped> `<not-escaped>`)", R"(<escaped> \`<not-escaped>\`)",
705 R"(<escaped> `<not-escaped>`)", R"(<escaped> `<not-escaped>`)"},
706 {R"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)",
707 R"(<escaped> \\\`<escaped> doxygen commands not parsed @p, \\c, @note, \\warning \\\`)",
708 R"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)",
709 R"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)"},
710 {R"(`multi
711line
712\c span`)",
713 R"(\`multi line
714\\c span\`)",
715 R"(`multi
716line
717\c span`)",
718 R"(`multi line
719\c span`)"},
720 };
721 for (const auto &C : Cases) {
722 markup::Document Doc;
723 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
724
725 SymbolDoc.briefToMarkup(Doc.addParagraph());
726
727 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
728 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
729 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
730 }
731}
732
733TEST(SymbolDocumentation, ParameterDocToString) {
734 CommentOptions CommentOpts;
735
736 struct Case {
737 llvm::StringRef Documentation;
738 llvm::StringRef ExpectedOutputString;
739 llvm::StringRef ParameterName;
740 } Cases[] = {
741 {"This documentation does not contain parameter docs", "", "a"},
742 {"@param a this is a parameter", "", "not_exists"},
743 {"@param a this is a parameter", " this is a parameter", "a"},
744 {R"(@param a parameter doc with an \p inline command)",
745 R"( parameter doc with an \p inline command)", "a"},
746 {R"(@param a parameter doc with an \unknown command)",
747 R"( parameter doc with an \unknown command)", "a"},
748 {"@param a parameter doc with an @unknown command",
749 " parameter doc with an @unknown command", "a"},
750 {R"(@param a parameter doc with
751multiple lines)",
752 R"( parameter doc with
753multiple lines)",
754 "a"},
755 {R"(@param a parameter doc with an
756@unknown command starting a new line)",
757 R"( parameter doc with an
758@unknown command starting a new line)",
759 "a"},
760 {R"(@param a parameter doc with a
761@note command which is a new block command and therefore ends the parameter doc paragraph)",
762 R"( parameter doc with a
763)",
764 "a"},
765 {R"(Unrelated docs
766@param a parameter doc
767
768New paragraph with unrelated docs)",
769 " parameter doc", "a"},
770 };
771 for (const auto &C : Cases) {
772 std::string Result;
773 llvm::raw_string_ostream OS(Result);
774 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
775
776 SymbolDoc.parameterDocToString(C.ParameterName, OS);
777
778 EXPECT_EQ(Result, C.ExpectedOutputString);
779 }
780}
781
782TEST(SymbolDocumentation, TemplateParameterDocToString) {
783 CommentOptions CommentOpts;
784
785 struct Case {
786 llvm::StringRef Documentation;
787 llvm::StringRef ExpectedOutputString;
788 llvm::StringRef TemplateParameterName;
789 } Cases[] = {
790 {"This documentation does not contain parameter docs", "", "a"},
791 {"@tparam a this is a template type parameter", "", "not_exists"},
792 {"@tparam a this is a template type parameter",
793 " this is a template type parameter", "a"},
794 {R"(@tparam a template type parameter doc with an \p inline command)",
795 R"( template type parameter doc with an \p inline command)", "a"},
796 {R"(@tparam a template type parameter doc with an \unknown command)",
797 R"( template type parameter doc with an \unknown command)", "a"},
798 {"@tparam a template type parameter doc with an @unknown command",
799 " template type parameter doc with an @unknown command", "a"},
800 {R"(@tparam a template type parameter doc with
801multiple lines)",
802 R"( template type parameter doc with
803multiple lines)",
804 "a"},
805 {R"(@tparam a template type parameter doc with an
806@unknown command starting a new line)",
807 R"( template type parameter doc with an
808@unknown command starting a new line)",
809 "a"},
810 {R"(@tparam a template type parameter doc with a
811@note command which is a new block command and therefore ends the template type parameter doc paragraph)",
812 R"( template type parameter doc with a
813)",
814 "a"},
815 {R"(Unrelated docs
816@tparam a template type parameter doc
817
818New paragraph with unrelated docs)",
819 " template type parameter doc", "a"},
820 };
821 for (const auto &C : Cases) {
822 std::string Result;
823 llvm::raw_string_ostream OS(Result);
824 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
825
826 SymbolDoc.templateTypeParmDocToString(C.TemplateParameterName, OS);
827
828 EXPECT_EQ(Result, C.ExpectedOutputString);
829 }
830}
831
832} // namespace clangd
833} // namespace clang
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
TEST(BackgroundQueueTest, Priority)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//