aboutsummaryrefslogtreecommitdiff
path: root/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp')
-rw-r--r--clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp575
1 files changed, 548 insertions, 27 deletions
diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
index 31a6a14..b3185cc 100644
--- a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
@@ -15,7 +15,7 @@
namespace clang {
namespace clangd {
-TEST(SymbolDocumentation, UnhandledDocs) {
+TEST(SymbolDocumentation, DetailedDocToMarkup) {
CommentOptions CommentOpts;
@@ -26,52 +26,52 @@ TEST(SymbolDocumentation, UnhandledDocs) {
llvm::StringRef ExpectedRenderPlainText;
} Cases[] = {
{
- "foo bar",
+ "brief\n\nfoo bar",
"foo bar",
"foo bar",
"foo bar",
},
{
- "foo\nbar\n",
+ "brief\n\nfoo\nbar\n",
"foo\nbar",
"foo\nbar",
"foo bar",
},
{
- "foo\n\nbar\n",
+ "brief\n\nfoo\n\nbar\n",
"foo\n\nbar",
"foo\n\nbar",
"foo\n\nbar",
},
{
- "foo \\p bar baz",
+ "brief\n\nfoo \\p bar baz",
"foo `bar` baz",
"foo `bar` baz",
"foo bar baz",
},
{
- "foo \\e bar baz",
+ "brief\n\nfoo \\e bar baz",
"foo \\*bar\\* baz",
"foo *bar* baz",
"foo *bar* baz",
},
{
- "foo \\b bar baz",
+ "brief\n\nfoo \\b bar baz",
"foo \\*\\*bar\\*\\* baz",
"foo **bar** baz",
"foo **bar** baz",
},
{
- "foo \\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",
},
{
- "foo @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",
@@ -80,10 +80,10 @@ TEST(SymbolDocumentation, UnhandledDocs) {
"",
},
{
- "\\throw exception foo",
- "\\*\\*\\\\throw\\*\\* \\*exception\\* foo",
- "**\\throw** *exception* foo",
- "**\\throw** *exception* foo",
+ "brief\n\n\\throw exception foo",
+ "\\*\\*\\\\throw\\*\\* `exception` foo",
+ "**\\throw** `exception` foo",
+ "**\\throw** exception foo",
},
{
R"(\brief this is a brief description
@@ -108,7 +108,8 @@ TEST(SymbolDocumentation, UnhandledDocs) {
- item 3)",
},
{
- "\\defgroup mygroup this is a group\nthis is not a group description",
+ "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",
@@ -118,7 +119,9 @@ TEST(SymbolDocumentation, UnhandledDocs) {
"description",
},
{
- R"(\verbatim
+ R"(brief
+
+\verbatim
this is a
verbatim block containing
some verbatim text
@@ -150,7 +153,7 @@ some verbatim text
**@endverbatim**)",
},
{
- "@param foo this is a parameter\n@param bar this is another "
+ "brief\n\n@param foo this is a parameter\n@param bar this is another "
"parameter",
"",
"",
@@ -169,24 +172,26 @@ More description
documentation)",
R"(\*\*\\brief\*\* another brief?
-\*\*\\details\*\* these are details
+these are details
More description
documentation)",
R"(**\brief** another brief?
-**\details** these are details
+these are details
More description
documentation)",
R"(**\brief** another brief?
-**\details** these are details
+these are details
More description documentation)",
},
{
- R"(<b>this is a bold text</b>
+ R"(brief
+
+<b>this is a bold text</b>
normal text<i>this is an italic text</i>
<code>this is a code block</code>)",
R"(\<b>this is a bold text\</b>
@@ -198,12 +203,528 @@ normal text\<i>this is an italic text\</i>
"<b>this is a bold text</b> normal text<i>this is an italic text</i> "
"<code>this is a code block</code>",
},
+ {"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"(<escaped> `<not-escaped>`)", R"(\<escaped> \`\<not-escaped>\`)",
+ R"(\<escaped> `<not-escaped>`)", R"(<escaped> `<not-escaped>`)"},
+ {R"(<escaped> \`<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)",
+ R"(\<escaped> \\\`\<escaped> doxygen commands not parsed @p, \\c, @note, \\warning \\\`)",
+ R"(\<escaped> \`\<escaped> doxygen commands not parsed @p, \c, @note, \warning \`)",
+ R"(<escaped> \`<escaped> 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.docToMarkup(Doc);
+ SymbolDoc.briefToMarkup(Doc.addParagraph());
EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);