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 &#42; bar"), escaped('&'));
97   EXPECT_THAT(escape("foo &#xaf; 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