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 * bar"), escaped('&'));
96 EXPECT_THAT(escape("foo ¯ 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