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