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
14namespace clang {
15namespace clangd {
16namespace markup {
17namespace {
18
19std::string escape(llvm::StringRef Text) {
20 return Paragraph().appendText(Text: Text.str()).asMarkdown();
21}
22
23MATCHER_P(escaped, C, "") {
24 return testing::ExplainMatchResult(::testing::HasSubstr(substring: std::string{'\\', C}),
25 arg, result_listener);
26}
27
28MATCHER(escapedNone, "") {
29 return testing::ExplainMatchResult(::testing::Not(m: ::testing::HasSubstr(substring: "\\")),
30 arg, result_listener);
31}
32
33TEST(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(Code: "* 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(Code: "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(Code: "`foo");
122 EXPECT_EQ(P.asMarkdown(), "` ``foo `");
123 P = Paragraph();
124 P.appendCode(Code: "foo`");
125 EXPECT_EQ(P.asMarkdown(), "` foo`` `");
126 P = Paragraph();
127 P.appendCode(Code: "`foo`");
128 EXPECT_EQ(P.asMarkdown(), "` ``foo`` `");
129
130 // Code blocks might need more than 3 backticks.
131 Document D;
132 D.addCodeBlock(Code: "foobarbaz `\nqux");
133 EXPECT_EQ(D.asMarkdown(), "```cpp\n"
134 "foobarbaz `\nqux\n"
135 "```");
136 D = Document();
137 D.addCodeBlock(Code: "foobarbaz ``\nqux");
138 EXPECT_THAT(D.asMarkdown(), "```cpp\n"
139 "foobarbaz ``\nqux\n"
140 "```");
141 D = Document();
142 D.addCodeBlock(Code: "foobarbaz ```\nqux");
143 EXPECT_EQ(D.asMarkdown(), "````cpp\n"
144 "foobarbaz ```\nqux\n"
145 "````");
146 D = Document();
147 D.addCodeBlock(Code: "foobarbaz ` `` ``` ```` `\nqux");
148 EXPECT_EQ(D.asMarkdown(), "`````cpp\n"
149 "foobarbaz ` `` ``` ```` `\nqux\n"
150 "`````");
151}
152
153TEST(Paragraph, Chunks) {
154 Paragraph P = Paragraph();
155 P.appendText(Text: "One ");
156 P.appendCode(Code: "fish");
157 P.appendText(Text: ", two ");
158 P.appendCode(Code: "fish", /*Preserve=*/true);
159
160 EXPECT_EQ(P.asMarkdown(), "One `fish`, two `fish`");
161 EXPECT_EQ(P.asPlainText(), "One fish, two `fish`");
162}
163
164TEST(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(Text: "after ");
171 EXPECT_EQ(P.asMarkdown(), "after");
172 EXPECT_EQ(P.asPlainText(), "after");
173
174 P.appendCode(Code: "foobar").appendSpace();
175 EXPECT_EQ(P.asMarkdown(), "after `foobar`");
176 EXPECT_EQ(P.asPlainText(), "after foobar");
177
178 P.appendText(Text: "bat");
179 EXPECT_EQ(P.asMarkdown(), "after `foobar` bat");
180 EXPECT_EQ(P.asPlainText(), "after foobar bat");
181
182 P.appendCode(Code: "no").appendCode(Code: "space");
183 EXPECT_EQ(P.asMarkdown(), "after `foobar` bat`no` `space`");
184 EXPECT_EQ(P.asPlainText(), "after foobar batno space");
185}
186
187TEST(Paragraph, ExtraSpaces) {
188 // Make sure spaces inside chunks are dropped.
189 Paragraph P;
190 P.appendText(Text: "foo\n \t baz");
191 P.appendCode(Code: " bar\n");
192 EXPECT_EQ(P.asMarkdown(), "foo baz`bar`");
193 EXPECT_EQ(P.asPlainText(), "foo bazbar");
194}
195
196TEST(Paragraph, SpacesCollapsed) {
197 Paragraph P;
198 P.appendText(Text: " foo bar ");
199 P.appendText(Text: " baz ");
200 EXPECT_EQ(P.asMarkdown(), "foo bar baz");
201 EXPECT_EQ(P.asPlainText(), "foo bar baz");
202}
203
204TEST(Paragraph, NewLines) {
205 // New lines before and after chunks are dropped.
206 Paragraph P;
207 P.appendText(Text: " \n foo\nbar\n ");
208 P.appendCode(Code: " \n foo\nbar \n ");
209 EXPECT_EQ(P.asMarkdown(), "foo bar `foo bar`");
210 EXPECT_EQ(P.asPlainText(), "foo bar foo bar");
211}
212
213TEST(Document, Separators) {
214 Document D;
215 D.addParagraph().appendText(Text: "foo");
216 D.addCodeBlock(Code: "test");
217 D.addParagraph().appendText(Text: "bar");
218
219 const char ExpectedMarkdown[] = R"md(foo
220```cpp
221test
222```
223bar)md";
224 EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
225
226 const char ExpectedText[] = R"pt(foo
227
228test
229
230bar)pt";
231 EXPECT_EQ(D.asPlainText(), ExpectedText);
232}
233
234TEST(Document, Ruler) {
235 Document D;
236 D.addParagraph().appendText(Text: "foo");
237 D.addRuler();
238
239 // Ruler followed by paragraph.
240 D.addParagraph().appendText(Text: "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(Text: "foo");
246 D.addRuler();
247 D.addCodeBlock(Code: "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(Text: "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(Text: "foo");
263 EXPECT_EQ(D.asMarkdown(), "foo \n\n---\nfoo");
264 EXPECT_EQ(D.asPlainText(), "foo\n\nfoo");
265}
266
267TEST(Document, Append) {
268 Document D;
269 D.addParagraph().appendText(Text: "foo");
270 D.addRuler();
271 Document E;
272 E.addRuler();
273 E.addParagraph().appendText(Text: "bar");
274 D.append(Other: std::move(E));
275 EXPECT_EQ(D.asMarkdown(), "foo \n\n---\nbar");
276}
277
278TEST(Document, Heading) {
279 Document D;
280 D.addHeading(Level: 1).appendText(Text: "foo");
281 D.addHeading(Level: 2).appendText(Text: "bar");
282 D.addParagraph().appendText(Text: "baz");
283 EXPECT_EQ(D.asMarkdown(), "# foo \n## bar \nbaz");
284 EXPECT_EQ(D.asPlainText(), "foo\nbar\nbaz");
285}
286
287TEST(CodeBlock, Render) {
288 Document D;
289 // Code blocks preserves any extra spaces.
290 D.addCodeBlock(Code: "foo\n bar\n baz");
291
292 llvm::StringRef ExpectedMarkdown =
293 R"md(```cpp
294foo
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(Code: "foo");
305 ExpectedMarkdown =
306 R"md(```cpp
307foo
308 bar
309 baz
310```
311```cpp
312foo
313```)md";
314 EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
315 ExpectedPlainText =
316 R"pt(foo
317 bar
318 baz
319
320foo)pt";
321 EXPECT_EQ(D.asPlainText(), ExpectedPlainText);
322}
323
324TEST(BulletList, Render) {
325 BulletList L;
326 // Flat list
327 L.addItem().addParagraph().appendText(Text: "foo");
328 EXPECT_EQ(L.asMarkdown(), "- foo");
329 EXPECT_EQ(L.asPlainText(), "- foo");
330
331 L.addItem().addParagraph().appendText(Text: "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(Text: "foo");
341 D.addParagraph().appendText(Text: "baz");
342
343 // Nest one level.
344 Document &Inner = D.addBulletList().addItem();
345 Inner.addParagraph().appendText(Text: "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(Text: "baz");
352 DeepDoc.addParagraph().appendText(Text: "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(Text: "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

source code of clang-tools-extra/clangd/unittests/support/MarkupTests.cpp