Skip to content

Latest commit

 

History

History
255 lines (187 loc) · 8.88 KB

File metadata and controls

255 lines (187 loc) · 8.88 KB

🔗 ATC301: Remove redundant RegexOptions.Compiled flag from [GeneratedRegex] attribute

📂 Category

Usage

⚠️ Severity

Warning

📖 Description

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.

📋 Rules

  1. GeneratedRegex attributes: Do not use RegexOptions.Compiled flag with [GeneratedRegex] attributes
  2. Already compiled: The [GeneratedRegex] attribute generates compiled regex code at build time using source generators
  3. No performance benefit: Adding RegexOptions.Compiled has no effect and provides no additional performance benefit
  4. Code clarity: Removing the redundant flag improves code clarity by eliminating confusion about how [GeneratedRegex] works
  5. Applies to: All usages of the [GeneratedRegex] attribute from System.Text.RegularExpressions
  6. Does NOT apply to: Traditional regex construction using new Regex() where RegexOptions.Compiled is still meaningful

💡 Motivation

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.

📝 Examples

Basic usage with single flag

// 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();

Usage with multiple flags

// 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();

Named parameters

// 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();

Multiple flags with Compiled in different positions

// 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();

Only Compiled flag

// 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();

Traditional Regex construction (not affected by this rule)

// 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);

Real-world example from user's code

// 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();

⚙️ Configuration

This rule is not configurable. It always flags RegexOptions.Compiled when used with [GeneratedRegex] attributes.

🤖 Source-Generated Code

This rule analyzes methods with the [GeneratedRegex] attribute, which is a source generator attribute. The analyzer needs to examine these methods because:

  1. User-written attributes: The [GeneratedRegex] attribute and its parameters are written by developers in source code
  2. Build-time generation: The source generator creates the implementation at build time, but the attribute declaration remains in user code
  3. Detectable before generation: The redundant RegexOptions.Compiled flag 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();
}

🔧 Code Fix

A code fix is available that automatically removes the redundant RegexOptions.Compiled flag from [GeneratedRegex] attributes.

How to apply:

  1. Position cursor on the [GeneratedRegex] attribute that contains RegexOptions.Compiled
  2. Click the lightbulb (💡) or press Ctrl+. (Windows/Linux) or Cmd+. (Mac)
  3. Select "Remove redundant RegexOptions.Compiled flag"

The code fix will:

  • Remove RegexOptions.Compiled from the options parameter
  • If Compiled is combined with other flags using |, remove only the Compiled flag and one adjacent | operator
  • If Compiled is 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();

🔗 Related Rules

This is the first Usage category rule in the Atc.Analyzer project. As more Usage rules are added, they will be linked here.