Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Oct 12, 2025

Implementation Complete: CA2027 Analyzer for Redundant Regex.IsMatch Guard

Summary

Successfully implemented analyzer CA2027 with all code review feedback addressed.

Problem Pattern Detected

if (Regex.IsMatch(input, pattern))
{
    Match m = Regex.Match(input, pattern);
    ...
}

Latest Changes (This Commit)

Simplified Implementation:

  • Removed support for inverted pattern (if (!IsMatch) { return; }) from both analyzer and fixer to reduce complexity
  • Now only handles the common case: if (IsMatch) { Match m = ... }
  • Removed 4 tests for inverted patterns (26 tests remaining, all passing)

Code Quality Improvements:

  1. Combined null checks in fixer using pattern matching (is not { })
  2. Removed unused diagnostic local variable - using context.Diagnostics[0] directly
  3. Removed helper methods that were only used for inverted pattern support (IsEarlyReturnPattern, FindMatchCallAfterConditional)

Benefits:

  • Simpler, more maintainable code
  • Covers the most common use case
  • No complex control flow analysis needed for inverted patterns
  • Easier for users to understand the fixer transformation

Test Results:
✅ All 26 tests passing
✅ Zero regressions
✅ All code review feedback addressed

Original prompt

This section details on the original issue you should resolve

<issue_title>[Analyzer]: Regex.IsMatch guarding Regex.Match</issue_title>
<issue_description>In looking through various code bases, I've seen the following pattern repeatedly:

if (Regex.IsMatch(pattern))
{
    Match m = Regex.Match(pattern);
    ...
}

That just doubles the work involved when there is a match. It should instead be:

if (Regex.Match(pattern) is { Success: true } m)
{
    ...
}

or something similar. We should have an analyzer that flags these duplicative guards and a fixer that transforms into something like the latter.
</issue_description>

<agent_instructions>Implement an analyzer and fixer in the Microsoft.CodeAnalysis.NetAnalyzers project, following the same approach and style as is used in the rest of the project. (Note that there's a pending PR open that adds a CA2026, so don't use that same ID.)</agent_instructions>

Comments on the Issue (you are @copilot in this section)

Fixes dotnet/runtime#111239


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@Copilot Copilot AI changed the title [WIP] Add analyzer and fixer for redundant Regex.IsMatch usage Add CA2027 analyzer to detect redundant Regex.IsMatch guard before Regex.Match Oct 12, 2025
@Copilot Copilot AI requested a review from stephentoub October 12, 2025 20:18
Copilot finished work on behalf of stephentoub October 12, 2025 20:18
Copilot AI and others added 2 commits October 12, 2025 20:46
@Copilot Copilot AI requested a review from stephentoub October 12, 2025 20:54
Copilot finished work on behalf of stephentoub October 12, 2025 20:54
@stephentoub
Copy link
Member

@copilot, there are build failures:

  One or more auto-generated documentation files were either edited manually, or not updated. Please revert changes made to the following files (if manually edited) and run `msbuild /t:pack` at the root of the repo to automatically update them:
      D:\a\_work\1\s\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers.md
      D:\a\_work\1\s\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers.sarif

Copy link
Contributor Author

Copilot AI commented Oct 13, 2025

@copilot, there are build failures:

  One or more auto-generated documentation files were either edited manually, or not updated. Please revert changes made to the following files (if manually e...

Fixed by regenerating the auto-generated documentation files. The files now contain the updated CA2027 descriptions with the corrected resource strings.

Copilot finished work on behalf of stephentoub October 13, 2025 16:06
@stephentoub stephentoub marked this pull request as ready for review October 13, 2025 16:53
@stephentoub stephentoub requested a review from a team as a code owner October 13, 2025 16:53
@Copilot Copilot AI review requested due to automatic review settings October 13, 2025 16:53
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements analyzer CA2027 to detect redundant Regex.IsMatch guard patterns before Regex.Match calls. The analyzer identifies cases where developers unnecessarily check Regex.IsMatch before calling Regex.Match with the same arguments, which duplicates the matching operation and impacts performance.

Key changes include:

  • New analyzer that detects both standard guard patterns (if (IsMatch) { Match... }) and inverted early-return patterns (if (!IsMatch) { return; } Match...)
  • Language-specific code fixers for C# and Visual Basic that remove redundant guards
  • Comprehensive test coverage with 14 test cases covering various scenarios including dataflow analysis to prevent false positives

Reviewed Changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
AvoidRedundantRegexIsMatchBeforeMatch.cs Core analyzer implementation with pattern detection and dataflow analysis
CSharpAvoidRedundantRegexIsMatchBeforeMatch.Fixer.cs C# code fixer that transforms guard patterns to use Match().Success
BasicAvoidRedundantRegexIsMatchBeforeMatch.Fixer.vb Visual Basic code fixer for removing redundant guards
AvoidRedundantRegexIsMatchBeforeMatchTests.cs Comprehensive test suite covering positive/negative cases and edge scenarios
MicrosoftNetCoreAnalyzersResources.resx Resource strings for analyzer messages and descriptions
DiagnosticCategoryAndIdRanges.txt Updated rule ID range to include CA2027
AnalyzerReleases.Unshipped.md Added CA2027 to unshipped analyzer releases
Multiple .xlf files Localization files with new resource strings marked for translation
Microsoft.CodeAnalysis.NetAnalyzers.md/.sarif Auto-generated documentation files

Copilot finished work on behalf of stephentoub October 13, 2025 17:25
@Copilot Copilot AI requested a review from stephentoub October 13, 2025 17:25
Comment on lines 32 to 42
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null)
{
return;
}

var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (semanticModel is null)
{
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, you can combine these blocks, e.g.

if (await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root ||
    await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is not { } semanticModel)
{
    return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combined null checks using pattern matching as suggested.


if (isNegated && IsEarlyReturnPattern(ifStatement))
{
// For inverted early return pattern: remove the entire if statement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, this doesn't look correct. It would still need a guard.

I think my suggestion of handling the inverted case complicates things too much. Let's revert back to just handling the original case and not handling the inverted case, both in the analyzer and the fixer. That should cover the most common cases while not adding a lot of complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed inverted pattern support from both analyzer and fixer. Now only handles the simple case: if (IsMatch) { Match m = ... }. This simplifies the implementation and covers the most common use case.

return;
}

var diagnostic = context.Diagnostics[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, diagnostic isn't used until later, and it's only used once... you can just use context.Diagnostics[0] as the argument to RegisterCodeFix rather than using a local.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the diagnostic local variable - now using context.Diagnostics[0] directly in the RegisterCodeFix call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Analyzer]: Regex.IsMatch guarding Regex.Match

2 participants