737 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8#include "SymbolDocumentation.h"9 10#include "support/Markup.h"11#include "clang/Basic/CommentOptions.h"12#include "llvm/ADT/StringRef.h"13#include "gtest/gtest.h"14 15namespace clang {16namespace clangd {17 18TEST(SymbolDocumentation, DetailedDocToMarkup) {19 20 CommentOptions CommentOpts;21 22 struct Case {23 llvm::StringRef Documentation;24 llvm::StringRef ExpectedRenderEscapedMarkdown;25 llvm::StringRef ExpectedRenderMarkdown;26 llvm::StringRef ExpectedRenderPlainText;27 } Cases[] = {28 {29 "brief\n\nfoo bar",30 "foo bar",31 "foo bar",32 "foo bar",33 },34 {35 "brief\n\nfoo\nbar\n",36 "foo\nbar",37 "foo\nbar",38 "foo bar",39 },40 {41 "brief\n\nfoo\n\nbar\n",42 "foo\n\nbar",43 "foo\n\nbar",44 "foo\n\nbar",45 },46 {47 "brief\n\nfoo \\p bar baz",48 "foo `bar` baz",49 "foo `bar` baz",50 "foo bar baz",51 },52 {53 "brief\n\nfoo \\e bar baz",54 "foo \\*bar\\* baz",55 "foo *bar* baz",56 "foo *bar* baz",57 },58 {59 "brief\n\nfoo \\b bar baz",60 "foo \\*\\*bar\\*\\* baz",61 "foo **bar** baz",62 "foo **bar** baz",63 },64 {65 "brief\n\nfoo \\ref bar baz",66 "foo \\*\\*\\\\ref\\*\\* `bar` baz",67 "foo **\\ref** `bar` baz",68 "foo **\\ref** bar baz",69 },70 {71 "brief\n\nfoo @ref bar baz",72 "foo \\*\\*@ref\\*\\* `bar` baz",73 "foo **@ref** `bar` baz",74 "foo **@ref** bar baz",75 },76 {77 "\\brief this is a \\n\nbrief description",78 "",79 "",80 "",81 },82 {83 "brief\n\n\\throw exception foo",84 "\\*\\*\\\\throw\\*\\* `exception` foo",85 "**\\throw** `exception` foo",86 "**\\throw** exception foo",87 },88 {89 R"(\brief this is a brief description90 91\li item 192\li item 293\arg item 3)",94 R"(- item 195 96- item 297 98- item 3)",99 R"(- item 1100 101- item 2102 103- item 3)",104 R"(- item 1105 106- item 2107 108- item 3)",109 },110 {111 "brief\n\n\\defgroup mygroup this is a group\nthis is not a group "112 "description",113 "\\*\\*@defgroup\\*\\* `mygroup this is a group`\n\nthis is not a "114 "group "115 "description",116 "**@defgroup** `mygroup this is a group`\n\nthis is not a group "117 "description",118 "**@defgroup** `mygroup this is a group`\n\nthis is not a group "119 "description",120 },121 {122 R"(brief123 124\verbatim125this is a126verbatim block containing127some verbatim text128\endverbatim)",129 R"(\*\*@verbatim\*\*130 131```132this is a133verbatim block containing134some verbatim text135```136 137\*\*@endverbatim\*\*)",138 R"(**@verbatim**139 140```141this is a142verbatim block containing143some verbatim text144```145 146**@endverbatim**)",147 R"(**@verbatim**148 149this is a150verbatim block containing151some verbatim text152 153**@endverbatim**)",154 },155 {156 "brief\n\n@param foo this is a parameter\n@param bar this is another "157 "parameter",158 "",159 "",160 "",161 },162 {163 R"(@brief brief docs164 165@param foo this is a parameter166 167\brief another brief?168 169\details these are details170 171More description172documentation)",173 R"(\*\*\\brief\*\* another brief?174 175these are details176 177More description178documentation)",179 R"(**\brief** another brief?180 181these are details182 183More description184documentation)",185 R"(**\brief** another brief?186 187these are details188 189More description documentation)",190 },191 {192 R"(brief193 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>198normal text\<i>this is an italic text\</i> 199\<code>this is a code block\</code>)",200 R"(\<b>this is a bold text\</b>201normal text\<i>this is an italic text\</i> 202\<code>this is a code block\</code>)",203 "<b>this is a bold text</b> normal text<i>this is an italic text</i> "204 "<code>this is a code block</code>",205 },206 {"brief\n\n@note This is a note",207 R"(\*\*Note:\*\* 208This is a note)",209 R"(**Note:** 210This is a note)",211 R"(**Note:**212This is a note)"},213 {R"(brief214 215Paragraph 1216@note This is a note217 218Paragraph 2)",219 R"(Paragraph 1220 221\*\*Note:\*\* 222This is a note223 224Paragraph 2)",225 R"(Paragraph 1226 227**Note:** 228This is a note229 230Paragraph 2)",231 R"(Paragraph 1232 233**Note:**234This is a note235 236Paragraph 2)"},237 {"brief\n\n@warning This is a warning",238 R"(\*\*Warning:\*\* 239This is a warning)",240 R"(**Warning:** 241This is a warning)",242 R"(**Warning:**243This is a warning)"},244 {R"(brief245 246Paragraph 1247@warning This is a warning248 249Paragraph 2)",250 R"(Paragraph 1251 252\*\*Warning:\*\* 253This is a warning254 255Paragraph 2)",256 R"(Paragraph 1257 258**Warning:** 259This is a warning260 261Paragraph 2)",262 R"(Paragraph 1263 264**Warning:**265This is a warning266 267Paragraph 2)"},268 {R"(@note this is not treated as brief269 270@brief this is the brief271 272Another paragraph)",273 R"(\*\*Note:\*\* 274this is not treated as brief275 276Another paragraph)",277 R"(**Note:** 278this is not treated as brief279 280Another paragraph)",281 R"(**Note:**282this is not treated as brief283 284Another paragraph)"},285 {R"(286@brief Some brief287)",288 "", "", ""},289 {R"(290Some brief291)",292 "", "", ""},293 };294 for (const auto &C : Cases) {295 markup::Document Doc;296 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);297 298 SymbolDoc.detailedDocToMarkup(Doc);299 300 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);301 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);302 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);303 }304}305 306TEST(SymbolDocumentation, RetvalCommand) {307 308 CommentOptions CommentOpts;309 310 struct Case {311 llvm::StringRef Documentation;312 llvm::StringRef ExpectedRenderEscapedMarkdown;313 llvm::StringRef ExpectedRenderMarkdown;314 llvm::StringRef ExpectedRenderPlainText;315 } Cases[] = {316 {"@retval", "", "", ""},317 {R"(@retval MyReturnVal318@retval MyOtherReturnVal)",319 R"(- `MyReturnVal`320- `MyOtherReturnVal`)",321 R"(- `MyReturnVal`322- `MyOtherReturnVal`)",323 R"(- MyReturnVal324- MyOtherReturnVal)"},325 {R"(@retval MyReturnVal if foo326@retval MyOtherReturnVal if bar)",327 R"(- `MyReturnVal` - if foo328- `MyOtherReturnVal` - if bar)",329 R"(- `MyReturnVal` - if foo330- `MyOtherReturnVal` - if bar)",331 R"(- MyReturnVal - if foo332- MyOtherReturnVal - if bar)"},333 };334 for (const auto &C : Cases) {335 markup::Document Doc;336 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);337 338 SymbolDoc.retvalsToMarkup(Doc);339 340 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);341 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);342 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);343 }344}345 346TEST(SymbolDocumentation, DoxygenCodeBlocks) {347 CommentOptions CommentOpts;348 349 struct Case {350 llvm::StringRef Documentation;351 llvm::StringRef ExpectedRenderEscapedMarkdown;352 llvm::StringRef ExpectedRenderMarkdown;353 llvm::StringRef ExpectedRenderPlainText;354 } Cases[] = {355 {R"(@code356int code() { return 0; }357@endcode358@code{.cpp}359int code_lang() { return 0; }360@endcode361@code{.c++}362int code_lang_plus() { return 0; }363@endcode364@code{.py}365class A:366 pass367@endcode368@code{nolang}369class B:370 pass371@endcode)",372 R"(```373int code() { return 0; }374```375 376```cpp377int code_lang() { return 0; }378```379 380```c++381int code_lang_plus() { return 0; }382```383 384```py385class A:386 pass387```388 389```nolang390class B:391 pass392```)",393 R"(```394int code() { return 0; }395```396 397```cpp398int code_lang() { return 0; }399```400 401```c++402int code_lang_plus() { return 0; }403```404 405```py406class A:407 pass408```409 410```nolang411class B:412 pass413```)",414 R"(int code() { return 0; }415 416int code_lang() { return 0; }417 418int code_lang_plus() { return 0; }419 420class A:421 pass422 423class B:424 pass)"},425 };426 for (const auto &C : Cases) {427 markup::Document Doc;428 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);429 430 SymbolDoc.detailedDocToMarkup(Doc);431 432 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);433 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);434 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);435 }436}437 438TEST(SymbolDocumentation, MarkdownCodeBlocks) {439 CommentOptions CommentOpts;440 441 struct Case {442 llvm::StringRef Documentation;443 llvm::StringRef ExpectedRenderEscapedMarkdown;444 llvm::StringRef ExpectedRenderMarkdown;445 llvm::StringRef ExpectedRenderPlainText;446 } Cases[] = {447 {R"(```448int backticks() { return 0; }449```450```cpp451int backticks_lang() { return 0; }452```453```c++454int backticks_lang_plus() { return 0; }455```456~~~457int tilde() { return 0; }458~~~459~~~~~~~~~~~~~~~~~~~~~~~~460int tilde_many() { return 0; }461~~~~~~~~~~~~~~~~~~~~~~~~462~~~~~~~~~~~~~~~~~~~~~~~~{.c++}463int tilde_many_lang() { return 0; }464~~~~~~~~~~~~~~~~~~~~~~~~465```py466class A:467 pass468```469```python470class B:471 pass472```473~~~{.python}474class C:475 pass476~~~477)",478 R"(```479int backticks() { return 0; }480```481 482```cpp483int backticks_lang() { return 0; }484```485 486```c++487int backticks_lang_plus() { return 0; }488```489 490```491int tilde() { return 0; }492```493 494```495int tilde_many() { return 0; }496```497 498```c++499int tilde_many_lang() { return 0; }500```501 502```py503class A:504 pass505```506 507```python508class B:509 pass510```511 512```python513class C:514 pass515```)",516 R"(```517int backticks() { return 0; }518```519 520```cpp521int backticks_lang() { return 0; }522```523 524```c++525int backticks_lang_plus() { return 0; }526```527 528```529int tilde() { return 0; }530```531 532```533int tilde_many() { return 0; }534```535 536```c++537int tilde_many_lang() { return 0; }538```539 540```py541class A:542 pass543```544 545```python546class B:547 pass548```549 550```python551class C:552 pass553```)",554 R"(int backticks() { return 0; }555 556int backticks_lang() { return 0; }557 558int backticks_lang_plus() { return 0; }559 560int tilde() { return 0; }561 562int tilde_many() { return 0; }563 564int tilde_many_lang() { return 0; }565 566class A:567 pass568 569class B:570 pass571 572class C:573 pass)"},574 {R"(```575// this code block is missing end backticks576 577)",578 R"(```579// this code block is missing end backticks580```)",581 R"(```582// this code block is missing end backticks583```)",584 R"(// this code block is missing end backticks)"},585 };586 for (const auto &C : Cases) {587 markup::Document Doc;588 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);589 590 SymbolDoc.detailedDocToMarkup(Doc);591 592 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);593 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);594 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);595 }596}597 598TEST(SymbolDocumentation, MarkdownCodeBlocksSeparation) {599 CommentOptions CommentOpts;600 601 struct Case {602 llvm::StringRef Documentation;603 llvm::StringRef ExpectedRenderEscapedMarkdown;604 llvm::StringRef ExpectedRenderMarkdown;605 llvm::StringRef ExpectedRenderPlainText;606 } Cases[] = {607 {R"(@note Show that code blocks are correctly separated608```609/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.610 611/// With preprocessing, the code block is correctly separated from the @note paragraph.612/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.613int function() { return 0; }614```)",615 R"(\*\*Note:\*\* 616Show that code blocks are correctly separated617 618```619/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.620 621/// With preprocessing, the code block is correctly separated from the @note paragraph.622/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.623int function() { return 0; }624```)",625 R"(**Note:** 626Show that code blocks are correctly separated627 628```629/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.630 631/// With preprocessing, the code block is correctly separated from the @note paragraph.632/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.633int function() { return 0; }634```)",635 R"(**Note:**636Show that code blocks are correctly separated637 638/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.639 640/// With preprocessing, the code block is correctly separated from the @note paragraph.641/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.642int function() { return 0; })"},643 {R"(@note Show that code blocks are correctly separated644~~~~~~~~~645/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.646 647/// With preprocessing, the code block is correctly separated from the @note paragraph.648/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.649int function() { return 0; }650~~~~~~~~~)",651 R"(\*\*Note:\*\* 652Show that code blocks are correctly separated653 654```655/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.656 657/// With preprocessing, the code block is correctly separated from the @note paragraph.658/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.659int function() { return 0; }660```)",661 R"(**Note:** 662Show that code blocks are correctly separated663 664```665/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.666 667/// With preprocessing, the code block is correctly separated from the @note paragraph.668/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.669int function() { return 0; }670```)",671 R"(**Note:**672Show that code blocks are correctly separated673 674/// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph.675 676/// With preprocessing, the code block is correctly separated from the @note paragraph.677/// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted.678int function() { return 0; })"},679 };680 for (const auto &C : Cases) {681 markup::Document Doc;682 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);683 684 SymbolDoc.detailedDocToMarkup(Doc);685 686 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);687 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);688 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);689 }690}691 692TEST(SymbolDocumentation, MarkdownCodeSpans) {693 CommentOptions CommentOpts;694 695 struct Case {696 llvm::StringRef Documentation;697 llvm::StringRef ExpectedRenderEscapedMarkdown;698 llvm::StringRef ExpectedRenderMarkdown;699 llvm::StringRef ExpectedRenderPlainText;700 } Cases[] = {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"(`this is a code span with @p and \c inside`)"},705 {R"(<escaped> `<not-escaped>`)", R"(\<escaped> \`\<not-escaped>\`)",706 R"(\<escaped> `<not-escaped>`)", R"(<escaped> `<not-escaped>`)"},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"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)"},711 {R"(`multi712line713\c span`)",714 R"(\`multi715line 716\\c span\`)",717 R"(`multi718line 719\c span`)",720 R"(`multi line721\c span`)"},722 };723 for (const auto &C : Cases) {724 markup::Document Doc;725 SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);726 727 SymbolDoc.briefToMarkup(Doc.addParagraph());728 729 EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);730 EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);731 EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);732 }733}734 735} // namespace clangd736} // namespace clang737