xref: /llvm-project/clang/unittests/Format/FormatTestCSharp.cpp (revision 50d8977c459de302fcef7a2578b0e8f8862a2fe0)
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