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 75 TEST_F(FormatTestCSharp, AccessModifiers) { 76 verifyFormat("public String toString()\n" 77 "{\n" 78 "}"); 79 verifyFormat("private String toString()\n" 80 "{\n" 81 "}"); 82 verifyFormat("protected String toString()\n" 83 "{\n" 84 "}"); 85 verifyFormat("internal String toString()\n" 86 "{\n" 87 "}"); 88 89 verifyFormat("public override String toString()\n" 90 "{\n" 91 "}"); 92 verifyFormat("private override String toString()\n" 93 "{\n" 94 "}"); 95 verifyFormat("protected override String toString()\n" 96 "{\n" 97 "}"); 98 verifyFormat("internal override String toString()\n" 99 "{\n" 100 "}"); 101 102 verifyFormat("internal static String toString()\n" 103 "{\n" 104 "}"); 105 } 106 107 TEST_F(FormatTestCSharp, NoStringLiteralBreaks) { 108 verifyFormat("foo(" 109 "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 110 "aaaaaa\");"); 111 } 112 113 TEST_F(FormatTestCSharp, CSharpVerbatiumStringLiterals) { 114 verifyFormat("foo(@\"aaaaaaaa\\abc\\aaaa\");"); 115 // @"ABC\" + ToString("B") - handle embedded \ in literal string at 116 // the end 117 // 118 /* 119 * After removal of Lexer change we are currently not able 120 * To handle these cases 121 verifyFormat("string s = @\"ABC\\\" + ToString(\"B\");"); 122 verifyFormat("string s = @\"ABC\"\"DEF\"\"GHI\""); 123 verifyFormat("string s = @\"ABC\"\"DEF\"\"\""); 124 verifyFormat("string s = @\"ABC\"\"DEF\"\"\" + abc"); 125 */ 126 } 127 128 TEST_F(FormatTestCSharp, CSharpInterpolatedStringLiterals) { 129 verifyFormat("foo($\"aaaaaaaa{aaa}aaaa\");"); 130 verifyFormat("foo($\"aaaa{A}\");"); 131 verifyFormat( 132 "foo($\"aaaa{A}" 133 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");"); 134 verifyFormat("Name = $\"{firstName} {lastName}\";"); 135 136 // $"ABC\" + ToString("B") - handle embedded \ in literal string at 137 // the end 138 verifyFormat("string s = $\"A{abc}BC\" + ToString(\"B\");"); 139 verifyFormat("$\"{domain}\\\\{user}\""); 140 verifyFormat( 141 "var verbatimInterpolated = $@\"C:\\Users\\{userName}\\Documents\\\";"); 142 } 143 144 TEST_F(FormatTestCSharp, CSharpFatArrows) { 145 verifyFormat("Task serverTask = Task.Run(async() => {"); 146 verifyFormat("public override string ToString() => \"{Name}\\{Age}\";"); 147 } 148 149 TEST_F(FormatTestCSharp, CSharpNullConditional) { 150 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 151 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 152 153 verifyFormat( 154 "public Person(string firstName, string lastName, int? age=null)"); 155 156 verifyFormat("foo () {\n" 157 " switch (args?.Length) {}\n" 158 "}", 159 Style); 160 161 verifyFormat("switch (args?.Length) {}", Style); 162 163 verifyFormat("public static void Main(string[] args)\n" 164 "{\n" 165 " string dirPath = args?[0];\n" 166 "}"); 167 168 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 169 170 verifyFormat("switch(args?.Length) {}", Style); 171 } 172 173 TEST_F(FormatTestCSharp, Attributes) { 174 verifyFormat("[STAThread]\n" 175 "static void Main(string[] args)\n" 176 "{\n" 177 "}"); 178 179 verifyFormat("[TestMethod]\n" 180 "private class Test\n" 181 "{\n" 182 "}"); 183 184 verifyFormat("[TestMethod]\n" 185 "protected class Test\n" 186 "{\n" 187 "}"); 188 189 verifyFormat("[TestMethod]\n" 190 "internal class Test\n" 191 "{\n" 192 "}"); 193 194 verifyFormat("[TestMethod]\n" 195 "class Test\n" 196 "{\n" 197 "}"); 198 199 verifyFormat("[TestMethod]\n" 200 "[DeploymentItem(\"Test.txt\")]\n" 201 "public class Test\n" 202 "{\n" 203 "}"); 204 205 verifyFormat("[System.AttributeUsage(System.AttributeTargets.Method)]\n" 206 "[System.Runtime.InteropServices.ComVisible(true)]\n" 207 "public sealed class STAThreadAttribute : Attribute\n" 208 "{\n" 209 "}"); 210 211 verifyFormat("[Verb(\"start\", HelpText = \"Starts the server listening on " 212 "provided port\")]\n" 213 "class Test\n" 214 "{\n" 215 "}"); 216 217 verifyFormat("[TestMethod]\n" 218 "public string Host\n" 219 "{\n" 220 " set;\n" 221 " get;\n" 222 "}"); 223 224 verifyFormat("[TestMethod(\"start\", HelpText = \"Starts the server " 225 "listening on provided host\")]\n" 226 "public string Host\n" 227 "{\n" 228 " set;\n" 229 " get;\n" 230 "}"); 231 232 verifyFormat( 233 "[DllImport(\"Hello\", EntryPoint = \"hello_world\")]\n" 234 "// The const char* returned by hello_world must not be deleted.\n" 235 "private static extern IntPtr HelloFromCpp();)"); 236 237 // Unwrappable lines go on a line of their own. 238 // 'target:' is not treated as a label. 239 // Modify Style to enforce a column limit. 240 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 241 Style.ColumnLimit = 10; 242 verifyFormat(R"([assembly:InternalsVisibleTo( 243 "SomeAssembly, PublicKey=SomePublicKeyThatExceedsTheColumnLimit")])", 244 Style); 245 } 246 247 TEST_F(FormatTestCSharp, CSharpUsing) { 248 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 249 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 250 verifyFormat("public void foo () {\n" 251 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 252 " using () {}\n" 253 "}", 254 Style); 255 256 // Ensure clang-format affects top-level snippets correctly. 257 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 258 Style); 259 260 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 261 verifyFormat("public void foo() {\n" 262 " using(StreamWriter sw = new StreamWriter(filenameB)) {}\n" 263 " using() {}\n" 264 "}", 265 Style); 266 267 // Ensure clang-format affects top-level snippets correctly. 268 verifyFormat("using(StreamWriter sw = new StreamWriter(filenameB)) {}", 269 Style); 270 271 Style.SpaceBeforeParens = FormatStyle::SBPO_ControlStatements; 272 verifyFormat("public void foo() {\n" 273 " using (StreamWriter sw = new StreamWriter(filenameA)) {}\n" 274 " using () {}\n" 275 "}", 276 Style); 277 278 // Ensure clang-format affects top-level snippets correctly. 279 verifyFormat("using (StreamWriter sw = new StreamWriter(filenameB)) {}", 280 Style); 281 282 Style.SpaceBeforeParens = FormatStyle::SBPO_NonEmptyParentheses; 283 verifyFormat("public void foo() {\n" 284 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 285 " using() {}\n" 286 "}", 287 Style); 288 289 // Ensure clang-format affects top-level snippets correctly. 290 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 291 Style); 292 } 293 294 TEST_F(FormatTestCSharp, CSharpRegions) { 295 verifyFormat("#region aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa " 296 "aaaaaaaaaaaaaaa long region"); 297 } 298 299 TEST_F(FormatTestCSharp, CSharpKeyWordEscaping) { 300 verifyFormat("public enum var { none, @string, bool, @enum }"); 301 } 302 303 TEST_F(FormatTestCSharp, CSharpNullCoalescing) { 304 verifyFormat("var test = ABC ?? DEF"); 305 verifyFormat("string myname = name ?? \"ABC\";"); 306 verifyFormat("return _name ?? \"DEF\";"); 307 } 308 309 TEST_F(FormatTestCSharp, AttributesIndentation) { 310 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 311 Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None; 312 313 verifyFormat("[STAThread]\n" 314 "static void Main(string[] args)\n" 315 "{\n" 316 "}", 317 Style); 318 319 verifyFormat("[STAThread]\n" 320 "void " 321 "veryLooooooooooooooongFunctionName(string[] args)\n" 322 "{\n" 323 "}", 324 Style); 325 326 verifyFormat("[STAThread]\n" 327 "veryLoooooooooooooooooooongReturnType " 328 "veryLooooooooooooooongFunctionName(string[] args)\n" 329 "{\n" 330 "}", 331 Style); 332 333 verifyFormat("[SuppressMessage(\"A\", \"B\", Justification = \"C\")]\n" 334 "public override X Y()\n" 335 "{\n" 336 "}\n", 337 Style); 338 339 verifyFormat("[SuppressMessage]\n" 340 "public X Y()\n" 341 "{\n" 342 "}\n", 343 Style); 344 345 verifyFormat("[SuppressMessage]\n" 346 "public override X Y()\n" 347 "{\n" 348 "}\n", 349 Style); 350 351 verifyFormat("public A(B b) : base(b)\n" 352 "{\n" 353 " [SuppressMessage]\n" 354 " public override X Y()\n" 355 " {\n" 356 " }\n" 357 "}\n", 358 Style); 359 360 verifyFormat("public A : Base\n" 361 "{\n" 362 "}\n" 363 "[Test]\n" 364 "public Foo()\n" 365 "{\n" 366 "}\n", 367 Style); 368 369 verifyFormat("namespace\n" 370 "{\n" 371 "public A : Base\n" 372 "{\n" 373 "}\n" 374 "[Test]\n" 375 "public Foo()\n" 376 "{\n" 377 "}\n" 378 "}\n", 379 Style); 380 } 381 382 TEST_F(FormatTestCSharp, CSharpSpaceBefore) { 383 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 384 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 385 386 verifyFormat("List<string> list;", Style); 387 verifyFormat("Dictionary<string, string> dict;", Style); 388 389 verifyFormat("for (int i = 0; i < size (); i++) {\n" 390 "}", 391 Style); 392 verifyFormat("foreach (var x in y) {\n" 393 "}", 394 Style); 395 verifyFormat("switch (x) {}", Style); 396 verifyFormat("do {\n" 397 "} while (x);", 398 Style); 399 400 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 401 402 verifyFormat("List<string> list;", Style); 403 verifyFormat("Dictionary<string, string> dict;", Style); 404 405 verifyFormat("for(int i = 0; i < size(); i++) {\n" 406 "}", 407 Style); 408 verifyFormat("foreach(var x in y) {\n" 409 "}", 410 Style); 411 verifyFormat("switch(x) {}", Style); 412 verifyFormat("do {\n" 413 "} while(x);", 414 Style); 415 } 416 417 TEST_F(FormatTestCSharp, CSharpSpaceAfterCStyleCast) { 418 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 419 420 verifyFormat("(int)x / y;", Style); 421 422 Style.SpaceAfterCStyleCast = true; 423 verifyFormat("(int) x / y;", Style); 424 } 425 426 TEST_F(FormatTestCSharp, CSharpEscapedQuotesInVerbatimStrings) { 427 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 428 429 verifyFormat(R"(string str = @"""";)", Style); 430 verifyFormat(R"(string str = @"""Hello world""";)", Style); 431 verifyFormat(R"(string str = $@"""Hello {friend}""";)", Style); 432 } 433 434 TEST_F(FormatTestCSharp, CSharpQuotesInInterpolatedStrings) { 435 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 436 437 verifyFormat(R"(string str1 = $"{null ?? "null"}";)", Style); 438 verifyFormat(R"(string str2 = $"{{{braceCount} braces";)", Style); 439 verifyFormat(R"(string str3 = $"{braceCount}}} braces";)", Style); 440 } 441 442 TEST_F(FormatTestCSharp, CSharpNewlinesInVerbatimStrings) { 443 // Use MS style as Google Style inserts a line break before multiline strings. 444 445 // verifyFormat does not understand multiline C# string-literals 446 // so check the format explicitly. 447 448 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 449 450 std::string Code = R"(string s1 = $@"some code: 451 class {className} {{ 452 {className}() {{}} 453 }}";)"; 454 455 EXPECT_EQ(Code, format(Code, Style)); 456 457 // Multiline string in the middle of a function call. 458 Code = R"( 459 var x = foo(className, $@"some code: 460 class {className} {{ 461 {className}() {{}} 462 }}", 463 y);)"; // y aligned with `className` arg. 464 465 EXPECT_EQ(Code, format(Code, Style)); 466 467 // Interpolated string with embedded multiline string. 468 Code = R"(Console.WriteLine($"{string.Join(@", 469 ", values)}");)"; 470 471 EXPECT_EQ(Code, format(Code, Style)); 472 } 473 474 TEST_F(FormatTestCSharp, CSharpObjectInitializers) { 475 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 476 477 // Start code fragemnts with a comment line so that C++ raw string literals 478 // as seen are identical to expected formatted code. 479 480 verifyFormat(R"(// 481 Shape[] shapes = new[] { 482 new Circle { 483 Radius = 2.7281, 484 Colour = Colours.Red, 485 }, 486 new Square { 487 Side = 101.1, 488 Colour = Colours.Yellow, 489 }, 490 };)", 491 Style); 492 493 // Omitted final `,`s will change the formatting. 494 verifyFormat(R"(// 495 Shape[] shapes = new[] {new Circle {Radius = 2.7281, Colour = Colours.Red}, 496 new Square { 497 Side = 101.1, 498 Colour = Colours.Yellow, 499 }};)", 500 Style); 501 } 502 503 } // namespace format 504 } // end namespace clang 505