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