//===-- SymbolDocumentationTests.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "SymbolDocumentation.h" #include "support/Markup.h" #include "clang/Basic/CommentOptions.h" #include "llvm/ADT/StringRef.h" #include "gtest/gtest.h" namespace clang { namespace clangd { TEST(SymbolDocumentation, DetailedDocToMarkup) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { { "brief\n\nfoo bar", "foo bar", "foo bar", "foo bar", }, { "brief\n\nfoo\nbar\n", "foo\nbar", "foo\nbar", "foo bar", }, { "brief\n\nfoo\n\nbar\n", "foo\n\nbar", "foo\n\nbar", "foo\n\nbar", }, { "brief\n\nfoo \\p bar baz", "foo `bar` baz", "foo `bar` baz", "foo bar baz", }, { "brief\n\nfoo \\e bar baz", "foo \\*bar\\* baz", "foo *bar* baz", "foo *bar* baz", }, { "brief\n\nfoo \\b bar baz", "foo \\*\\*bar\\*\\* baz", "foo **bar** baz", "foo **bar** baz", }, { "brief\n\nfoo \\ref bar baz", "foo \\*\\*\\\\ref\\*\\* `bar` baz", "foo **\\ref** `bar` baz", "foo **\\ref** bar baz", }, { "brief\n\nfoo @ref bar baz", "foo \\*\\*@ref\\*\\* `bar` baz", "foo **@ref** `bar` baz", "foo **@ref** bar baz", }, { "\\brief this is a \\n\nbrief description", "", "", "", }, { "brief\n\n\\throw exception foo", "\\*\\*\\\\throw\\*\\* `exception` foo", "**\\throw** `exception` foo", "**\\throw** exception foo", }, { R"(\brief this is a brief description \li item 1 \li item 2 \arg item 3)", R"(- item 1 - item 2 - item 3)", R"(- item 1 - item 2 - item 3)", R"(- item 1 - item 2 - item 3)", }, { "brief\n\n\\defgroup mygroup this is a group\nthis is not a group " "description", "\\*\\*@defgroup\\*\\* `mygroup this is a group`\n\nthis is not a " "group " "description", "**@defgroup** `mygroup this is a group`\n\nthis is not a group " "description", "**@defgroup** `mygroup this is a group`\n\nthis is not a group " "description", }, { R"(brief \verbatim this is a verbatim block containing some verbatim text \endverbatim)", R"(\*\*@verbatim\*\* ``` this is a verbatim block containing some verbatim text ``` \*\*@endverbatim\*\*)", R"(**@verbatim** ``` this is a verbatim block containing some verbatim text ``` **@endverbatim**)", R"(**@verbatim** this is a verbatim block containing some verbatim text **@endverbatim**)", }, { "brief\n\n@param foo this is a parameter\n@param bar this is another " "parameter", "", "", "", }, { R"(@brief brief docs @param foo this is a parameter \brief another brief? \details these are details More description documentation)", R"(\*\*\\brief\*\* another brief? these are details More description documentation)", R"(**\brief** another brief? these are details More description documentation)", R"(**\brief** another brief? these are details More description documentation)", }, { R"(brief this is a bold text normal textthis is an italic text this is a code block)", R"(\this is a bold text\ normal text\this is an italic text\ \this is a code block\)", R"(\this is a bold text\ normal text\this is an italic text\ \this is a code block\)", "this is a bold text normal textthis is an italic text " "this is a code block", }, {"brief\n\n@note This is a note", R"(\*\*Note:\*\* This is a note)", R"(**Note:** This is a note)", R"(**Note:** This is a note)"}, {R"(brief Paragraph 1 @note This is a note Paragraph 2)", R"(Paragraph 1 \*\*Note:\*\* This is a note Paragraph 2)", R"(Paragraph 1 **Note:** This is a note Paragraph 2)", R"(Paragraph 1 **Note:** This is a note Paragraph 2)"}, {"brief\n\n@warning This is a warning", R"(\*\*Warning:\*\* This is a warning)", R"(**Warning:** This is a warning)", R"(**Warning:** This is a warning)"}, {R"(brief Paragraph 1 @warning This is a warning Paragraph 2)", R"(Paragraph 1 \*\*Warning:\*\* This is a warning Paragraph 2)", R"(Paragraph 1 **Warning:** This is a warning Paragraph 2)", R"(Paragraph 1 **Warning:** This is a warning Paragraph 2)"}, {R"(@note this is not treated as brief @brief this is the brief Another paragraph)", R"(\*\*Note:\*\* this is not treated as brief Another paragraph)", R"(**Note:** this is not treated as brief Another paragraph)", R"(**Note:** this is not treated as brief Another paragraph)"}, {R"( @brief Some brief )", "", "", ""}, {R"( Some brief )", "", "", ""}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.detailedDocToMarkup(Doc); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } TEST(SymbolDocumentation, RetvalCommand) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { {"@retval", "", "", ""}, {R"(@retval MyReturnVal @retval MyOtherReturnVal)", R"(- `MyReturnVal` - `MyOtherReturnVal`)", R"(- `MyReturnVal` - `MyOtherReturnVal`)", R"(- MyReturnVal - MyOtherReturnVal)"}, {R"(@retval MyReturnVal if foo @retval MyOtherReturnVal if bar)", R"(- `MyReturnVal` - if foo - `MyOtherReturnVal` - if bar)", R"(- `MyReturnVal` - if foo - `MyOtherReturnVal` - if bar)", R"(- MyReturnVal - if foo - MyOtherReturnVal - if bar)"}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.retvalsToMarkup(Doc); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } TEST(SymbolDocumentation, DoxygenCodeBlocks) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { {R"(@code int code() { return 0; } @endcode @code{.cpp} int code_lang() { return 0; } @endcode @code{.c++} int code_lang_plus() { return 0; } @endcode @code{.py} class A: pass @endcode @code{nolang} class B: pass @endcode)", R"(``` int code() { return 0; } ``` ```cpp int code_lang() { return 0; } ``` ```c++ int code_lang_plus() { return 0; } ``` ```py class A: pass ``` ```nolang class B: pass ```)", R"(``` int code() { return 0; } ``` ```cpp int code_lang() { return 0; } ``` ```c++ int code_lang_plus() { return 0; } ``` ```py class A: pass ``` ```nolang class B: pass ```)", R"(int code() { return 0; } int code_lang() { return 0; } int code_lang_plus() { return 0; } class A: pass class B: pass)"}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.detailedDocToMarkup(Doc); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } TEST(SymbolDocumentation, MarkdownCodeBlocks) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { {R"(``` int backticks() { return 0; } ``` ```cpp int backticks_lang() { return 0; } ``` ```c++ int backticks_lang_plus() { return 0; } ``` ~~~ int tilde() { return 0; } ~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ int tilde_many() { return 0; } ~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~{.c++} int tilde_many_lang() { return 0; } ~~~~~~~~~~~~~~~~~~~~~~~~ ```py class A: pass ``` ```python class B: pass ``` ~~~{.python} class C: pass ~~~ )", R"(``` int backticks() { return 0; } ``` ```cpp int backticks_lang() { return 0; } ``` ```c++ int backticks_lang_plus() { return 0; } ``` ``` int tilde() { return 0; } ``` ``` int tilde_many() { return 0; } ``` ```c++ int tilde_many_lang() { return 0; } ``` ```py class A: pass ``` ```python class B: pass ``` ```python class C: pass ```)", R"(``` int backticks() { return 0; } ``` ```cpp int backticks_lang() { return 0; } ``` ```c++ int backticks_lang_plus() { return 0; } ``` ``` int tilde() { return 0; } ``` ``` int tilde_many() { return 0; } ``` ```c++ int tilde_many_lang() { return 0; } ``` ```py class A: pass ``` ```python class B: pass ``` ```python class C: pass ```)", R"(int backticks() { return 0; } int backticks_lang() { return 0; } int backticks_lang_plus() { return 0; } int tilde() { return 0; } int tilde_many() { return 0; } int tilde_many_lang() { return 0; } class A: pass class B: pass class C: pass)"}, {R"(``` // this code block is missing end backticks )", R"(``` // this code block is missing end backticks ```)", R"(``` // this code block is missing end backticks ```)", R"(// this code block is missing end backticks)"}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.detailedDocToMarkup(Doc); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } TEST(SymbolDocumentation, MarkdownCodeBlocksSeparation) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { {R"(@note Show that code blocks are correctly separated ``` /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ```)", R"(\*\*Note:\*\* Show that code blocks are correctly separated ``` /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ```)", R"(**Note:** Show that code blocks are correctly separated ``` /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ```)", R"(**Note:** Show that code blocks are correctly separated /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; })"}, {R"(@note Show that code blocks are correctly separated ~~~~~~~~~ /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ~~~~~~~~~)", R"(\*\*Note:\*\* Show that code blocks are correctly separated ``` /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ```)", R"(**Note:** Show that code blocks are correctly separated ``` /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; } ```)", R"(**Note:** Show that code blocks are correctly separated /// Without the markdown preprocessing, this line and the line above would be part of the @note paragraph. /// With preprocessing, the code block is correctly separated from the @note paragraph. /// Also note that without preprocessing, all doxygen commands inside code blocks, like @p would be incorrectly interpreted. int function() { return 0; })"}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.detailedDocToMarkup(Doc); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } TEST(SymbolDocumentation, MarkdownCodeSpans) { CommentOptions CommentOpts; struct Case { llvm::StringRef Documentation; llvm::StringRef ExpectedRenderEscapedMarkdown; llvm::StringRef ExpectedRenderMarkdown; llvm::StringRef ExpectedRenderPlainText; } Cases[] = { {R"(`this is a code span with @p and \c inside`)", R"(\`this is a code span with @p and \\c inside\`)", R"(`this is a code span with @p and \c inside`)", R"(`this is a code span with @p and \c inside`)"}, {R"( ``)", R"(\ \`\\`)", R"(\ ``)", R"( ``)"}, {R"( \` doxygen commands not parsed @p, \c, @note, \warning \`)", R"(\ \\\`\ doxygen commands not parsed @p, \\c, @note, \\warning \\\`)", R"(\ \`\ doxygen commands not parsed @p, \c, @note, \warning \`)", R"( \` doxygen commands not parsed @p, \c, @note, \warning \`)"}, {R"(`multi line \c span`)", R"(\`multi line \\c span\`)", R"(`multi line \c span`)", R"(`multi line \c span`)"}, }; for (const auto &C : Cases) { markup::Document Doc; SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); SymbolDoc.briefToMarkup(Doc.addParagraph()); EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); } } } // namespace clangd } // namespace clang