Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ public sealed class UpgradeToRegexGeneratorAnalyzer : DiagnosticAnalyzer
private const string RegexTypeName = "System.Text.RegularExpressions.Regex";
private const string RegexGeneratorTypeName = "System.Text.RegularExpressions.RegexGeneratorAttribute";

internal const string PatternIndexName = "PatternIndex";
internal const string RegexOptionsIndexName = "RegexOptionsIndex";
internal const string PatternArgumentName = "pattern";
internal const string OptionsArgumentName = "options";

internal const string PatternKeyName = "Pattern";
internal const string RegexOptionsKeyName = "RegexOption";

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.UseRegexSourceGeneration);
Expand All @@ -50,14 +53,6 @@ public override void Initialize(AnalysisContext context)
return;
}

// Validate that the project is not using top-level statements, since if it were, the code-fixer
// can't easily convert to the source generator without having to make the program not use top-level
// statements any longer.
if (ProjectUsesTopLevelStatements(compilation))
{
return;
}

// Pre-compute a hash with all of the method symbols that we want to analyze for possibly emitting
// a diagnostic.
HashSet<IMethodSymbol> staticMethodsToDetect = GetMethodSymbolHash(regexTypeSymbol,
Expand Down Expand Up @@ -115,20 +110,17 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, INamedTy
// code fixer can later use that property bag to generate the code fix and emit the RegexGenerator attribute.
if (staticMethodsToDetect.Contains(method))
{
string? patternArgumentIndex = null;
string? optionsArgumentIndex = null;

// Validate that arguments pattern and options are constant and timeout was not passed in.
if (!TryValidateParametersAndExtractArgumentIndices(invocationOperation.Arguments, ref patternArgumentIndex, ref optionsArgumentIndex))
if (!TryValidateParametersAndExtractArgumentIndices(invocationOperation.Arguments, out string? patternArgument, out string? optionsArgument))
{
return;
}

// Create the property bag.
ImmutableDictionary<string, string?> properties = ImmutableDictionary.CreateRange(new[]
{
new KeyValuePair<string, string?>(PatternIndexName, patternArgumentIndex),
new KeyValuePair<string, string?>(RegexOptionsIndexName, optionsArgumentIndex)
new KeyValuePair<string, string?>(PatternKeyName, patternArgument),
new KeyValuePair<string, string?>(RegexOptionsKeyName, optionsArgument)
});

// Report the diagnostic.
Expand Down Expand Up @@ -158,19 +150,16 @@ private static void AnalyzeObjectCreation(OperationAnalysisContext context, INam
return;
}

string? patternArgumentIndex = null;
string? optionsArgumentIndex = null;

if (!TryValidateParametersAndExtractArgumentIndices(operation.Arguments, ref patternArgumentIndex, ref optionsArgumentIndex))
if (!TryValidateParametersAndExtractArgumentIndices(operation.Arguments, out string? patternArgument, out string? optionsArgument))
{
return;
}

// Create the property bag.
ImmutableDictionary<string, string?> properties = ImmutableDictionary.CreateRange(new[]
{
new KeyValuePair<string, string?>(PatternIndexName, patternArgumentIndex),
new KeyValuePair<string, string?>(RegexOptionsIndexName, optionsArgumentIndex)
new KeyValuePair<string, string?>(PatternKeyName, patternArgument),
new KeyValuePair<string, string?>(RegexOptionsKeyName, optionsArgument)
});

// Report the diagnostic.
Expand All @@ -183,12 +172,13 @@ private static void AnalyzeObjectCreation(OperationAnalysisContext context, INam
/// Validates the operation arguments ensuring they all have constant values, and if so it stores the argument
/// indices for the pattern and options. If timeout argument was used, then this returns false.
/// </summary>
private static bool TryValidateParametersAndExtractArgumentIndices(ImmutableArray<IArgumentOperation> arguments, ref string? patternArgumentIndex, ref string? optionsArgumentIndex)
private static bool TryValidateParametersAndExtractArgumentIndices(ImmutableArray<IArgumentOperation> arguments, out string? patternArgument, out string? optionsArgument)
{
const string timeoutArgumentName = "timeout";
const string matchTimeoutArgumentName = "matchTimeout";
const string patternArgumentName = "pattern";
const string optionsArgumentName = "options";

patternArgument = null;
optionsArgument = null;

if (arguments == null)
{
Expand All @@ -208,19 +198,19 @@ private static bool TryValidateParametersAndExtractArgumentIndices(ImmutableArra
}

// If the argument is the pattern, then we validate that it is constant and we store the index.
if (argumentName.Equals(patternArgumentName, StringComparison.OrdinalIgnoreCase))
if (argumentName.Equals(PatternArgumentName, StringComparison.OrdinalIgnoreCase))
{
if (!IsConstant(argument))
{
return false;
}

patternArgumentIndex = i.ToString();
patternArgument = (string)argument.Value.ConstantValue.Value;
continue;
}

// If the argument is the options, then we validate that it is constant, that it doesn't have RegexOptions.NonBacktracking, and we store the index.
if (argumentName.Equals(optionsArgumentName, StringComparison.OrdinalIgnoreCase))
if (argumentName.Equals(OptionsArgumentName, StringComparison.OrdinalIgnoreCase))
{
if (!IsConstant(argument))
{
Expand All @@ -233,7 +223,7 @@ private static bool TryValidateParametersAndExtractArgumentIndices(ImmutableArra
return false;
}

optionsArgumentIndex = i.ToString();
optionsArgument = value.ToString();
continue;
}
}
Expand All @@ -250,15 +240,6 @@ private static bool TryValidateParametersAndExtractArgumentIndices(ImmutableArra
private static bool IsConstant(IArgumentOperation argument)
=> argument.Value.ConstantValue.HasValue;

/// <summary>
/// Detects whether or not the current project is using top-level statements.
/// </summary>
private static bool ProjectUsesTopLevelStatements(Compilation compilation)
{
INamedTypeSymbol? programType = compilation.GetTypeByMetadataName("Program");
return programType is not null && !programType.GetMembers("<Main>$").IsEmpty;
}

/// <summary>
/// Ensures that the compilation can find the Regex and RegexAttribute types, and also validates that the
/// LangVersion of the project is >= 10.0 (which is the current requirement for the Regex source generator.
Expand Down
Loading