xref: /llvm-project/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp (revision 4d006520b8c0cc3a52913b4665bf741c737e5592)
1 //===-- MarkupTests.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 //===----------------------------------------------------------------------===//
8 #include "support/Markup.h"
9 #include "clang/Basic/LLVM.h"
10 #include "llvm/ADT/StringRef.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13 
14 namespace clang {
15 namespace clangd {
16 namespace markup {
17 namespace {
18 
escape(llvm::StringRef Text)19 std::string escape(llvm::StringRef Text) {
20   return Paragraph().appendText(Text.str()).asMarkdown();
21 }
22 
23 MATCHER_P(escaped, C, "") {
24   return testing::ExplainMatchResult(::testing::HasSubstr(std::string{'\\', C}),
25                                      arg, result_listener);
26 }
27 
28 MATCHER(escapedNone, "") {
29   return testing::ExplainMatchResult(::testing::Not(::testing::HasSubstr("\\")),
30                                      arg, result_listener);
31 }
32 
TEST(Render,Escaping)33 TEST(Render, Escaping) {
34   // Check all ASCII punctuation.
35   std::string Punctuation = R"txt(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)txt";
36   std::string EscapedPunc = R"txt(!"#$%&'()\*+,-./:;<=>?@[\\]^\_\`{|}~)txt";
37   EXPECT_EQ(escape(Punctuation), EscapedPunc);
38 
39   // Inline code
40   EXPECT_EQ(escape("`foo`"), R"(\`foo\`)");
41   EXPECT_EQ(escape("`foo"), R"(\`foo)");
42   EXPECT_EQ(escape("foo`"), R"(foo\`)");
43   EXPECT_EQ(escape("``foo``"), R"(\`\`foo\`\`)");
44   // Code blocks
45   EXPECT_EQ(escape("```"), R"(\`\`\`)"); // This could also be inline code!
46   EXPECT_EQ(escape("~~~"), R"(\~~~)");
47 
48   // Rulers and headings
49   EXPECT_THAT(escape("## Heading"), escaped('#'));
50   EXPECT_THAT(escape("Foo # bar"), escapedNone());
51   EXPECT_EQ(escape("---"), R"(\---)");
52   EXPECT_EQ(escape("-"), R"(\-)");
53   EXPECT_EQ(escape("==="), R"(\===)");
54   EXPECT_EQ(escape("="), R"(\=)");
55   EXPECT_EQ(escape("***"), R"(\*\*\*)"); // \** could start emphasis!
56 
57   // HTML tags.
58   EXPECT_THAT(escape("<pre"), escaped('<'));
59   EXPECT_THAT(escape("< pre"), escapedNone());
60   EXPECT_THAT(escape("if a<b then"), escaped('<'));
61   EXPECT_THAT(escape("if a<b then c."), escapedNone());
62   EXPECT_THAT(escape("if a<b then c='foo'."), escaped('<'));
63   EXPECT_THAT(escape("std::vector<T>"), escaped('<'));
64   EXPECT_THAT(escape("std::vector<std::string>"), escaped('<'));
65   EXPECT_THAT(escape("std::map<int, int>"), escapedNone());
66   // Autolinks
67   EXPECT_THAT(escape("Email <foo@bar.com>"), escapedNone());
68   EXPECT_THAT(escape("Website <http://foo.bar>"), escapedNone());
69 
70   // Bullet lists.
71   EXPECT_THAT(escape("- foo"), escaped('-'));
72   EXPECT_THAT(escape("* foo"), escaped('*'));
73   EXPECT_THAT(escape("+ foo"), escaped('+'));
74   EXPECT_THAT(escape("+"), escaped('+'));
75   EXPECT_THAT(escape("a + foo"), escapedNone());
76   EXPECT_THAT(escape("a+ foo"), escapedNone());
77   EXPECT_THAT(escape("1. foo"), escaped('.'));
78   EXPECT_THAT(escape("a. foo"), escapedNone());
79 
80   // Emphasis.
81   EXPECT_EQ(escape("*foo*"), R"(\*foo\*)");
82   EXPECT_EQ(escape("**foo**"), R"(\*\*foo\*\*)");
83   EXPECT_THAT(escape("*foo"), escaped('*'));
84   EXPECT_THAT(escape("foo *"), escapedNone());
85   EXPECT_THAT(escape("foo * bar"), escapedNone());
86   EXPECT_THAT(escape("foo_bar"), escapedNone());
87   EXPECT_THAT(escape("foo _bar"), escaped('_'));
88   EXPECT_THAT(escape("foo_ bar"), escaped('_'));
89   EXPECT_THAT(escape("foo _ bar"), escapedNone());
90 
91   // HTML entities.
92   EXPECT_THAT(escape("fish &chips;"), escaped('&'));
93   EXPECT_THAT(escape("fish & chips;"), escapedNone());
94   EXPECT_THAT(escape("fish &chips"), escapedNone());
95   EXPECT_THAT(escape("foo &#42; bar"), escaped('&'));
96   EXPECT_THAT(escape("foo &#xaf; bar"), escaped('&'));
97   EXPECT_THAT(escape("foo &?; bar"), escapedNone());
98 
99   // Links.
100   EXPECT_THAT(escape("[foo](bar)"), escaped(']'));
101   EXPECT_THAT(escape("[foo]: bar"), escaped(']'));
102   // No need to escape these, as the target never exists.
103   EXPECT_THAT(escape("[foo][]"), escapedNone());
104   EXPECT_THAT(escape("[foo][bar]"), escapedNone());
105   EXPECT_THAT(escape("[foo]"), escapedNone());
106 
107   // In code blocks we don't need to escape ASCII punctuation.
108   Paragraph P = Paragraph();
109   P.appendCode("* foo !+ bar * baz");
110   EXPECT_EQ(P.asMarkdown(), "`* foo !+ bar * baz`");
111 
112   // But we have to escape the backticks.
113   P = Paragraph();
114   P.appendCode("foo`bar`baz", /*Preserve=*/true);
115   EXPECT_EQ(P.asMarkdown(), "`foo``bar``baz`");
116   // In plain-text, we fall back to different quotes.
117   EXPECT_EQ(P.asPlainText(), "'foo`bar`baz'");
118 
119   // Inline code blocks starting or ending with backticks should add spaces.
120   P = Paragraph();
121   P.appendCode("`foo");
122   EXPECT_EQ(P.asMarkdown(), "` ``foo `");
123   P = Paragraph();
124   P.appendCode("foo`");
125   EXPECT_EQ(P.asMarkdown(), "` foo`` `");
126   P = Paragraph();
127   P.appendCode("`foo`");
128   EXPECT_EQ(P.asMarkdown(), "` ``foo`` `");
129 
130   // Code blocks might need more than 3 backticks.
131   Document D;
132   D.addCodeBlock("foobarbaz `\nqux");
133   EXPECT_EQ(D.asMarkdown(), "```cpp\n"
134                             "foobarbaz `\nqux\n"
135                             "```");
136   D = Document();
137   D.addCodeBlock("foobarbaz ``\nqux");
138   EXPECT_THAT(D.asMarkdown(), "```cpp\n"
139                               "foobarbaz ``\nqux\n"
140                               "```");
141   D = Document();
142   D.addCodeBlock("foobarbaz ```\nqux");
143   EXPECT_EQ(D.asMarkdown(), "````cpp\n"
144                             "foobarbaz ```\nqux\n"
145                             "````");
146   D = Document();
147   D.addCodeBlock("foobarbaz ` `` ``` ```` `\nqux");
148   EXPECT_EQ(D.asMarkdown(), "`````cpp\n"
149                             "foobarbaz ` `` ``` ```` `\nqux\n"
150                             "`````");
151 }
152 
TEST(Paragraph,Chunks)153 TEST(Paragraph, Chunks) {
154   Paragraph P = Paragraph();
155   P.appendText("One ");
156   P.appendCode("fish");
157   P.appendText(", two ");
158   P.appendCode("fish", /*Preserve=*/true);
159 
160   EXPECT_EQ(P.asMarkdown(), "One `fish`, two `fish`");
161   EXPECT_EQ(P.asPlainText(), "One fish, two `fish`");
162 }
163 
TEST(Paragraph,SeparationOfChunks)164 TEST(Paragraph, SeparationOfChunks) {
165   // This test keeps appending contents to a single Paragraph and checks
166   // expected accumulated contents after each one.
167   // Purpose is to check for separation between different chunks.
168   Paragraph P;
169 
170   P.appendText("after ");
171   EXPECT_EQ(P.asMarkdown(), "after");
172   EXPECT_EQ(P.asPlainText(), "after");
173 
174   P.appendCode("foobar").appendSpace();
175   EXPECT_EQ(P.asMarkdown(), "after `foobar`");
176   EXPECT_EQ(P.asPlainText(), "after foobar");
177 
178   P.appendText("bat");
179   EXPECT_EQ(P.asMarkdown(), "after `foobar` bat");
180   EXPECT_EQ(P.asPlainText(), "after foobar bat");
181 
182   P.appendCode("no").appendCode("space");
183   EXPECT_EQ(P.asMarkdown(), "after `foobar` bat`no` `space`");
184   EXPECT_EQ(P.asPlainText(), "after foobar batno space");
185 }
186 
TEST(Paragraph,ExtraSpaces)187 TEST(Paragraph, ExtraSpaces) {
188   // Make sure spaces inside chunks are dropped.
189   Paragraph P;
190   P.appendText("foo\n   \t   baz");
191   P.appendCode(" bar\n");
192   EXPECT_EQ(P.asMarkdown(), "foo baz`bar`");
193   EXPECT_EQ(P.asPlainText(), "foo bazbar");
194 }
195 
TEST(Paragraph,SpacesCollapsed)196 TEST(Paragraph, SpacesCollapsed) {
197   Paragraph P;
198   P.appendText(" foo bar ");
199   P.appendText(" baz ");
200   EXPECT_EQ(P.asMarkdown(), "foo bar baz");
201   EXPECT_EQ(P.asPlainText(), "foo bar baz");
202 }
203 
TEST(Paragraph,NewLines)204 TEST(Paragraph, NewLines) {
205   // New lines before and after chunks are dropped.
206   Paragraph P;
207   P.appendText(" \n foo\nbar\n ");
208   P.appendCode(" \n foo\nbar \n ");
209   EXPECT_EQ(P.asMarkdown(), "foo bar `foo bar`");
210   EXPECT_EQ(P.asPlainText(), "foo bar foo bar");
211 }
212 
TEST(Document,Separators)213 TEST(Document, Separators) {
214   Document D;
215   D.addParagraph().appendText("foo");
216   D.addCodeBlock("test");
217   D.addParagraph().appendText("bar");
218 
219   const char ExpectedMarkdown[] = R"md(foo
220 ```cpp
221 test
222 ```
223 bar)md";
224   EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
225 
226   const char ExpectedText[] = R"pt(foo
227 
228 test
229 
230 bar)pt";
231   EXPECT_EQ(D.asPlainText(), ExpectedText);
232 }
233 
TEST(Document,Ruler)234 TEST(Document, Ruler) {
235   Document D;
236   D.addParagraph().appendText("foo");
237   D.addRuler();
238 
239   // Ruler followed by paragraph.
240   D.addParagraph().appendText("bar");
241   EXPECT_EQ(D.asMarkdown(), "foo  \n\n---\nbar");
242   EXPECT_EQ(D.asPlainText(), "foo\n\nbar");
243 
244   D = Document();
245   D.addParagraph().appendText("foo");
246   D.addRuler();
247   D.addCodeBlock("bar");
248   // Ruler followed by a codeblock.
249   EXPECT_EQ(D.asMarkdown(), "foo  \n\n---\n```cpp\nbar\n```");
250   EXPECT_EQ(D.asPlainText(), "foo\n\nbar");
251 
252   // Ruler followed by another ruler
253   D = Document();
254   D.addParagraph().appendText("foo");
255   D.addRuler();
256   D.addRuler();
257   EXPECT_EQ(D.asMarkdown(), "foo");
258   EXPECT_EQ(D.asPlainText(), "foo");
259 
260   // Multiple rulers between blocks
261   D.addRuler();
262   D.addParagraph().appendText("foo");
263   EXPECT_EQ(D.asMarkdown(), "foo  \n\n---\nfoo");
264   EXPECT_EQ(D.asPlainText(), "foo\n\nfoo");
265 }
266 
TEST(Document,Append)267 TEST(Document, Append) {
268   Document D;
269   D.addParagraph().appendText("foo");
270   D.addRuler();
271   Document E;
272   E.addRuler();
273   E.addParagraph().appendText("bar");
274   D.append(std::move(E));
275   EXPECT_EQ(D.asMarkdown(), "foo  \n\n---\nbar");
276 }
277 
TEST(Document,Heading)278 TEST(Document, Heading) {
279   Document D;
280   D.addHeading(1).appendText("foo");
281   D.addHeading(2).appendText("bar");
282   D.addParagraph().appendText("baz");
283   EXPECT_EQ(D.asMarkdown(), "# foo  \n## bar  \nbaz");
284   EXPECT_EQ(D.asPlainText(), "foo\nbar\nbaz");
285 }
286 
TEST(CodeBlock,Render)287 TEST(CodeBlock, Render) {
288   Document D;
289   // Code blocks preserves any extra spaces.
290   D.addCodeBlock("foo\n  bar\n  baz");
291 
292   llvm::StringRef ExpectedMarkdown =
293       R"md(```cpp
294 foo
295   bar
296   baz
297 ```)md";
298   llvm::StringRef ExpectedPlainText =
299       R"pt(foo
300   bar
301   baz)pt";
302   EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
303   EXPECT_EQ(D.asPlainText(), ExpectedPlainText);
304   D.addCodeBlock("foo");
305   ExpectedMarkdown =
306       R"md(```cpp
307 foo
308   bar
309   baz
310 ```
311 ```cpp
312 foo
313 ```)md";
314   EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
315   ExpectedPlainText =
316       R"pt(foo
317   bar
318   baz
319 
320 foo)pt";
321   EXPECT_EQ(D.asPlainText(), ExpectedPlainText);
322 }
323 
TEST(BulletList,Render)324 TEST(BulletList, Render) {
325   BulletList L;
326   // Flat list
327   L.addItem().addParagraph().appendText("foo");
328   EXPECT_EQ(L.asMarkdown(), "- foo");
329   EXPECT_EQ(L.asPlainText(), "- foo");
330 
331   L.addItem().addParagraph().appendText("bar");
332   llvm::StringRef Expected = R"md(- foo
333 - bar)md";
334   EXPECT_EQ(L.asMarkdown(), Expected);
335   EXPECT_EQ(L.asPlainText(), Expected);
336 
337   // Nested list, with a single item.
338   Document &D = L.addItem();
339   // First item with foo\nbaz
340   D.addParagraph().appendText("foo");
341   D.addParagraph().appendText("baz");
342 
343   // Nest one level.
344   Document &Inner = D.addBulletList().addItem();
345   Inner.addParagraph().appendText("foo");
346 
347   // Nest one more level.
348   BulletList &InnerList = Inner.addBulletList();
349   // Single item, baz\nbaz
350   Document &DeepDoc = InnerList.addItem();
351   DeepDoc.addParagraph().appendText("baz");
352   DeepDoc.addParagraph().appendText("baz");
353   StringRef ExpectedMarkdown = R"md(- foo
354 - bar
355 - foo
356   baz
357   - foo
358     - baz
359       baz)md";
360   EXPECT_EQ(L.asMarkdown(), ExpectedMarkdown);
361   StringRef ExpectedPlainText = R"pt(- foo
362 - bar
363 - foo
364   baz
365   - foo
366     - baz
367       baz)pt";
368   EXPECT_EQ(L.asPlainText(), ExpectedPlainText);
369 
370   // Termination
371   Inner.addParagraph().appendText("after");
372   ExpectedMarkdown = R"md(- foo
373 - bar
374 - foo
375   baz
376   - foo
377     - baz
378       baz
379 
380     after)md";
381   EXPECT_EQ(L.asMarkdown(), ExpectedMarkdown);
382   ExpectedPlainText = R"pt(- foo
383 - bar
384 - foo
385   baz
386   - foo
387     - baz
388       baz
389     after)pt";
390   EXPECT_EQ(L.asPlainText(), ExpectedPlainText);
391 }
392 
393 } // namespace
394 } // namespace markup
395 } // namespace clangd
396 } // namespace clang
397