79fc411464bacef8e0bc8685bc73a936c2a5e16c
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 using System;
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Linq;
9 using System.Text;
10 using System.Text.RegularExpressions.Generator;
11 using System.Threading;
12 using System.Threading.Tasks;
13 using Microsoft.CodeAnalysis;
14 using Microsoft.CodeAnalysis.CSharp;
15 using Microsoft.CodeAnalysis.Testing;
16 using Xunit;
17 using VerifyCS = System.Text.RegularExpressions.Tests.CSharpCodeFixVerifier<
18     System.Text.RegularExpressions.Generator.UpgradeToGeneratedRegexAnalyzer,
19     System.Text.RegularExpressions.Generator.UpgradeToGeneratedRegexCodeFixer>;
20
21 namespace System.Text.RegularExpressions.Tests
22 {
23     [ActiveIssue("https://github.com/dotnet/runtime/issues/69823", TestRuntimes.Mono)]
24     public class UpgradeToGeneratedRegexAnalyzerTests
25     {
26         private const string UseRegexSourceGeneratorDiagnosticId = @"SYSLIB1045";
27
28         [Fact]
29         public async Task NoDiagnosticsForEmpty()
30             => await VerifyCS.VerifyAnalyzerAsync(source: string.Empty);
31
32         public static IEnumerable<object[]> ConstructorWithTimeoutTestData()
33         {
34             yield return new object[] { @"using System;
35 using System.Text.RegularExpressions;
36
37 public class Program
38 {
39     public static void Main(string[] args)
40     {
41         var regex = new Regex("""", RegexOptions.None, TimeSpan.FromSeconds(10));
42     }
43 }" };
44
45             yield return new object[] { @"using System;
46 using System.Text.RegularExpressions;
47
48 public class Program
49 {
50     public static void Main(string[] args)
51     {
52         var regex = new Regex("""", matchTimeout: TimeSpan.FromSeconds(10), options: RegexOptions.None);
53     }
54 }" };
55
56             yield return new object[] { @"using System;
57 using System.Text.RegularExpressions;
58
59 public class Program
60 {
61     public static void Main(string[] args)
62     {
63         var regex = new Regex(matchTimeout: TimeSpan.FromSeconds(10), pattern: """", options: RegexOptions.None);
64     }
65 }" };
66
67             yield return new object[] { @"using System;
68 using System.Text.RegularExpressions;
69
70 public class Program
71 {
72     public static void Main(string[] args)
73     {
74         var regex = new Regex(matchTimeout: TimeSpan.FromSeconds(10), options: RegexOptions.None, pattern: """");
75     }
76 }" };
77         }
78
79         [Theory]
80         [MemberData(nameof(ConstructorWithTimeoutTestData))]
81         public async Task NoDiagnosticForConstructorWithTimeout(string test)
82         {
83             await VerifyCS.VerifyAnalyzerAsync(test);
84         }
85
86         [Theory]
87         [MemberData(nameof(InvocationTypes))]
88         public async Task TopLevelStatements(InvocationType invocationType)
89         {
90             string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
91             string test = @"using System.Text.RegularExpressions;
92 var isMatch = [|" + ConstructRegexInvocation(invocationType, pattern: "\"\"") + @"|]" + isMatchInvocation + ";";
93             string fixedCode = @"using System.Text.RegularExpressions;
94 var isMatch = MyRegex().IsMatch("""");
95
96 partial class Program
97 {
98     [GeneratedRegex("""")]
99     private static partial Regex MyRegex();
100 }";
101             await new VerifyCS.Test
102             {
103                 TestCode = test,
104                 FixedCode = fixedCode,
105                 TestState = { OutputKind = OutputKind.ConsoleApplication },
106             }.RunAsync();
107         }
108
109         public static IEnumerable<object[]> StaticInvocationWithTimeoutTestData()
110         {
111             foreach (string method in new[] { "Count", "EnumerateMatches", "IsMatch", "Match", "Matches", "Split" })
112             {
113                 yield return new object[] { @"using System;
114 using System.Text.RegularExpressions;
115
116 public class Program
117 {
118     public static void Main(string[] args)
119     {
120         Regex." + method + @"(""input"", ""a|b"", RegexOptions.None, TimeSpan.FromSeconds(10));
121     }
122 }" };
123             }
124
125             // Replace is special since it takes an extra argument
126             yield return new object[] { @"using System;
127 using System.Text.RegularExpressions;
128
129 public class Program
130 {
131     public static void Main(string[] args)
132     {
133         Regex.Replace(""input"", ""a|b"", ""replacement"" ,RegexOptions.None, TimeSpan.FromSeconds(10));
134     }
135 }" };
136         }
137
138         [Theory]
139         [MemberData(nameof(StaticInvocationWithTimeoutTestData))]
140         public async Task NoDiagnosticForStaticInvocationWithTimeout(string test)
141             => await VerifyCS.VerifyAnalyzerAsync(test);
142
143         [Theory]
144         [MemberData(nameof(InvocationTypes))]
145         public async Task NoDiagnosticsForNet60(InvocationType invocationType)
146         {
147             string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
148             string test = @"using System.Text;
149 using System.Text.RegularExpressions;
150
151 public class Program
152 {
153     public static void Main(string[] args)
154     {
155         var isMatch = " + ConstructRegexInvocation(invocationType, pattern: "\"\"") + isMatchInvocation + @";
156     }
157 }";
158
159             await VerifyCS.VerifyAnalyzerAsync(test, ReferenceAssemblies.Net.Net60);
160         }
161
162         [Theory]
163         [MemberData(nameof(InvocationTypes))]
164         public async Task NoDiagnosticsForLowerLanguageVersion(InvocationType invocationType)
165         {
166             string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
167             string test = @"using System.Text;
168 using System.Text.RegularExpressions;
169
170 public class Program
171 {
172     public static void Main(string[] args)
173     {
174         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"") + isMatchInvocation + @";
175     }
176 }";
177             await new VerifyCS.Test
178             {
179                 TestCode = test,
180                 FixedCode = test,
181                 LanguageVersion = LanguageVersion.CSharp10,
182             }.RunAsync();
183         }
184
185         [Fact]
186         public async Task CodeFixSupportsInvalidPatternFromWhichOptionsCanBeParsed()
187         {
188             string pattern = ".{99999999999}"; // throws during real parse
189             string test = $@"using System.Text;
190 using System.Text.RegularExpressions;
191
192 public class Program
193 {{
194     public static void Main(string[] args)
195     {{
196         var isMatch = [|Regex.IsMatch("""", @""{pattern}"")|];
197     }}
198 }}";
199
200             string fixedSource = @$"using System.Text;
201 using System.Text.RegularExpressions;
202
203 public partial class Program
204 {{
205     public static void Main(string[] args)
206     {{
207         var isMatch = MyRegex().IsMatch("""");
208     }}
209
210     [GeneratedRegex(@""{pattern}"")]
211     private static partial Regex MyRegex();
212 }}";
213             // We successfully offer the diagnostic and make the fix despite the pattern
214             // being invalid, because it was valid enough to parse out any options in the pattern.
215             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
216         }
217
218         [Fact]
219         public async Task CodeFixIsNotOfferedForInvalidPatternFromWhichOptionsCannotBeParsed()
220         {
221             string pattern = "\\g"; // throws during pre-parse for options
222             string test = $@"using System.Text;
223 using System.Text.RegularExpressions;
224
225 public class Program
226 {{
227     public static void Main(string[] args)
228     {{
229         var isMatch = Regex.IsMatch("""", @""{pattern}""); // fixer not offered
230     }}
231 }}";
232             // We need to be able to parse the pattern sufficiently that we can extract
233             // any inline options; in this case we can't, so we don't offer the fix.
234             await VerifyCS.VerifyCodeFixAsync(test, test);
235         }
236
237         public static IEnumerable<object[]> ConstantPatternTestData()
238         {
239             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
240             {
241                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
242                 // Test constructor with a passed in literal pattern.
243                 yield return new object[] { @"using System.Text;
244 using System.Text.RegularExpressions;
245
246 public class Program
247 {
248     public static void Main(string[] args)
249     {
250         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"") + @"|]" + isMatchInvocation + @";
251     }
252 }", @"using System.Text;
253 using System.Text.RegularExpressions;
254
255 public partial class Program
256 {
257     public static void Main(string[] args)
258     {
259         var isMatch = MyRegex().IsMatch("""");
260     }
261
262     [GeneratedRegex("""")]
263     private static partial Regex MyRegex();
264 }" };
265
266                 // Test constructor with a local constant pattern.
267                 yield return new object[] { @"using System.Text;
268 using System.Text.RegularExpressions;
269
270 public class Program
271 {
272     public static void Main(string[] args)
273     {
274         const string pattern = @"""";
275         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"") + @"|]" + isMatchInvocation + @";
276     }
277 }", @"using System.Text;
278 using System.Text.RegularExpressions;
279
280 public partial class Program
281 {
282     public static void Main(string[] args)
283     {
284         const string pattern = @"""";
285         var isMatch = MyRegex().IsMatch("""");
286     }
287
288     [GeneratedRegex("""")]
289     private static partial Regex MyRegex();
290 }" };
291
292                 // Test constructor with a constant field pattern.
293                 yield return new object[] { @"using System.Text;
294 using System.Text.RegularExpressions;
295
296 public class Program
297 {
298     private const string pattern = @"""";
299
300     public static void Main(string[] args)
301     {
302         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"") + @"|]" + isMatchInvocation + @";
303     }
304 }", @"using System.Text;
305 using System.Text.RegularExpressions;
306
307 public partial class Program
308 {
309     private const string pattern = @"""";
310
311     public static void Main(string[] args)
312     {
313         var isMatch = MyRegex().IsMatch("""");
314     }
315
316     [GeneratedRegex("""")]
317     private static partial Regex MyRegex();
318 }" };
319             }
320         }
321
322         [Theory]
323         [MemberData(nameof(ConstantPatternTestData))]
324         public async Task DiagnosticEmittedForConstantPattern(string test, string fixedSource)
325         {
326             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
327         }
328
329         public static IEnumerable<object[]> VariablePatternTestData()
330         {
331             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
332             {
333                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
334                 // Test constructor with passed in parameter
335                 yield return new object[] { @"using System.Text;
336 using System.Text.RegularExpressions;
337
338 public class Program
339 {
340     public static void Main(string[] args)
341     {
342         var isMatch = " + ConstructRegexInvocation(invocationType, "args[0]") + isMatchInvocation + @";
343     }
344 }" };
345
346                 // Test constructor with passed in variable
347                 yield return new object[] { @"using System.Text;
348 using System.Text.RegularExpressions;
349
350 public class Program
351 {
352     public static void Main(string[] args)
353     {
354         string somePattern = """";
355         var isMatch = " + ConstructRegexInvocation(invocationType, "somePattern") + isMatchInvocation + @";
356     }
357 }" };
358
359                 // Test constructor with readonly property
360                 yield return new object[] { @"using System.Text.RegularExpressions;
361
362 public class Program
363 {
364     public string Pattern { get; }
365
366     public void M()
367     {
368         var isMatch = " + ConstructRegexInvocation(invocationType, "Pattern") + isMatchInvocation + @";
369     }
370 }" };
371
372                 // Test constructor with field
373                 yield return new object[] { @"using System.Text.RegularExpressions;
374
375 public class Program
376 {
377     public readonly string Pattern;
378
379     public void M()
380     {
381         var isMatch = " + ConstructRegexInvocation(invocationType, "Pattern") + isMatchInvocation + @";
382     }
383 }" };
384
385                 // Test constructor with return method
386                 yield return new object[] { @"using System.Text.RegularExpressions;
387
388 public class Program
389 {
390     public string GetMyPattern() => """";
391
392     public void M()
393     {
394         var isMatch = " + ConstructRegexInvocation(invocationType, "GetMyPattern()") + isMatchInvocation + @";
395     }
396 }" };
397             }
398         }
399
400         [Theory]
401         [MemberData(nameof(VariablePatternTestData))]
402         public async Task DiagnosticNotEmittedForVariablePattern(string test)
403             => await VerifyCS.VerifyAnalyzerAsync(test);
404
405         public static IEnumerable<object[]> ConstantOptionsTestData()
406         {
407             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
408             {
409                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
410                 // Test options as passed in literal
411                 yield return new object[] { @"using System.Text.RegularExpressions;
412
413 public class Program
414 {
415     public static void Main(string[] args)
416     {
417         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"", "RegexOptions.None") + @"|]" + isMatchInvocation + @";
418     }
419 }", @"using System.Text.RegularExpressions;
420
421 public partial class Program
422 {
423     public static void Main(string[] args)
424     {
425         var isMatch = MyRegex().IsMatch("""");
426     }
427
428     [GeneratedRegex("""", RegexOptions.None)]
429     private static partial Regex MyRegex();
430 }" };
431
432                 // Test options as local constant
433                 yield return new object[] { @"using System.Text.RegularExpressions;
434
435 public class Program
436 {
437     public static void Main(string[] args)
438     {
439         const RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
440         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"", "options") + @"|]" + isMatchInvocation + @";
441     }
442 }", @"using System.Text.RegularExpressions;
443
444 public partial class Program
445 {
446     public static void Main(string[] args)
447     {
448         const RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
449         var isMatch = MyRegex().IsMatch("""");
450     }
451
452     [GeneratedRegex("""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
453     private static partial Regex MyRegex();
454 }" };
455
456                 // Test options as constant field
457                 yield return new object[] { @"using System.Text.RegularExpressions;
458
459 public class Program
460 {
461     const RegexOptions Options = RegexOptions.None;
462
463     public static void Main(string[] args)
464     {
465         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"\"", "Options") + @"|]" + isMatchInvocation + @";
466     }
467 }", @"using System.Text.RegularExpressions;
468
469 public partial class Program
470 {
471     const RegexOptions Options = RegexOptions.None;
472
473     public static void Main(string[] args)
474     {
475         var isMatch = MyRegex().IsMatch("""");
476     }
477
478     [GeneratedRegex("""", RegexOptions.None)]
479     private static partial Regex MyRegex();
480 }" };
481             }
482         }
483
484         [Theory]
485         [MemberData(nameof(ConstantOptionsTestData))]
486         public async Task DiagnosticEmittedForConstantOptions(string test, string fixedSource)
487         {
488             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
489         }
490
491         public static IEnumerable<object[]> VariableOptionsTestData()
492         {
493             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
494             {
495                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
496                 // Test options as passed in parameter
497                 yield return new object[] { @"using System.Text;
498 using System.Text.RegularExpressions;
499
500 public class Program
501 {
502     public static void Main(RegexOptions options)
503     {
504         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "options") + isMatchInvocation + @";
505     }
506 }" };
507
508                 // Test options as passed in variable
509                 yield return new object[] { @"using System.Text;
510 using System.Text.RegularExpressions;
511
512 public class Program
513 {
514     public static void Main(string[] args)
515     {
516         RegexOptions options = RegexOptions.None;
517         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "options") + isMatchInvocation + @";
518     }
519 }" };
520
521                 // Test options as readonly property
522                 yield return new object[] { @"using System.Text.RegularExpressions;
523
524 public class Program
525 {
526     public RegexOptions Options { get; }
527
528     public void M()
529     {
530         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "Options") + isMatchInvocation + @";
531     }
532 }" };
533
534                 // Test options as readonly field
535                 yield return new object[] { @"using System.Text.RegularExpressions;
536
537 public class Program
538 {
539     public readonly RegexOptions Options;
540
541     public void M()
542     {
543         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "Options") + isMatchInvocation + @";
544     }
545 }" };
546
547                 // Test options as return method.
548                 yield return new object[] { @"using System.Text.RegularExpressions;
549
550 public class Program
551 {
552     public RegexOptions GetMyOptions() => RegexOptions.None;
553
554     public void M()
555     {
556         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "GetMyOptions()") + isMatchInvocation + @";
557     }
558 }" };
559             }
560         }
561
562         [Theory]
563         [MemberData(nameof(VariableOptionsTestData))]
564         public async Task DiagnosticNotEmittedForVariableOptions(string test)
565             => await VerifyCS.VerifyAnalyzerAsync(test);
566
567         public static IEnumerable<object[]> StaticInvocationsAndFixedSourceTestData()
568         {
569             const string testTemplateWithOptions = @"using System.Text.RegularExpressions;
570
571 public class Program
572 {
573     public static void Main(string[] args)
574     {
575         [|Regex.@@Method@@(""input"", ""a|b"", RegexOptions.None)|];
576     }
577 }";
578             const string fixedSourceWithOptions = @"using System.Text.RegularExpressions;
579
580 public partial class Program
581 {
582     public static void Main(string[] args)
583     {
584         MyRegex().@@Method@@(""input"");
585     }
586
587     [GeneratedRegex(""a|b"", RegexOptions.None)]
588     private static partial Regex MyRegex();
589 }";
590
591             const string testTemplateWithoutOptions = @"using System.Text.RegularExpressions;
592
593 public class Program
594 {
595     public static void Main(string[] args)
596     {
597         [|Regex.@@Method@@(""input"", ""a|b"")|];
598     }
599 }";
600             const string fixedSourceWithoutOptions = @"using System.Text.RegularExpressions;
601
602 public partial class Program
603 {
604     public static void Main(string[] args)
605     {
606         MyRegex().@@Method@@(""input"");
607     }
608
609     [GeneratedRegex(""a|b"")]
610     private static partial Regex MyRegex();
611 }";
612
613             foreach (bool includeRegexOptions in new[] { true, false })
614             {
615                 foreach (string methodName in new[] { "Count", "EnumerateMatches", "IsMatch", "Match", "Matches", "Split" })
616                 {
617                     if (includeRegexOptions)
618                     {
619                         yield return new object[] { testTemplateWithOptions.Replace("@@Method@@", methodName), fixedSourceWithOptions.Replace("@@Method@@", methodName) };
620                     }
621                     else
622                     {
623                         yield return new object[] { testTemplateWithoutOptions.Replace("@@Method@@", methodName), fixedSourceWithoutOptions.Replace("@@Method@@", methodName) };
624
625                     }
626                 }
627             }
628
629             // Replace has one additional parameter so we treat that case separately.
630
631             yield return new object[] { @"using System.Text.RegularExpressions;
632
633 public class Program
634 {
635     public static void Main(string[] args)
636     {
637         [|Regex.Replace(""input"", ""a[b|c]*"", ""replacement"", RegexOptions.CultureInvariant)|];
638     }
639 }
640 ", @"using System.Text.RegularExpressions;
641
642 public partial class Program
643 {
644     public static void Main(string[] args)
645     {
646         MyRegex().Replace(""input"", ""replacement"");
647     }
648
649     [GeneratedRegex(""a[b|c]*"", RegexOptions.CultureInvariant)]
650     private static partial Regex MyRegex();
651 }
652 " };
653
654             yield return new object[] { @"using System.Text.RegularExpressions;
655
656 public class Program
657 {
658     public static void Main(string[] args)
659     {
660         [|Regex.Replace(""input"", ""a[b|c]*"", ""replacement"")|];
661     }
662 }
663 ", @"using System.Text.RegularExpressions;
664
665 public partial class Program
666 {
667     public static void Main(string[] args)
668     {
669         MyRegex().Replace(""input"", ""replacement"");
670     }
671
672     [GeneratedRegex(""a[b|c]*"")]
673     private static partial Regex MyRegex();
674 }
675 " };
676         }
677
678         [Theory]
679         [MemberData(nameof(StaticInvocationsAndFixedSourceTestData))]
680         public async Task DiagnosticAndCodeFixForAllStaticMethods(string test, string fixedSource)
681          => await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
682
683         [Fact]
684         public async Task CodeFixSupportsNesting()
685         {
686             string test = @"using System.Text.RegularExpressions;
687
688 public class A
689 {
690     public partial class B
691     {
692         public class C
693         {
694             public partial class D
695             {
696                 public void Foo()
697                 {
698                     Regex regex = [|new Regex(""pattern"", RegexOptions.IgnorePatternWhitespace)|];
699                 }
700             }
701         }
702     }
703 }
704 ";
705             string fixedSource = @"using System.Text.RegularExpressions;
706
707 public partial class A
708 {
709     public partial class B
710     {
711         public partial class C
712         {
713             public partial class D
714             {
715                 public void Foo()
716                 {
717                     Regex regex = MyRegex();
718                 }
719
720                 [GeneratedRegex(""pattern"", RegexOptions.IgnorePatternWhitespace)]
721                 private static partial Regex MyRegex();
722             }
723         }
724     }
725 }
726 ";
727
728             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
729         }
730
731         [Theory]
732         [MemberData(nameof(InvocationTypes))]
733         public async Task NoDiagnosticForRegexOptionsNonBacktracking(InvocationType invocationType)
734         {
735             string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
736             string test = @"using System.Text.RegularExpressions;
737
738 public class Program
739 {
740     public static void Main(string[] args)
741     {
742         var isMatch = " + ConstructRegexInvocation(invocationType, "\"\"", "RegexOptions.IgnoreCase | RegexOptions.NonBacktracking") + isMatchInvocation + @";
743     }
744 }";
745
746             await VerifyCS.VerifyAnalyzerAsync(test);
747         }
748
749         [Fact]
750         public async Task AnayzerSupportsMultipleDiagnostics()
751         {
752             string test = @"using System.Text.RegularExpressions;
753
754 public class Program
755 {
756     public static void Main()
757     {
758         Regex regex1 = [|new Regex(""a|b"")|];
759         Regex regex2 = [|new Regex(""c|d"", RegexOptions.CultureInvariant)|];
760     }
761 }
762 ";
763             string fixedSource = @"using System.Text.RegularExpressions;
764
765 public partial class Program
766 {
767     public static void Main()
768     {
769         Regex regex1 = MyRegex();
770         Regex regex2 = MyRegex1();
771     }
772
773     [GeneratedRegex(""a|b"")]
774     private static partial Regex MyRegex();
775     [GeneratedRegex(""c|d"", RegexOptions.CultureInvariant)]
776     private static partial Regex MyRegex1();
777 }
778 ";
779             await new VerifyCS.Test
780             {
781                 TestCode = test,
782                 FixedCode = fixedSource,
783                 NumberOfFixAllIterations = 2,
784             }.RunAsync();
785         }
786
787         [Fact]
788         public async Task CodeFixerSupportsNamedParameters()
789         {
790             string test = @"using System.Text.RegularExpressions;
791
792 class Program
793 {
794     static void Main(string[] args)
795     {
796         Regex r = [|new Regex(options: RegexOptions.None, pattern: ""a|b"")|];
797     }
798 }";
799
800             string fixedSource = @"using System.Text.RegularExpressions;
801
802 partial class Program
803 {
804     static void Main(string[] args)
805     {
806         Regex r = MyRegex();
807     }
808
809     [GeneratedRegex(""a|b"", RegexOptions.None)]
810     private static partial Regex MyRegex();
811 }";
812
813             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
814         }
815
816         [Fact]
817         public async Task CodeFixerDoesNotSimplifyStyle()
818         {
819             string test = @"using System.Text.RegularExpressions;
820
821 class Program
822 {
823     static void Main()
824     {
825         int i = (4 - 4); // this shouldn't be changed by fixer
826         Regex r = [|new Regex(options: RegexOptions.None, pattern: ""a|b"")|];
827     }
828 }";
829
830             string fixedSource = @"using System.Text.RegularExpressions;
831
832 partial class Program
833 {
834     static void Main()
835     {
836         int i = (4 - 4); // this shouldn't be changed by fixer
837         Regex r = MyRegex();
838     }
839
840     [GeneratedRegex(""a|b"", RegexOptions.None)]
841     private static partial Regex MyRegex();
842 }";
843
844             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
845         }
846
847         [Fact]
848         public async Task TopLevelStatements_MultipleSourceFiles()
849         {
850             await new VerifyCS.Test
851             {
852                 TestState =
853                 {
854                     Sources = { "public class C { }", @"var r = [|new System.Text.RegularExpressions.Regex("""")|];" },
855                     OutputKind = OutputKind.ConsoleApplication,
856                 },
857                 FixedState =
858                 {
859                     Sources = { "public class C { }", @"var r = MyRegex();
860
861 partial class Program
862 {
863     [System.Text.RegularExpressions.GeneratedRegex("""")]
864     private static partial System.Text.RegularExpressions.Regex MyRegex();
865 }" }
866                 },
867             }.RunAsync();
868         }
869
870         [Fact]
871         public async Task LeadingAndTrailingTriviaIsPreservedByFixer()
872         {
873             string test = @"using System.Text.RegularExpressions;
874
875 static class Class
876 {
877     public static string CollapseWhitespace(this string text) =>
878         [|Regex.Replace(text, "" \\s+"" , ""  "")|];
879 }";
880
881             string expectedFixedCode = @"using System.Text.RegularExpressions;
882
883 static partial class Class
884 {
885     public static string CollapseWhitespace(this string text) =>
886         MyRegex().Replace(text, ""  "");
887     [GeneratedRegex("" \\s+"")]
888     private static partial Regex MyRegex();
889 }";
890
891             await VerifyCS.VerifyCodeFixAsync(test, expectedFixedCode);
892         }
893
894         [Fact]
895         public async Task VerbatimStringLiteralSyntaxPreservedByFixer()
896         {
897             string test = @"using System.Text.RegularExpressions;
898
899 static class Class
900 {
901     public static string CollapseWhitespace(this string text) =>
902         [|Regex.Replace(text, @"" \s+"" , @""  "")|];
903 }";
904
905             string expectedFixedCode = @"using System.Text.RegularExpressions;
906
907 static partial class Class
908 {
909     public static string CollapseWhitespace(this string text) =>
910         MyRegex().Replace(text, @""  "");
911     [GeneratedRegex(@"" \s+"")]
912     private static partial Regex MyRegex();
913 }";
914
915             await VerifyCS.VerifyCodeFixAsync(test, expectedFixedCode);
916         }
917
918         [Fact]
919         public async Task RawStringLiteralSyntaxPreservedByFixer()
920         {
921             string test = @"using System.Text.RegularExpressions;
922
923 static class Class
924 {
925     public static string CollapseWhitespace(this string text) =>
926         [|Regex.Replace(text, """"""
927                               \s+
928                               """""",
929                               """""""" hello """""" world """""""")|];
930 }";
931
932             string expectedFixedCode = @"using System.Text.RegularExpressions;
933
934 static partial class Class
935 {
936     public static string CollapseWhitespace(this string text) =>
937         MyRegex().Replace(text, """""""" hello """""" world """""""");
938     [GeneratedRegex(""""""
939                               \s+
940                               """""")]
941     private static partial Regex MyRegex();
942 }";
943
944             await VerifyCS.VerifyCodeFixAsync(test, expectedFixedCode);
945         }
946
947         [Fact]
948         public async Task InterpolatedStringLiteralSyntaxPreservedByFixer()
949         {
950             string test = @"using System.Text.RegularExpressions;
951
952 partial class Program
953 {
954     static void Main(string[] args)
955     {
956         const string pattern = @""a|b\s\n"";
957         const string pattern2 = $""{pattern}2"";
958
959         Regex regex = [|new Regex(pattern2)|];
960     }
961 }";
962
963             string expectedFixedCode = @"using System.Text.RegularExpressions;
964
965 partial class Program
966 {
967     static void Main(string[] args)
968     {
969         const string pattern = @""a|b\s\n"";
970         const string pattern2 = $""{pattern}2"";
971
972         Regex regex = MyRegex();
973     }
974
975     [GeneratedRegex(""a|b\\s\\n2"")]
976     private static partial Regex MyRegex();
977 }";
978
979             await VerifyCS.VerifyCodeFixAsync(test, expectedFixedCode);
980         }
981
982         [Fact]
983         public async Task TestAsArgument()
984         {
985             string test = @"using System.Text.RegularExpressions;
986 public class C
987 {
988     void M1(Regex r) => _ = r;
989     void M2() => M1([|new Regex("""")|]);
990 }
991 ";
992
993             string fixedCode = @"using System.Text.RegularExpressions;
994 public partial class C
995 {
996     void M1(Regex r) => _ = r;
997     void M2() => M1(MyRegex());
998     [GeneratedRegex("""")]
999     private static partial Regex MyRegex();
1000 }
1001 ";
1002
1003             await VerifyCS.VerifyCodeFixAsync(test, fixedCode);
1004         }
1005
1006         [Fact]
1007         public async Task InvalidRegexOptions()
1008         {
1009             string test = @"using System.Text.RegularExpressions;
1010
1011 public class A
1012 {
1013     public void Foo()
1014     {
1015         Regex regex = [|new Regex(""pattern"", (RegexOptions)0x0800)|];
1016     }
1017 }
1018 ";
1019             string fixedSource = @"using System.Text.RegularExpressions;
1020
1021 public partial class A
1022 {
1023     public void Foo()
1024     {
1025         Regex regex = MyRegex();
1026     }
1027
1028     [GeneratedRegex(""pattern"", (RegexOptions)(2048))]
1029     private static partial Regex MyRegex();
1030 }
1031 ";
1032
1033             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
1034         }
1035
1036         [Fact]
1037         public async Task InvalidRegexOptions_LocalConstant()
1038         {
1039             string test = @"using System.Text.RegularExpressions;
1040
1041 public class A
1042 {
1043     public void Foo()
1044     {
1045         const RegexOptions MyOptions = (RegexOptions)0x0800;
1046         Regex regex = [|new Regex(""pattern"", MyOptions)|];
1047     }
1048 }
1049 ";
1050             string fixedSource = @"using System.Text.RegularExpressions;
1051
1052 public partial class A
1053 {
1054     public void Foo()
1055     {
1056         const RegexOptions MyOptions = (RegexOptions)0x0800;
1057         Regex regex = MyRegex();
1058     }
1059
1060     [GeneratedRegex(""pattern"", (RegexOptions)(2048))]
1061     private static partial Regex MyRegex();
1062 }
1063 ";
1064
1065             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
1066         }
1067
1068         [Fact]
1069         public async Task InvalidRegexOptions_Negative()
1070         {
1071             string test = @"using System.Text.RegularExpressions;
1072
1073 public class A
1074 {
1075     public void Foo()
1076     {
1077         Regex regex = [|new Regex(""pattern"", (RegexOptions)(-10000))|];
1078     }
1079 }
1080 ";
1081             string fixedSource = @"using System.Text.RegularExpressions;
1082
1083 public partial class A
1084 {
1085     public void Foo()
1086     {
1087         Regex regex = MyRegex();
1088     }
1089
1090     [GeneratedRegex(""pattern"", (RegexOptions)(-10000))]
1091     private static partial Regex MyRegex();
1092 }
1093 ";
1094             await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
1095         }
1096
1097         public static IEnumerable<object[]> DetectsCurrentCultureTestData()
1098         {
1099             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
1100             {
1101                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
1102
1103                 foreach (bool useInlineIgnoreCase in new[] { true, false })
1104                 {
1105                     string pattern = useInlineIgnoreCase ? "\"(?:(?>abc)(?:(?s)d|e)(?:(?:(?xi)ki)*))\"" : "\"abc\"";
1106                     string options = useInlineIgnoreCase ? "RegexOptions.None" : "RegexOptions.IgnoreCase";
1107
1108                     // Test using current culture
1109                     yield return new object[] { @"using System.Text.RegularExpressions;
1110
1111 public class Program
1112 {
1113     public static void Main(string[] args)
1114     {
1115         var isMatch = [|" + ConstructRegexInvocation(invocationType, pattern, options) + @"|]" + isMatchInvocation + @";
1116     }
1117 }", @"using System.Text.RegularExpressions;
1118
1119 public partial class Program
1120 {
1121     public static void Main(string[] args)
1122     {
1123         var isMatch = MyRegex().IsMatch("""");
1124     }
1125
1126     [GeneratedRegex(" + $"{pattern}, {options}, \"{CultureInfo.CurrentCulture.Name}" + @""")]
1127     private static partial Regex MyRegex();
1128 }" };
1129
1130                     // Test using CultureInvariant which should default to the 2 parameter constructor
1131                     options = useInlineIgnoreCase ? "RegexOptions.CultureInvariant" : "RegexOptions.IgnoreCase | RegexOptions.CultureInvariant";
1132                     yield return new object[] { @"using System.Text.RegularExpressions;
1133
1134 public class Program
1135 {
1136     public static void Main(string[] args)
1137     {
1138         var isMatch = [|" + ConstructRegexInvocation(invocationType, pattern, options) + @"|]" + isMatchInvocation + @";
1139     }
1140 }", @"using System.Text.RegularExpressions;
1141
1142 public partial class Program
1143 {
1144     public static void Main(string[] args)
1145     {
1146         var isMatch = MyRegex().IsMatch("""");
1147     }
1148
1149     [GeneratedRegex(" + $"{pattern}, {options}" + @")]
1150     private static partial Regex MyRegex();
1151 }" };
1152                 }
1153             }
1154         }
1155
1156         public static IEnumerable<object[]> NoOptionsCultureTestData()
1157         {
1158             foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods })
1159             {
1160                 string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty;
1161
1162                 // Test no options passed in
1163                 yield return new object[] { @"using System.Text.RegularExpressions;
1164
1165 public class Program
1166 {
1167     public static void Main(string[] args)
1168     {
1169         var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"(?i)abc\"") + @"|]" + isMatchInvocation + @";
1170     }
1171 }", @"using System.Text.RegularExpressions;
1172
1173 public partial class Program
1174 {
1175     public static void Main(string[] args)
1176     {
1177         var isMatch = MyRegex().IsMatch("""");
1178     }
1179
1180     [GeneratedRegex(" + $"\"(?i)abc\", RegexOptions.None, \"{CultureInfo.CurrentCulture.Name}" + @""")]
1181     private static partial Regex MyRegex();
1182 }" };
1183             }
1184         }
1185
1186         [Theory]
1187         [MemberData(nameof(DetectsCurrentCultureTestData))]
1188         [MemberData(nameof(NoOptionsCultureTestData))]
1189         public async Task DetectsCurrentCulture(string test, string fixedSource)
1190             => await VerifyCS.VerifyCodeFixAsync(test, fixedSource);
1191
1192         #region Test helpers
1193
1194         private static string ConstructRegexInvocation(InvocationType invocationType, string pattern, string? options = null)
1195             => invocationType switch
1196             {
1197                 InvocationType.StaticMethods => (pattern is null, options is null) switch
1198                 {
1199                     (false, true) => $"Regex.IsMatch(\"\", {pattern})",
1200                     (false, false) => $"Regex.IsMatch(\"\", {pattern}, {options})",
1201                     _ => throw new InvalidOperationException()
1202                 },
1203                 InvocationType.Constructor => (pattern is null, options is null) switch
1204                 {
1205                     (false, true) => $"new Regex({pattern})",
1206                     (false, false) => $"new Regex({pattern}, {options})",
1207                     _ => throw new InvalidOperationException()
1208                 },
1209                 _ => throw new ArgumentOutOfRangeException(nameof(invocationType))
1210             };
1211
1212         public static IEnumerable<object[]> InvocationTypes
1213             => new object[][]
1214             {
1215                 new object[] { InvocationType.StaticMethods },
1216                 new object[] { InvocationType.Constructor }
1217             };
1218
1219         public enum InvocationType
1220         {
1221             StaticMethods,
1222             Constructor
1223         }
1224
1225         #endregion Test helpers
1226     }
1227 }