@@ -256,16 +256,16 @@ public static void Main(string[] args)
256256 private static partial Regex MyRegex();
257257}" } ;
258258
259- // Test constructor with a local constant pattern.
259+ // Test constructor with a local constant pattern (local constants are expanded) .
260260 yield return new object [ ] { @"using System.Text;
261261using System.Text.RegularExpressions;
262262
263263public class Program
264264{
265265 public static void Main(string[] args)
266266 {
267- const string pattern = @"""";
268- var isMatch = [|" + ConstructRegexInvocation ( invocationType , "\" \" " ) + @"|]" + isMatchInvocation + @";
267+ const string pattern = @""a|b "";
268+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "pattern " ) + @"|]" + isMatchInvocation + @";
269269 }
270270}" , @"using System.Text;
271271using System.Text.RegularExpressions;
@@ -274,39 +274,73 @@ public partial class Program
274274{
275275 public static void Main(string[] args)
276276 {
277- const string pattern = @"""";
277+ const string pattern = @""a|b "";
278278 var isMatch = MyRegex().IsMatch("""");
279279 }
280280
281- [GeneratedRegex("""")]
281+ [GeneratedRegex(""a|b "")]
282282 private static partial Regex MyRegex();
283283}" } ;
284284
285- // Test constructor with a constant field pattern.
285+ // Test constructor with a constant field pattern (field constants are preserved) .
286286 yield return new object [ ] { @"using System.Text;
287287using System.Text.RegularExpressions;
288288
289289public class Program
290290{
291- private const string pattern = @"""";
291+ private const string Pattern = @""a|b "";
292292
293293 public static void Main(string[] args)
294294 {
295- var isMatch = [|" + ConstructRegexInvocation ( invocationType , "\" \" " ) + @"|]" + isMatchInvocation + @";
295+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "Pattern " ) + @"|]" + isMatchInvocation + @";
296296 }
297297}" , @"using System.Text;
298298using System.Text.RegularExpressions;
299299
300300public partial class Program
301301{
302- private const string pattern = @"""";
302+ private const string Pattern = @""a|b "";
303303
304304 public static void Main(string[] args)
305305 {
306306 var isMatch = MyRegex().IsMatch("""");
307307 }
308308
309- [GeneratedRegex("""")]
309+ [GeneratedRegex(Pattern)]
310+ private static partial Regex MyRegex();
311+ }" } ;
312+
313+ // Test constructor with external constant field pattern (external field constants are preserved).
314+ yield return new object [ ] { @"using System.Text;
315+ using System.Text.RegularExpressions;
316+
317+ public class PatternConstants
318+ {
319+ public const string EmailPattern = @""^[^@]+@[^@]+\.[^@]+$"";
320+ }
321+
322+ public class Program
323+ {
324+ public static void Main(string[] args)
325+ {
326+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "PatternConstants.EmailPattern" ) + @"|]" + isMatchInvocation + @";
327+ }
328+ }" , @"using System.Text;
329+ using System.Text.RegularExpressions;
330+
331+ public class PatternConstants
332+ {
333+ public const string EmailPattern = @""^[^@]+@[^@]+\.[^@]+$"";
334+ }
335+
336+ public partial class Program
337+ {
338+ public static void Main(string[] args)
339+ {
340+ var isMatch = MyRegex().IsMatch("""");
341+ }
342+
343+ [GeneratedRegex(PatternConstants.EmailPattern)]
310344 private static partial Regex MyRegex();
311345}" } ;
312346 }
@@ -468,7 +502,39 @@ public static void Main(string[] args)
468502 var isMatch = MyRegex().IsMatch("""");
469503 }
470504
471- [GeneratedRegex("""", RegexOptions.None)]
505+ [GeneratedRegex("""", Options)]
506+ private static partial Regex MyRegex();
507+ }" } ;
508+
509+ // Test options as external constant field
510+ yield return new object [ ] { @"using System.Text.RegularExpressions;
511+
512+ public class RegexConstants
513+ {
514+ public const RegexOptions DefaultOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
515+ }
516+
517+ public class Program
518+ {
519+ public static void Main(string[] args)
520+ {
521+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "\" \" " , "RegexConstants.DefaultOptions" ) + @"|]" + isMatchInvocation + @";
522+ }
523+ }" , @"using System.Text.RegularExpressions;
524+
525+ public class RegexConstants
526+ {
527+ public const RegexOptions DefaultOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
528+ }
529+
530+ public partial class Program
531+ {
532+ public static void Main(string[] args)
533+ {
534+ var isMatch = MyRegex().IsMatch("""");
535+ }
536+
537+ [GeneratedRegex("""", RegexConstants.DefaultOptions)]
472538 private static partial Regex MyRegex();
473539}" } ;
474540 }
@@ -481,6 +547,186 @@ public async Task DiagnosticEmittedForConstantOptions(string test, string fixedS
481547 await VerifyCS . VerifyCodeFixAsync ( test , fixedSource ) ;
482548 }
483549
550+ public static IEnumerable < object [ ] > MixedConstantTestData ( )
551+ {
552+ foreach ( InvocationType invocationType in new [ ] { InvocationType . Constructor , InvocationType . StaticMethods } )
553+ {
554+ string isMatchInvocation = invocationType == InvocationType . Constructor ? @".IsMatch("""")" : string . Empty ;
555+
556+ // Test both pattern and options as field constants (both should be preserved)
557+ yield return new object [ ] { @"using System.Text.RegularExpressions;
558+
559+ public class Program
560+ {
561+ const string MyPattern = @""[a-z]+"";
562+ const RegexOptions MyOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
563+
564+ public static void Main(string[] args)
565+ {
566+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "MyPattern" , "MyOptions" ) + @"|]" + isMatchInvocation + @";
567+ }
568+ }" , @"using System.Text.RegularExpressions;
569+
570+ public partial class Program
571+ {
572+ const string MyPattern = @""[a-z]+"";
573+ const RegexOptions MyOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
574+
575+ public static void Main(string[] args)
576+ {
577+ var isMatch = MyRegex().IsMatch("""");
578+ }
579+
580+ [GeneratedRegex(MyPattern, MyOptions)]
581+ private static partial Regex MyRegex();
582+ }" } ;
583+
584+ // Test pattern as field constant and options as local constant (field preserved, local expanded)
585+ yield return new object [ ] { @"using System.Text.RegularExpressions;
586+
587+ public class Program
588+ {
589+ const string GlobalPattern = @""\d+"";
590+
591+ public static void Main(string[] args)
592+ {
593+ const RegexOptions localOptions = RegexOptions.Multiline;
594+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "GlobalPattern" , "localOptions" ) + @"|]" + isMatchInvocation + @";
595+ }
596+ }" , @"using System.Text.RegularExpressions;
597+
598+ public partial class Program
599+ {
600+ const string GlobalPattern = @""\d+"";
601+
602+ public static void Main(string[] args)
603+ {
604+ const RegexOptions localOptions = RegexOptions.Multiline;
605+ var isMatch = MyRegex().IsMatch("""");
606+ }
607+
608+ [GeneratedRegex(GlobalPattern, RegexOptions.Multiline)]
609+ private static partial Regex MyRegex();
610+ }" } ;
611+
612+ // Test pattern as local constant and options as field constant (local expanded, field preserved)
613+ yield return new object [ ] { @"using System.Text.RegularExpressions;
614+
615+ public class Program
616+ {
617+ const RegexOptions DefaultOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
618+
619+ public static void Main(string[] args)
620+ {
621+ const string localPattern = @""test.*pattern"";
622+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "localPattern" , "DefaultOptions" ) + @"|]" + isMatchInvocation + @";
623+ }
624+ }" , @"using System.Text.RegularExpressions;
625+
626+ public partial class Program
627+ {
628+ const RegexOptions DefaultOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
629+
630+ public static void Main(string[] args)
631+ {
632+ const string localPattern = @""test.*pattern"";
633+ var isMatch = MyRegex().IsMatch("""");
634+ }
635+
636+ [GeneratedRegex(""test.*pattern"", DefaultOptions)]
637+ private static partial Regex MyRegex();
638+ }" } ;
639+
640+ // Test external constants for both pattern and options
641+ yield return new object [ ] { @"using System.Text.RegularExpressions;
642+
643+ public static class RegexConfig
644+ {
645+ public const string EmailPattern = @""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"";
646+ public const RegexOptions EmailOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
647+ }
648+
649+ public class Program
650+ {
651+ public static void Main(string[] args)
652+ {
653+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "RegexConfig.EmailPattern" , "RegexConfig.EmailOptions" ) + @"|]" + isMatchInvocation + @";
654+ }
655+ }" , @"using System.Text.RegularExpressions;
656+
657+ public static class RegexConfig
658+ {
659+ public const string EmailPattern = @""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"";
660+ public const RegexOptions EmailOptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
661+ }
662+
663+ public partial class Program
664+ {
665+ public static void Main(string[] args)
666+ {
667+ var isMatch = MyRegex().IsMatch("""");
668+ }
669+
670+ [GeneratedRegex(RegexConfig.EmailPattern, RegexConfig.EmailOptions)]
671+ private static partial Regex MyRegex();
672+ }" } ;
673+ }
674+ }
675+
676+ [ Theory ]
677+ [ MemberData ( nameof ( MixedConstantTestData ) ) ]
678+ public async Task DiagnosticEmittedForMixedConstants ( string test , string fixedSource )
679+ {
680+ await VerifyCS . VerifyCodeFixAsync ( test , fixedSource ) ;
681+ }
682+
683+ public static IEnumerable < object [ ] > StaticFieldConstantTestData ( )
684+ {
685+ foreach ( InvocationType invocationType in new [ ] { InvocationType . Constructor , InvocationType . StaticMethods } )
686+ {
687+ string isMatchInvocation = invocationType == InvocationType . Constructor ? @".IsMatch("""")" : string . Empty ;
688+
689+ // Test static field constants (should be preserved)
690+ yield return new object [ ] { @"using System.Text.RegularExpressions;
691+
692+ public class Program
693+ {
694+ public static readonly string ReadOnlyPattern = @""readonly""; // This is not const, so won't be preserved
695+ public const string ConstPattern = @""const"";
696+ private static readonly RegexOptions ReadOnlyOptions = RegexOptions.IgnoreCase; // This is not const, so won't be preserved
697+ private const RegexOptions ConstOptions = RegexOptions.Multiline;
698+
699+ public static void Main(string[] args)
700+ {
701+ var isMatch = [|" + ConstructRegexInvocation ( invocationType , "ConstPattern" , "ConstOptions" ) + @"|]" + isMatchInvocation + @";
702+ }
703+ }" , @"using System.Text.RegularExpressions;
704+
705+ public partial class Program
706+ {
707+ public static readonly string ReadOnlyPattern = @""readonly""; // This is not const, so won't be preserved
708+ public const string ConstPattern = @""const"";
709+ private static readonly RegexOptions ReadOnlyOptions = RegexOptions.IgnoreCase; // This is not const, so won't be preserved
710+ private const RegexOptions ConstOptions = RegexOptions.Multiline;
711+
712+ public static void Main(string[] args)
713+ {
714+ var isMatch = MyRegex().IsMatch("""");
715+ }
716+
717+ [GeneratedRegex(ConstPattern, ConstOptions)]
718+ private static partial Regex MyRegex();
719+ }" } ;
720+ }
721+ }
722+
723+ [ Theory ]
724+ [ MemberData ( nameof ( StaticFieldConstantTestData ) ) ]
725+ public async Task DiagnosticEmittedForStaticFieldConstants ( string test , string fixedSource )
726+ {
727+ await VerifyCS . VerifyCodeFixAsync ( test , fixedSource ) ;
728+ }
729+
484730 public static IEnumerable < object [ ] > VariableOptionsTestData ( )
485731 {
486732 foreach ( InvocationType invocationType in new [ ] { InvocationType . Constructor , InvocationType . StaticMethods } )
0 commit comments