1 //===- unittest/Format/FormatTestCSharp.cpp - Formatting tests for CSharp -===// 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 9 #include "FormatTestUtils.h" 10 #include "clang/Format/Format.h" 11 #include "llvm/Support/Debug.h" 12 #include "gtest/gtest.h" 13 14 #define DEBUG_TYPE "format-test" 15 16 namespace clang { 17 namespace format { 18 19 class FormatTestCSharp : public ::testing::Test { 20 protected: 21 static std::string format(llvm::StringRef Code, unsigned Offset, 22 unsigned Length, const FormatStyle &Style) { 23 LLVM_DEBUG(llvm::errs() << "---\n"); 24 LLVM_DEBUG(llvm::errs() << Code << "\n\n"); 25 std::vector<tooling::Range> Ranges(1, tooling::Range(Offset, Length)); 26 tooling::Replacements Replaces = reformat(Style, Code, Ranges); 27 auto Result = applyAllReplacements(Code, Replaces); 28 EXPECT_TRUE(static_cast<bool>(Result)); 29 LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); 30 return *Result; 31 } 32 33 static std::string 34 format(llvm::StringRef Code, 35 const FormatStyle &Style = getMicrosoftStyle(FormatStyle::LK_CSharp)) { 36 return format(Code, 0, Code.size(), Style); 37 } 38 39 static FormatStyle getStyleWithColumns(unsigned ColumnLimit) { 40 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 41 Style.ColumnLimit = ColumnLimit; 42 return Style; 43 } 44 45 static void verifyFormat( 46 llvm::StringRef Code, 47 const FormatStyle &Style = getMicrosoftStyle(FormatStyle::LK_CSharp)) { 48 EXPECT_EQ(Code.str(), format(Code, Style)) << "Expected code is not stable"; 49 EXPECT_EQ(Code.str(), format(test::messUp(Code), Style)); 50 } 51 }; 52 53 TEST_F(FormatTestCSharp, CSharpClass) { 54 verifyFormat("public class SomeClass\n" 55 "{\n" 56 " void f()\n" 57 " {\n" 58 " }\n" 59 " int g()\n" 60 " {\n" 61 " return 0;\n" 62 " }\n" 63 " void h()\n" 64 " {\n" 65 " while (true)\n" 66 " f();\n" 67 " for (;;)\n" 68 " f();\n" 69 " if (true)\n" 70 " f();\n" 71 " }\n" 72 "}"); 73 74 // Ensure that small and empty classes are handled correctly with condensed 75 // (Google C++-like) brace-breaking style. 76 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 77 Style.BreakBeforeBraces = FormatStyle::BS_Attach; 78 79 verifyFormat("public class SomeEmptyClass {}", Style); 80 81 verifyFormat("public class SomeTinyClass {\n" 82 " int X;\n" 83 "}", 84 Style); 85 verifyFormat("private class SomeTinyClass {\n" 86 " int X;\n" 87 "}", 88 Style); 89 verifyFormat("protected class SomeTinyClass {\n" 90 " int X;\n" 91 "}", 92 Style); 93 verifyFormat("internal class SomeTinyClass {\n" 94 " int X;\n" 95 "}", 96 Style); 97 } 98 99 TEST_F(FormatTestCSharp, AccessModifiers) { 100 verifyFormat("public String toString()\n" 101 "{\n" 102 "}"); 103 verifyFormat("private String toString()\n" 104 "{\n" 105 "}"); 106 verifyFormat("protected String toString()\n" 107 "{\n" 108 "}"); 109 verifyFormat("internal String toString()\n" 110 "{\n" 111 "}"); 112 113 verifyFormat("public override String toString()\n" 114 "{\n" 115 "}"); 116 verifyFormat("private override String toString()\n" 117 "{\n" 118 "}"); 119 verifyFormat("protected override String toString()\n" 120 "{\n" 121 "}"); 122 verifyFormat("internal override String toString()\n" 123 "{\n" 124 "}"); 125 126 verifyFormat("internal static String toString()\n" 127 "{\n" 128 "}"); 129 } 130 131 TEST_F(FormatTestCSharp, NoStringLiteralBreaks) { 132 verifyFormat("foo(" 133 "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 134 "aaaaaa\");"); 135 } 136 137 TEST_F(FormatTestCSharp, CSharpVerbatiumStringLiterals) { 138 verifyFormat("foo(@\"aaaaaaaa\\abc\\aaaa\");"); 139 // @"ABC\" + ToString("B") - handle embedded \ in literal string at 140 // the end 141 // 142 /* 143 * After removal of Lexer change we are currently not able 144 * To handle these cases 145 verifyFormat("string s = @\"ABC\\\" + ToString(\"B\");"); 146 verifyFormat("string s = @\"ABC\"\"DEF\"\"GHI\""); 147 verifyFormat("string s = @\"ABC\"\"DEF\"\"\""); 148 verifyFormat("string s = @\"ABC\"\"DEF\"\"\" + abc"); 149 */ 150 } 151 152 TEST_F(FormatTestCSharp, CSharpInterpolatedStringLiterals) { 153 verifyFormat("foo($\"aaaaaaaa{aaa}aaaa\");"); 154 verifyFormat("foo($\"aaaa{A}\");"); 155 verifyFormat( 156 "foo($\"aaaa{A}" 157 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");"); 158 verifyFormat("Name = $\"{firstName} {lastName}\";"); 159 160 // $"ABC\" + ToString("B") - handle embedded \ in literal string at 161 // the end 162 verifyFormat("string s = $\"A{abc}BC\" + ToString(\"B\");"); 163 verifyFormat("$\"{domain}\\\\{user}\""); 164 verifyFormat( 165 "var verbatimInterpolated = $@\"C:\\Users\\{userName}\\Documents\\\";"); 166 } 167 168 TEST_F(FormatTestCSharp, CSharpFatArrows) { 169 verifyFormat("Task serverTask = Task.Run(async() => {"); 170 verifyFormat("public override string ToString() => \"{Name}\\{Age}\";"); 171 } 172 173 TEST_F(FormatTestCSharp, CSharpNullConditional) { 174 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 175 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 176 177 verifyFormat( 178 "public Person(string firstName, string lastName, int? age=null)"); 179 180 verifyFormat("foo () {\n" 181 " switch (args?.Length) {}\n" 182 "}", 183 Style); 184 185 verifyFormat("switch (args?.Length) {}", Style); 186 187 verifyFormat("public static void Main(string[] args)\n" 188 "{\n" 189 " string dirPath = args?[0];\n" 190 "}"); 191 192 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 193 194 verifyFormat("switch(args?.Length) {}", Style); 195 } 196 197 TEST_F(FormatTestCSharp, Attributes) { 198 verifyFormat("[STAThread]\n" 199 "static void Main(string[] args)\n" 200 "{\n" 201 "}"); 202 203 verifyFormat("[TestMethod]\n" 204 "private class Test\n" 205 "{\n" 206 "}"); 207 208 verifyFormat("[TestMethod]\n" 209 "protected class Test\n" 210 "{\n" 211 "}"); 212 213 verifyFormat("[TestMethod]\n" 214 "internal class Test\n" 215 "{\n" 216 "}"); 217 218 verifyFormat("[TestMethod]\n" 219 "class Test\n" 220 "{\n" 221 "}"); 222 223 verifyFormat("[TestMethod]\n" 224 "[DeploymentItem(\"Test.txt\")]\n" 225 "public class Test\n" 226 "{\n" 227 "}"); 228 229 verifyFormat("[System.AttributeUsage(System.AttributeTargets.Method)]\n" 230 "[System.Runtime.InteropServices.ComVisible(true)]\n" 231 "public sealed class STAThreadAttribute : Attribute\n" 232 "{\n" 233 "}"); 234 235 verifyFormat("[Verb(\"start\", HelpText = \"Starts the server listening on " 236 "provided port\")]\n" 237 "class Test\n" 238 "{\n" 239 "}"); 240 241 verifyFormat("[TestMethod]\n" 242 "public string Host\n" 243 "{\n" 244 " set;\n" 245 " get;\n" 246 "}"); 247 248 verifyFormat("[TestMethod(\"start\", HelpText = \"Starts the server " 249 "listening on provided host\")]\n" 250 "public string Host\n" 251 "{\n" 252 " set;\n" 253 " get;\n" 254 "}"); 255 256 verifyFormat( 257 "[DllImport(\"Hello\", EntryPoint = \"hello_world\")]\n" 258 "// The const char* returned by hello_world must not be deleted.\n" 259 "private static extern IntPtr HelloFromCpp();)"); 260 261 // Class attributes go on their own line and do not affect layout of 262 // interfaces. Line wrapping decisions previously caused each interface to be 263 // on its own line. 264 verifyFormat("[SomeAttribute]\n" 265 "[SomeOtherAttribute]\n" 266 "public class A : IShape, IAnimal, IVehicle\n" 267 "{\n" 268 " int X;\n" 269 "}"); 270 271 // Attributes in a method declaration do not cause line wrapping. 272 verifyFormat("void MethodA([In][Out] ref double x)\n" 273 "{\n" 274 "}"); 275 276 // Unwrappable lines go on a line of their own. 277 // 'target:' is not treated as a label. 278 // Modify Style to enforce a column limit. 279 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 280 Style.ColumnLimit = 10; 281 verifyFormat(R"([assembly:InternalsVisibleTo( 282 "SomeAssembly, PublicKey=SomePublicKeyThatExceedsTheColumnLimit")])", 283 Style); 284 } 285 286 TEST_F(FormatTestCSharp, CSharpUsing) { 287 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 288 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 289 verifyFormat("public void foo () {\n" 290 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 291 " using () {}\n" 292 "}", 293 Style); 294 295 // Ensure clang-format affects top-level snippets correctly. 296 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 297 Style); 298 299 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 300 verifyFormat("public void foo() {\n" 301 " using(StreamWriter sw = new StreamWriter(filenameB)) {}\n" 302 " using() {}\n" 303 "}", 304 Style); 305 306 // Ensure clang-format affects top-level snippets correctly. 307 verifyFormat("using(StreamWriter sw = new StreamWriter(filenameB)) {}", 308 Style); 309 310 Style.SpaceBeforeParens = FormatStyle::SBPO_ControlStatements; 311 verifyFormat("public void foo() {\n" 312 " using (StreamWriter sw = new StreamWriter(filenameA)) {}\n" 313 " using () {}\n" 314 "}", 315 Style); 316 317 // Ensure clang-format affects top-level snippets correctly. 318 verifyFormat("using (StreamWriter sw = new StreamWriter(filenameB)) {}", 319 Style); 320 321 Style.SpaceBeforeParens = FormatStyle::SBPO_NonEmptyParentheses; 322 verifyFormat("public void foo() {\n" 323 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 324 " using() {}\n" 325 "}", 326 Style); 327 328 // Ensure clang-format affects top-level snippets correctly. 329 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 330 Style); 331 } 332 333 TEST_F(FormatTestCSharp, CSharpRegions) { 334 verifyFormat("#region aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa " 335 "aaaaaaaaaaaaaaa long region"); 336 } 337 338 TEST_F(FormatTestCSharp, CSharpKeyWordEscaping) { 339 verifyFormat("public enum var { none, @string, bool, @enum }"); 340 } 341 342 TEST_F(FormatTestCSharp, CSharpNullCoalescing) { 343 verifyFormat("var test = ABC ?? DEF"); 344 verifyFormat("string myname = name ?? \"ABC\";"); 345 verifyFormat("return _name ?? \"DEF\";"); 346 } 347 348 TEST_F(FormatTestCSharp, AttributesIndentation) { 349 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 350 Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None; 351 352 verifyFormat("[STAThread]\n" 353 "static void Main(string[] args)\n" 354 "{\n" 355 "}", 356 Style); 357 358 verifyFormat("[STAThread]\n" 359 "void " 360 "veryLooooooooooooooongFunctionName(string[] args)\n" 361 "{\n" 362 "}", 363 Style); 364 365 verifyFormat("[STAThread]\n" 366 "veryLoooooooooooooooooooongReturnType " 367 "veryLooooooooooooooongFunctionName(string[] args)\n" 368 "{\n" 369 "}", 370 Style); 371 372 verifyFormat("[SuppressMessage(\"A\", \"B\", Justification = \"C\")]\n" 373 "public override X Y()\n" 374 "{\n" 375 "}\n", 376 Style); 377 378 verifyFormat("[SuppressMessage]\n" 379 "public X Y()\n" 380 "{\n" 381 "}\n", 382 Style); 383 384 verifyFormat("[SuppressMessage]\n" 385 "public override X Y()\n" 386 "{\n" 387 "}\n", 388 Style); 389 390 verifyFormat("public A(B b) : base(b)\n" 391 "{\n" 392 " [SuppressMessage]\n" 393 " public override X Y()\n" 394 " {\n" 395 " }\n" 396 "}\n", 397 Style); 398 399 verifyFormat("public A : Base\n" 400 "{\n" 401 "}\n" 402 "[Test]\n" 403 "public Foo()\n" 404 "{\n" 405 "}\n", 406 Style); 407 408 verifyFormat("namespace\n" 409 "{\n" 410 "public A : Base\n" 411 "{\n" 412 "}\n" 413 "[Test]\n" 414 "public Foo()\n" 415 "{\n" 416 "}\n" 417 "}\n", 418 Style); 419 } 420 421 TEST_F(FormatTestCSharp, CSharpSpaceBefore) { 422 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 423 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 424 425 verifyFormat("List<string> list;", Style); 426 verifyFormat("Dictionary<string, string> dict;", Style); 427 428 verifyFormat("for (int i = 0; i < size (); i++) {\n" 429 "}", 430 Style); 431 verifyFormat("foreach (var x in y) {\n" 432 "}", 433 Style); 434 verifyFormat("switch (x) {}", Style); 435 verifyFormat("do {\n" 436 "} while (x);", 437 Style); 438 439 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 440 441 verifyFormat("List<string> list;", Style); 442 verifyFormat("Dictionary<string, string> dict;", Style); 443 444 verifyFormat("for(int i = 0; i < size(); i++) {\n" 445 "}", 446 Style); 447 verifyFormat("foreach(var x in y) {\n" 448 "}", 449 Style); 450 verifyFormat("switch(x) {}", Style); 451 verifyFormat("do {\n" 452 "} while(x);", 453 Style); 454 } 455 456 TEST_F(FormatTestCSharp, CSharpSpaceAfterCStyleCast) { 457 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 458 459 verifyFormat("(int)x / y;", Style); 460 461 Style.SpaceAfterCStyleCast = true; 462 verifyFormat("(int) x / y;", Style); 463 } 464 465 TEST_F(FormatTestCSharp, CSharpEscapedQuotesInVerbatimStrings) { 466 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 467 468 verifyFormat(R"(string str = @"""";)", Style); 469 verifyFormat(R"(string str = @"""Hello world""";)", Style); 470 verifyFormat(R"(string str = $@"""Hello {friend}""";)", Style); 471 } 472 473 TEST_F(FormatTestCSharp, CSharpQuotesInInterpolatedStrings) { 474 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 475 476 verifyFormat(R"(string str1 = $"{null ?? "null"}";)", Style); 477 verifyFormat(R"(string str2 = $"{{{braceCount} braces";)", Style); 478 verifyFormat(R"(string str3 = $"{braceCount}}} braces";)", Style); 479 } 480 481 TEST_F(FormatTestCSharp, CSharpNewlinesInVerbatimStrings) { 482 // Use MS style as Google Style inserts a line break before multiline strings. 483 484 // verifyFormat does not understand multiline C# string-literals 485 // so check the format explicitly. 486 487 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 488 489 std::string Code = R"(string s1 = $@"some code: 490 class {className} {{ 491 {className}() {{}} 492 }}";)"; 493 494 EXPECT_EQ(Code, format(Code, Style)); 495 496 // Multiline string in the middle of a function call. 497 Code = R"( 498 var x = foo(className, $@"some code: 499 class {className} {{ 500 {className}() {{}} 501 }}", 502 y);)"; // y aligned with `className` arg. 503 504 EXPECT_EQ(Code, format(Code, Style)); 505 506 // Interpolated string with embedded multiline string. 507 Code = R"(Console.WriteLine($"{string.Join(@", 508 ", values)}");)"; 509 510 EXPECT_EQ(Code, format(Code, Style)); 511 } 512 513 TEST_F(FormatTestCSharp, CSharpObjectInitializers) { 514 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 515 516 // Start code fragments with a comment line so that C++ raw string literals 517 // as seen are identical to expected formatted code. 518 519 verifyFormat(R"(// 520 Shape[] shapes = new[] { 521 new Circle { 522 Radius = 2.7281, 523 Colour = Colours.Red, 524 }, 525 new Square { 526 Side = 101.1, 527 Colour = Colours.Yellow, 528 }, 529 };)", 530 Style); 531 532 // Omitted final `,`s will change the formatting. 533 verifyFormat(R"(// 534 Shape[] shapes = new[] {new Circle {Radius = 2.7281, Colour = Colours.Red}, 535 new Square { 536 Side = 101.1, 537 Colour = Colours.Yellow, 538 }};)", 539 Style); 540 } 541 542 TEST_F(FormatTestCSharp, CSharpNamedArguments) { 543 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 544 545 verifyFormat(R"(// 546 PrintOrderDetails(orderNum: 31, productName: "Red Mug", 547 sellerName: "Gift Shop");)", 548 Style); 549 550 // Ensure that trailing comments do not cause problems. 551 verifyFormat(R"(// 552 PrintOrderDetails(orderNum: 31, productName: "Red Mug", // comment 553 sellerName: "Gift Shop");)", 554 Style); 555 } 556 557 } // namespace format 558 } // end namespace clang 559