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