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 238 TEST_F(FormatTestCSharp, CSharpUsing) { 239 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 240 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 241 verifyFormat("public void foo () {\n" 242 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 243 " using () {}\n" 244 "}", 245 Style); 246 247 // Ensure clang-format affects top-level snippets correctly. 248 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 249 Style); 250 251 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 252 verifyFormat("public void foo() {\n" 253 " using(StreamWriter sw = new StreamWriter(filenameB)) {}\n" 254 " using() {}\n" 255 "}", 256 Style); 257 258 // Ensure clang-format affects top-level snippets correctly. 259 verifyFormat("using(StreamWriter sw = new StreamWriter(filenameB)) {}", 260 Style); 261 262 Style.SpaceBeforeParens = FormatStyle::SBPO_ControlStatements; 263 verifyFormat("public void foo() {\n" 264 " using (StreamWriter sw = new StreamWriter(filenameA)) {}\n" 265 " using () {}\n" 266 "}", 267 Style); 268 269 // Ensure clang-format affects top-level snippets correctly. 270 verifyFormat("using (StreamWriter sw = new StreamWriter(filenameB)) {}", 271 Style); 272 273 Style.SpaceBeforeParens = FormatStyle::SBPO_NonEmptyParentheses; 274 verifyFormat("public void foo() {\n" 275 " using (StreamWriter sw = new StreamWriter (filenameA)) {}\n" 276 " using() {}\n" 277 "}", 278 Style); 279 280 // Ensure clang-format affects top-level snippets correctly. 281 verifyFormat("using (StreamWriter sw = new StreamWriter (filenameB)) {}", 282 Style); 283 } 284 285 TEST_F(FormatTestCSharp, CSharpRegions) { 286 verifyFormat("#region aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa " 287 "aaaaaaaaaaaaaaa long region"); 288 } 289 290 TEST_F(FormatTestCSharp, CSharpKeyWordEscaping) { 291 verifyFormat("public enum var { none, @string, bool, @enum }"); 292 } 293 294 TEST_F(FormatTestCSharp, CSharpNullCoalescing) { 295 verifyFormat("var test = ABC ?? DEF"); 296 verifyFormat("string myname = name ?? \"ABC\";"); 297 verifyFormat("return _name ?? \"DEF\";"); 298 } 299 300 TEST_F(FormatTestCSharp, AttributesIndentation) { 301 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 302 Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None; 303 304 verifyFormat("[STAThread]\n" 305 "static void Main(string[] args)\n" 306 "{\n" 307 "}", 308 Style); 309 310 verifyFormat("[STAThread]\n" 311 "void " 312 "veryLooooooooooooooongFunctionName(string[] args)\n" 313 "{\n" 314 "}", 315 Style); 316 317 verifyFormat("[STAThread]\n" 318 "veryLoooooooooooooooooooongReturnType " 319 "veryLooooooooooooooongFunctionName(string[] args)\n" 320 "{\n" 321 "}", 322 Style); 323 324 verifyFormat("[SuppressMessage(\"A\", \"B\", Justification = \"C\")]\n" 325 "public override X Y()\n" 326 "{\n" 327 "}\n", 328 Style); 329 330 verifyFormat("[SuppressMessage]\n" 331 "public X Y()\n" 332 "{\n" 333 "}\n", 334 Style); 335 336 verifyFormat("[SuppressMessage]\n" 337 "public override X Y()\n" 338 "{\n" 339 "}\n", 340 Style); 341 342 verifyFormat("public A(B b) : base(b)\n" 343 "{\n" 344 " [SuppressMessage]\n" 345 " public override X Y()\n" 346 " {\n" 347 " }\n" 348 "}\n", 349 Style); 350 351 verifyFormat("public A : Base\n" 352 "{\n" 353 "}\n" 354 "[Test]\n" 355 "public Foo()\n" 356 "{\n" 357 "}\n", 358 Style); 359 360 verifyFormat("namespace\n" 361 "{\n" 362 "public A : Base\n" 363 "{\n" 364 "}\n" 365 "[Test]\n" 366 "public Foo()\n" 367 "{\n" 368 "}\n" 369 "}\n", 370 Style); 371 } 372 373 TEST_F(FormatTestCSharp, CSharpSpaceBefore) { 374 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 375 Style.SpaceBeforeParens = FormatStyle::SBPO_Always; 376 377 verifyFormat("List<string> list;", Style); 378 verifyFormat("Dictionary<string, string> dict;", Style); 379 380 verifyFormat("for (int i = 0; i < size (); i++) {\n" 381 "}", 382 Style); 383 verifyFormat("foreach (var x in y) {\n" 384 "}", 385 Style); 386 verifyFormat("switch (x) {}", Style); 387 verifyFormat("do {\n" 388 "} while (x);", 389 Style); 390 391 Style.SpaceBeforeParens = FormatStyle::SBPO_Never; 392 393 verifyFormat("List<string> list;", Style); 394 verifyFormat("Dictionary<string, string> dict;", Style); 395 396 verifyFormat("for(int i = 0; i < size(); i++) {\n" 397 "}", 398 Style); 399 verifyFormat("foreach(var x in y) {\n" 400 "}", 401 Style); 402 verifyFormat("switch(x) {}", Style); 403 verifyFormat("do {\n" 404 "} while(x);", 405 Style); 406 } 407 408 TEST_F(FormatTestCSharp, CSharpSpaceAfterCStyleCast) { 409 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 410 411 verifyFormat("(int)x / y;", Style); 412 413 Style.SpaceAfterCStyleCast = true; 414 verifyFormat("(int) x / y;", Style); 415 } 416 417 TEST_F(FormatTestCSharp, CSharpEscapedQuotesInVerbatimStrings) { 418 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 419 420 verifyFormat(R"(string str = @"""";)", Style); 421 verifyFormat(R"(string str = @"""Hello world""";)", Style); 422 verifyFormat(R"(string str = $@"""Hello {friend}""";)", Style); 423 } 424 425 TEST_F(FormatTestCSharp, CSharpQuotesInInterpolatedStrings) { 426 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 427 428 verifyFormat(R"(string str1 = $"{null ?? "null"}";)", Style); 429 verifyFormat(R"(string str2 = $"{{{braceCount} braces";)", Style); 430 verifyFormat(R"(string str3 = $"{braceCount}}} braces";)", Style); 431 } 432 433 TEST_F(FormatTestCSharp, CSharpNewlinesInVerbatimStrings) { 434 // Use MS style as Google Style inserts a line break before multiline strings. 435 436 // verifyFormat does not understand multiline C# string-literals 437 // so check the format explicitly. 438 439 FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp); 440 441 std::string Code = R"(string s1 = $@"some code: 442 class {className} {{ 443 {className}() {{}} 444 }}";)"; 445 446 EXPECT_EQ(Code, format(Code, Style)); 447 448 // Multiline string in the middle of a function call. 449 Code = R"( 450 var x = foo(className, $@"some code: 451 class {className} {{ 452 {className}() {{}} 453 }}", 454 y);)"; // y aligned with `className` arg. 455 456 EXPECT_EQ(Code, format(Code, Style)); 457 458 // Interpolated string with embedded multiline string. 459 Code = R"(Console.WriteLine($"{string.Join(@", 460 ", values)}");)"; 461 462 EXPECT_EQ(Code, format(Code, Style)); 463 } 464 465 TEST_F(FormatTestCSharp, CSharpObjectInitializers) { 466 FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); 467 468 // Start code fragemnts with a comment line so that C++ raw string literals 469 // as seen are identical to expected formatted code. 470 471 verifyFormat(R"(// 472 Shape[] shapes = new[] { 473 new Circle { 474 Radius = 2.7281, 475 Colour = Colours.Red, 476 }, 477 new Square { 478 Side = 101.1, 479 Colour = Colours.Yellow, 480 }, 481 };)", 482 Style); 483 484 // Omitted final `,`s will change the formatting. 485 verifyFormat(R"(// 486 Shape[] shapes = new[] {new Circle {Radius = 2.7281, Colour = Colours.Red}, 487 new Square { 488 Side = 101.1, 489 Colour = Colours.Yellow, 490 }};)", 491 Style); 492 } 493 494 } // namespace format 495 } // end namespace clang 496