clang-tools 23.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\nbar",
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
179documentation)",
180 R"(**\brief** another brief?
181
182these are details
183
184More description
185documentation)",
186 R"(**\brief** another brief?
187
188these are details
189
190More description documentation)",
191 },
192 {
193 R"(brief
194
195<b>this is a bold text</b>
196normal text<i>this is an italic text</i>
197<code>this is a code block</code>)",
198 R"(<b>this is a bold text</b>
199normal text<i>this is an italic text</i>
200<code>this is a code block</code>)",
201 R"(<b>this is a bold text</b>
202normal text<i>this is an italic text</i>
203<code>this is a code block</code>)",
204 "<b>this is a bold text</b> normal text<i>this is an italic text</i> "
205 "<code>this is a code block</code>",
206 },
207 {"brief\n\n@note This is a note",
208 R"(\*\*Note:\*\*
209This is a note)",
210 R"(**Note:**
211This is a note)",
212 R"(**Note:**
213This is a note)"},
214 {R"(brief
215
216Paragraph 1
217@note This is a note
218
219Paragraph 2)",
220 R"(Paragraph 1
221
222\*\*Note:\*\*
223This is a note
224
225Paragraph 2)",
226 R"(Paragraph 1
228**Note:**
229This is a note
230
231Paragraph 2)",
232 R"(Paragraph 1
233
234**Note:**
235This is a note
236
237Paragraph 2)"},
238 {"brief\n\n@warning This is a warning",
239 R"(\*\*Warning:\*\*
240This is a warning)",
241 R"(**Warning:**
242This is a warning)",
243 R"(**Warning:**
244This is a warning)"},
245 {R"(brief
246
247Paragraph 1
248@warning This is a warning
249
250Paragraph 2)",
251 R"(Paragraph 1
252
253\*\*Warning:\*\*
254This is a warning
255
256Paragraph 2)",
257 R"(Paragraph 1
258
259**Warning:**
260This is a warning
261
262Paragraph 2)",
263 R"(Paragraph 1
264
265**Warning:**
266This is a warning
267
268Paragraph 2)"},
269 {R"(@note this is not treated as brief
270
271@brief this is the brief
272
273Another paragraph)",
274 R"(\*\*Note:\*\*
275this is not treated as brief
276
277Another paragraph)",
278 R"(**Note:**
279this is not treated as brief
280
281Another paragraph)",
282 R"(**Note:**
283this is not treated as brief
284
285Another paragraph)"},
286 {R"(
287@brief Some brief
288)",
289 "", "", ""},
290 {R"(
291Some brief
292)",
293 "", "", ""},
294 };
295 for (const auto &C : Cases) {
296 markup::Document Doc;
297 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
298
299 SymbolDoc.detailedDocToMarkup(Doc);
300
301 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
302 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
303 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
304 }
305}
306
307TEST(SymbolDocumentation, RetvalCommand) {
308
309 CommentOptions CommentOpts;
310
311 struct Case {
312 llvm::StringRef Documentation;
313 llvm::StringRef ExpectedRenderEscapedMarkdown;
314 llvm::StringRef ExpectedRenderMarkdown;
315 llvm::StringRef ExpectedRenderPlainText;
316 } Cases[] = {
317 {"@retval", "", "", ""},
318 {R"(@retval MyReturnVal
319@retval MyOtherReturnVal)",
320 R"(- `MyReturnVal`
321- `MyOtherReturnVal`)",
322 R"(- `MyReturnVal`
323- `MyOtherReturnVal`)",
324 R"(- MyReturnVal
325- MyOtherReturnVal)"},
326 {R"(@retval MyReturnVal if foo
327@retval MyOtherReturnVal if bar)",
328 R"(- `MyReturnVal` - if foo
329- `MyOtherReturnVal` - if bar)",
330 R"(- `MyReturnVal` - if foo
331- `MyOtherReturnVal` - if bar)",
332 R"(- MyReturnVal - if foo
333- MyOtherReturnVal - if bar)"},
334 };
335 for (const auto &C : Cases) {
336 markup::Document Doc;
337 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
338
339 SymbolDoc.retvalsToMarkup(Doc);
340
341 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
342 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
343 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
344 }
345}
346
347TEST(SymbolDocumentation, DoxygenCodeBlocks) {
348 CommentOptions CommentOpts;
349
350 struct Case {
351 llvm::StringRef Documentation;
352 llvm::StringRef ExpectedRenderEscapedMarkdown;
353 llvm::StringRef ExpectedRenderMarkdown;
354 llvm::StringRef ExpectedRenderPlainText;
355 } Cases[] = {
356 {R"(@code
357int code() { return 0; }
358@endcode
359@code{.cpp}
360int code_lang() { return 0; }
361@endcode
362@code{.c++}
363int code_lang_plus() { return 0; }
364@endcode
365@code{.py}
366class A:
367 pass
368@endcode
369@code{nolang}
370class B:
371 pass
372@endcode)",
373 R"(```
374int code() { return 0; }
375```
376
377```cpp
378int code_lang() { return 0; }
379```
380
381```c++
382int code_lang_plus() { return 0; }
383```
384
385```py
386class A:
387 pass
388```
389
390```nolang
391class B:
392 pass
393```)",
394 R"(```
395int code() { return 0; }
396```
397
398```cpp
399int code_lang() { return 0; }
400```
401
402```c++
403int code_lang_plus() { return 0; }
404```
405
406```py
407class A:
408 pass
409```
410
411```nolang
412class B:
413 pass
414```)",
415 R"(int code() { return 0; }
416
417int code_lang() { return 0; }
418
419int code_lang_plus() { return 0; }
420
421class A:
422 pass
423
424class B:
425 pass)"},
426 };
427 for (const auto &C : Cases) {
428 markup::Document Doc;
429 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
430
431 SymbolDoc.detailedDocToMarkup(Doc);
432
433 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
434 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
435 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
436 }
437}
438
439TEST(SymbolDocumentation, MarkdownCodeBlocks) {
440 CommentOptions CommentOpts;
441
442 struct Case {
443 llvm::StringRef Documentation;
444 llvm::StringRef ExpectedRenderEscapedMarkdown;
445 llvm::StringRef ExpectedRenderMarkdown;
446 llvm::StringRef ExpectedRenderPlainText;
447 } Cases[] = {
448 {R"(```
449int backticks() { return 0; }
450```
451```cpp
452int backticks_lang() { return 0; }
453```
454```c++
455int backticks_lang_plus() { return 0; }
456```
457~~~
458int tilde() { return 0; }
459~~~
460~~~~~~~~~~~~~~~~~~~~~~~~
461int tilde_many() { return 0; }
462~~~~~~~~~~~~~~~~~~~~~~~~
463~~~~~~~~~~~~~~~~~~~~~~~~{.c++}
464int tilde_many_lang() { return 0; }
465~~~~~~~~~~~~~~~~~~~~~~~~
466```py
467class A:
468 pass
469```
470```python
471class B:
472 pass
473```
474~~~{.python}
475class C:
476 pass
477~~~
478)",
479 R"(```
480int backticks() { return 0; }
481```
482
483```cpp
484int backticks_lang() { return 0; }
485```
486
487```c++
488int backticks_lang_plus() { return 0; }
489```
490
491```
492int tilde() { return 0; }
493```
494
495```
496int tilde_many() { return 0; }
497```
498
499```c++
500int tilde_many_lang() { return 0; }
501```
502
503```py
504class A:
505 pass
506```
507
508```python
509class B:
510 pass
511```
512
513```python
514class C:
515 pass
516```)",
517 R"(```
518int backticks() { return 0; }
519```
520
521```cpp
522int backticks_lang() { return 0; }
523```
524
525```c++
526int backticks_lang_plus() { return 0; }
527```
528
529```
530int tilde() { return 0; }
531```
532
533```
534int tilde_many() { return 0; }
535```
536
537```c++
538int tilde_many_lang() { return 0; }
539```
540
541```py
542class A:
543 pass
544```
545
546```python
547class B:
548 pass
549```
550
551```python
552class C:
553 pass
554```)",
555 R"(int backticks() { return 0; }
556
557int backticks_lang() { return 0; }
558
559int backticks_lang_plus() { return 0; }
560
561int tilde() { return 0; }
562
563int tilde_many() { return 0; }
564
565int tilde_many_lang() { return 0; }
566
567class A:
568 pass
569
570class B:
571 pass
572
573class C:
574 pass)"},
575 {R"(```
576// this code block is missing end backticks
577
578)",
579 R"(```
580// this code block is missing end backticks
581```)",
582 R"(```
583// this code block is missing end backticks
584```)",
585 R"(// this code block is missing end backticks)"},
586 };
587 for (const auto &C : Cases) {
588 markup::Document Doc;
589 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
590
591 SymbolDoc.detailedDocToMarkup(Doc);
592
593 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
594 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
595 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
596 }
597}
598
599TEST(SymbolDocumentation, MarkdownCodeBlocksSeparation) {
600 CommentOptions CommentOpts;
601
602 struct Case {
603 llvm::StringRef Documentation;
604 llvm::StringRef ExpectedRenderEscapedMarkdown;
605 llvm::StringRef ExpectedRenderMarkdown;
606 llvm::StringRef ExpectedRenderPlainText;
607 } Cases[] = {
608 {R"(@note Show that code blocks are correctly separated
609```
610/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
611
612/// With preprocessing, the code block is correctly separated from the @note paragraph.
613/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
614int function() { return 0; }
615```)",
616 R"(\*\*Note:\*\*
617Show that code blocks are correctly separated
618
619```
620/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
621
622/// With preprocessing, the code block is correctly separated from the @note paragraph.
623/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
624int function() { return 0; }
625```)",
626 R"(**Note:**
627Show that code blocks are correctly separated
628
629```
630/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
631
632/// With preprocessing, the code block is correctly separated from the @note paragraph.
633/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
634int function() { return 0; }
635```)",
636 R"(**Note:**
637Show that code blocks are correctly separated
638
639/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
640
641/// With preprocessing, the code block is correctly separated from the @note paragraph.
642/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
643int function() { return 0; })"},
644 {R"(@note Show that code blocks are correctly separated
645~~~~~~~~~
646/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
647
648/// With preprocessing, the code block is correctly separated from the @note paragraph.
649/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
650int function() { return 0; }
651~~~~~~~~~)",
652 R"(\*\*Note:\*\*
653Show that code blocks are correctly separated
654
655```
656/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
657
658/// With preprocessing, the code block is correctly separated from the @note paragraph.
659/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
660int function() { return 0; }
661```)",
662 R"(**Note:**
663Show that code blocks are correctly separated
664
665```
666/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
667
668/// With preprocessing, the code block is correctly separated from the @note paragraph.
669/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
670int function() { return 0; }
671```)",
672 R"(**Note:**
673Show that code blocks are correctly separated
674
675/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.
676
677/// With preprocessing, the code block is correctly separated from the @note paragraph.
678/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.
679int function() { return 0; })"},
680 };
681 for (const auto &C : Cases) {
682 markup::Document Doc;
683 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
684
685 SymbolDoc.detailedDocToMarkup(Doc);
686
687 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
688 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
689 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
690 }
691}
692
693TEST(SymbolDocumentation, MarkdownCodeSpans) {
694 CommentOptions CommentOpts;
695
696 struct Case {
697 llvm::StringRef Documentation;
698 llvm::StringRef ExpectedRenderEscapedMarkdown;
699 llvm::StringRef ExpectedRenderMarkdown;
700 llvm::StringRef ExpectedRenderPlainText;
701 } Cases[] = {
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"(`this is a code span with @p and \c inside`)",
705 R"(`this is a code span with @p and \c inside`)"},
706 {R"(<escaped> `<not-escaped>`)", R"(<escaped> \`<not-escaped>\`)",
707 R"(<escaped> `<not-escaped>`)", R"(<escaped> `<not-escaped>`)"},
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"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)",
711 R"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)"},
712 {R"(`multi
713line
714\c span`)",
715 R"(\`multi
716line
717\\c span\`)",
718 R"(`multi
719line
720\c span`)",
721 R"(`multi line
722\c span`)"},
723 };
724 for (const auto &C : Cases) {
725 markup::Document Doc;
726 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
727
728 SymbolDoc.briefToMarkup(Doc.addParagraph());
729
730 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
731 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
732 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
733 }
734}
735
736TEST(SymbolDocumentation, ParameterDocToString) {
737 CommentOptions CommentOpts;
738
739 struct Case {
740 llvm::StringRef Documentation;
741 llvm::StringRef ExpectedOutputString;
742 llvm::StringRef ParameterName;
743 } Cases[] = {
744 {"This documentation does not contain parameter docs", "", "a"},
745 {"@param a this is a parameter", "", "not_exists"},
746 {"@param a this is a parameter", " this is a parameter", "a"},
747 {R"(@param a parameter doc with an \p inline command)",
748 R"( parameter doc with an \p inline command)", "a"},
749 {R"(@param a parameter doc with an \unknown command)",
750 R"( parameter doc with an \unknown command)", "a"},
751 {"@param a parameter doc with an @unknown command",
752 " parameter doc with an @unknown command", "a"},
753 {R"(@param a parameter doc with
754multiple lines)",
755 R"( parameter doc with
756multiple lines)",
757 "a"},
758 {R"(@param a parameter doc with an
759@unknown command starting a new line)",
760 R"( parameter doc with an
761@unknown command starting a new line)",
762 "a"},
763 {R"(@param a parameter doc with a
764@note command which is a new block command and therefore ends the parameter doc paragraph)",
765 R"( parameter doc with a
766)",
767 "a"},
768 {R"(Unrelated docs
769@param a parameter doc
770
771New paragraph with unrelated docs)",
772 " parameter doc", "a"},
773 };
774 for (const auto &C : Cases) {
775 std::string Result;
776 llvm::raw_string_ostream OS(Result);
777 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
778
779 SymbolDoc.parameterDocToString(C.ParameterName, OS);
780
781 EXPECT_EQ(Result, C.ExpectedOutputString);
782 }
783}
784
785TEST(SymbolDocumentation, TemplateParameterDocToString) {
786 CommentOptions CommentOpts;
787
788 struct Case {
789 llvm::StringRef Documentation;
790 llvm::StringRef ExpectedOutputString;
791 llvm::StringRef TemplateParameterName;
792 } Cases[] = {
793 {"This documentation does not contain parameter docs", "", "a"},
794 {"@tparam a this is a template type parameter", "", "not_exists"},
795 {"@tparam a this is a template type parameter",
796 " this is a template type parameter", "a"},
797 {R"(@tparam a template type parameter doc with an \p inline command)",
798 R"( template type parameter doc with an \p inline command)", "a"},
799 {R"(@tparam a template type parameter doc with an \unknown command)",
800 R"( template type parameter doc with an \unknown command)", "a"},
801 {"@tparam a template type parameter doc with an @unknown command",
802 " template type parameter doc with an @unknown command", "a"},
803 {R"(@tparam a template type parameter doc with
804multiple lines)",
805 R"( template type parameter doc with
806multiple lines)",
807 "a"},
808 {R"(@tparam a template type parameter doc with an
809@unknown command starting a new line)",
810 R"( template type parameter doc with an
811@unknown command starting a new line)",
812 "a"},
813 {R"(@tparam a template type parameter doc with a
814@note command which is a new block command and therefore ends the template type parameter doc paragraph)",
815 R"( template type parameter doc with a
816)",
817 "a"},
818 {R"(Unrelated docs
819@tparam a template type parameter doc
820
821New paragraph with unrelated docs)",
822 " template type parameter doc", "a"},
823 };
824 for (const auto &C : Cases) {
825 std::string Result;
826 llvm::raw_string_ostream OS(Result);
827 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
828
829 SymbolDoc.templateTypeParmDocToString(C.TemplateParameterName, OS);
830
831 EXPECT_EQ(Result, C.ExpectedOutputString);
832 }
833}
834
835} // namespace clangd
836} // 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++ -*-===//