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 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 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 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 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 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 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 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 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 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 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 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 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 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