Usage
Warning
Detects and removes redundant RegexOptions.Compiled flags from [GeneratedRegex] attributes. The [GeneratedRegex] attribute uses source generators to produce compiled regex code at build time, making the Compiled flag unnecessary and redundant.
- GeneratedRegex attributes: Do not use
RegexOptions.Compiledflag with[GeneratedRegex]attributes - Already compiled: The
[GeneratedRegex]attribute generates compiled regex code at build time using source generators - No performance benefit: Adding
RegexOptions.Compiledhas no effect and provides no additional performance benefit - Code clarity: Removing the redundant flag improves code clarity by eliminating confusion about how
[GeneratedRegex]works - Applies to: All usages of the
[GeneratedRegex]attribute fromSystem.Text.RegularExpressions - Does NOT apply to: Traditional regex construction using
new Regex()whereRegexOptions.Compiledis still meaningful
Using RegexOptions.Compiled with [GeneratedRegex]:
- Is redundant because the source generator already produces compiled code
- Can confuse developers about how source-generated regexes work
- Adds unnecessary noise to the code
- May mislead readers into thinking it provides additional optimization
- Violates the principle of avoiding redundant code
The [GeneratedRegex] attribute was introduced in .NET 7 to generate highly optimized regex code at compile time using source generators. This generated code is already compiled and optimized, making the RegexOptions.Compiled flag meaningless in this context.
// non-compliant - Compiled flag is redundant
[GeneratedRegex(@"\d+", RegexOptions.Compiled)]
private static partial Regex NumberRegex();
// compliant - No Compiled flag needed
[GeneratedRegex(@"\d+")]
private static partial Regex NumberRegex();// non-compliant - Compiled flag is redundant even with other flags
[GeneratedRegex(@"[a-z]+", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
private static partial Regex CaseInsensitiveRegex();
// compliant - Keep only meaningful flags
[GeneratedRegex(@"[a-z]+", RegexOptions.IgnoreCase)]
private static partial Regex CaseInsensitiveRegex();// non-compliant - Compiled flag in named parameter
[GeneratedRegex(
pattern: @"[$>]",
options: RegexOptions.Compiled | RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: 1000)]
private static partial Regex PromptRegex();
// compliant - Remove Compiled flag
[GeneratedRegex(
pattern: @"[$>]",
options: RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: 1000)]
private static partial Regex PromptRegex();// non-compliant - Compiled at the beginning
[GeneratedRegex(@"^\d+$", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase)]
private static partial Regex MultiRegex1();
// non-compliant - Compiled in the middle
[GeneratedRegex(@"^\d+$", RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.IgnoreCase)]
private static partial Regex MultiRegex2();
// non-compliant - Compiled at the end
[GeneratedRegex(@"^\d+$", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex MultiRegex3();
// compliant - No Compiled flag
[GeneratedRegex(@"^\d+$", RegexOptions.Multiline | RegexOptions.IgnoreCase)]
private static partial Regex MultiRegex();// non-compliant - Compiled is the only option
[GeneratedRegex(@"\w+", RegexOptions.Compiled)]
private static partial Regex WordRegex();
// compliant - Remove entire options parameter
[GeneratedRegex(@"\w+")]
private static partial Regex WordRegex();// compliant - This is NOT a GeneratedRegex attribute, so Compiled flag is meaningful
private static readonly Regex CompiledRegex = new Regex(@"\d+", RegexOptions.Compiled);
// compliant - Traditional Regex construction with multiple options
private static readonly Regex MultiOptionsRegex =
new Regex(@"[a-z]+", RegexOptions.Compiled | RegexOptions.IgnoreCase);// non-compliant - User's original example
[GeneratedRegex(
pattern: "[$>]",
options: RegexOptions.Compiled | RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: RegexTimeoutPrompt)]
private static partial Regex UserOrFtpPromptRegex();
// compliant - Corrected version
[GeneratedRegex(
pattern: "[$>]",
options: RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: RegexTimeoutPrompt)]
private static partial Regex UserOrFtpPromptRegex();This rule is not configurable. It always flags RegexOptions.Compiled when used with [GeneratedRegex] attributes.
This rule analyzes methods with the [GeneratedRegex] attribute, which is a source generator attribute. The analyzer needs to examine these methods because:
- User-written attributes: The
[GeneratedRegex]attribute and its parameters are written by developers in source code - Build-time generation: The source generator creates the implementation at build time, but the attribute declaration remains in user code
- Detectable before generation: The redundant
RegexOptions.Compiledflag can be detected in the attribute before code generation occurs
Important: While the [GeneratedRegex] attribute triggers source generation, the attribute itself and its parameters are part of the user's source code and should be analyzed for correctness.
Example of analyzed code:
using System.Text.RegularExpressions;
public partial class MyClass
{
// ATC301 analyzes this attribute declaration (user-written code)
// even though the method implementation will be source-generated
[GeneratedRegex(@"\d+", RegexOptions.Compiled)] // ⚠️ ATC301 warning
private static partial Regex NumberRegex();
}A code fix is available that automatically removes the redundant RegexOptions.Compiled flag from [GeneratedRegex] attributes.
How to apply:
- Position cursor on the
[GeneratedRegex]attribute that containsRegexOptions.Compiled - Click the lightbulb (💡) or press
Ctrl+.(Windows/Linux) orCmd+.(Mac) - Select "Remove redundant RegexOptions.Compiled flag"
The code fix will:
- Remove
RegexOptions.Compiledfrom the options parameter - If
Compiledis combined with other flags using|, remove only theCompiledflag and one adjacent|operator - If
Compiledis the only flag, remove the entire options parameter - Preserve all other flags and formatting
- Maintain proper syntax and structure of the attribute
Example 1 - Remove only flag:
// Before code fix
[GeneratedRegex(@"\d+", RegexOptions.Compiled)]
private static partial Regex NumberRegex();
// After code fix (automatic)
[GeneratedRegex(@"\d+")]
private static partial Regex NumberRegex();Example 2 - Remove flag from combination (first position):
// Before code fix
[GeneratedRegex(@"\d+", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
private static partial Regex NumberRegex();
// After code fix (automatic)
[GeneratedRegex(@"\d+", RegexOptions.IgnoreCase)]
private static partial Regex NumberRegex();Example 3 - Remove flag from combination (last position):
// Before code fix
[GeneratedRegex(@"\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex NumberRegex();
// After code fix (automatic)
[GeneratedRegex(@"\d+", RegexOptions.IgnoreCase)]
private static partial Regex NumberRegex();Example 4 - Remove flag from multiple flags (middle position):
// Before code fix
[GeneratedRegex(@"\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline)]
private static partial Regex NumberRegex();
// After code fix (automatic)
[GeneratedRegex(@"\d+", RegexOptions.IgnoreCase | RegexOptions.Multiline)]
private static partial Regex NumberRegex();Example 5 - Multi-line attribute with named parameters:
// Before code fix
[GeneratedRegex(
pattern: @"[$>]",
options: RegexOptions.Compiled | RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: 1000)]
private static partial Regex PromptRegex();
// After code fix (automatic)
[GeneratedRegex(
pattern: @"[$>]",
options: RegexOptions.ExplicitCapture,
matchTimeoutMilliseconds: 1000)]
private static partial Regex PromptRegex();This is the first Usage category rule in the Atc.Analyzer project. As more Usage rules are added, they will be linked here.