Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;

namespace System.Text.RegularExpressions.Generator
{
public partial class RegexGenerator
{
/// <summary>
/// Provides diagnostics for invalid usage of the <see cref="GeneratedRegexAttribute"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Analyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [
DiagnosticDescriptors.InvalidGeneratedRegexAttribute,
DiagnosticDescriptors.MultipleGeneratedRegexAttributes,
DiagnosticDescriptors.InvalidRegexArguments,
DiagnosticDescriptors.RegexMemberMustHaveValidSignature,
DiagnosticDescriptors.LimitedSourceGeneration,
];

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

context.RegisterCompilationStartAction(context =>
{
var generatedRegexAttributeSymbol = context.Compilation.GetTypeByMetadataName(GeneratedRegexAttributeName);
if (generatedRegexAttributeSymbol is null)
{
return;
}
context.RegisterSymbolAction(context =>
{
if (context.Symbol is
IMethodSymbol { PartialDefinitionPart: not null } or IPropertySymbol { PartialDefinitionPart: not null })
{
// The analyzer will also see the source-generated parts, so we must take care to emit diagnostics only for
// the partial definition that the user wrote.
return;
}
var attributes = context.Symbol.GetAttributes().Where(x => SymbolEqualityComparer.Default.Equals(x.AttributeClass, generatedRegexAttributeSymbol)).ToImmutableArray();
if (attributes.Length == 0)
{
return;
}
if (context.Symbol.DeclaringSyntaxReferences.Length == 0)
{
return;
}
var onReportDiagnostic = context.ReportDiagnostic;
var targetNode = context.Symbol.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken);
var regexPatternAndSyntax = ParseGeneratedRegexAttribute(targetNode, context.Symbol, context.Compilation, attributes, onReportDiagnostic);
if (regexPatternAndSyntax is null)
{
return;
}
_ = GetRegexMethod(regexPatternAndSyntax, targetNode, onReportDiagnostic);
}, SymbolKind.Method, SymbolKind.Property);
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedText
}

/// <summary>Emits the Regex-derived type for a method where we're unable to generate custom code.</summary>
private static void EmitRegexLimitedBoilerplate(
IndentedTextWriter writer, RegexMethod rm, string reason, LanguageVersion langVer)
private static void EmitRegexLimitedBoilerplate(IndentedTextWriter writer, RegexMethod rm)
{
Debug.Assert(!rm.SupportsCodeGeneration);
string visibility;
if (langVer >= LanguageVersion.CSharp11)
if (rm.CompilationData.LanguageVersion >= LanguageVersion.CSharp11)
{
visibility = "file";
writer.WriteLine($"/// <summary>Caches a <see cref=\"Regex\"/> instance for the {rm.MemberName} method.</summary>");
Expand All @@ -128,7 +128,7 @@ private static void EmitRegexLimitedBoilerplate(
visibility = "internal";
writer.WriteLine($"/// <summary>This class supports generated regexes and should not be used by other code directly.</summary>");
}
writer.WriteLine($"/// <remarks>A custom Regex-derived type could not be generated because {reason}.</remarks>");
writer.WriteLine($"/// <remarks>A custom Regex-derived type could not be generated because {rm.CodeGenerationUnsupportedReason}.</remarks>");
writer.WriteLine($"[{s_generatedCodeAttribute}]");
writer.WriteLine($"{visibility} sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"{{");
Expand All @@ -148,12 +148,11 @@ private static void EmitRegexLimitedBoilerplate(
private const string HasDefaultTimeoutFieldName = "s_hasTimeout";

/// <summary>Emits the Regex-derived type for a method whose RunnerFactory implementation was generated into <paramref name="runnerFactoryImplementation"/>.</summary>
private static void EmitRegexDerivedImplementation(
IndentedTextWriter writer, RegexMethod rm, string runnerFactoryImplementation, bool allowUnsafe)
private static void EmitRegexDerivedImplementation(IndentedTextWriter writer, RegexMethod rm, string runnerFactoryImplementation)
{
writer.WriteLine($"/// <summary>Custom <see cref=\"Regex\"/>-derived type for the {rm.MemberName} method.</summary>");
writer.WriteLine($"[{s_generatedCodeAttribute}]");
if (allowUnsafe)
if (rm.CompilationData.AllowUnsafe)
{
writer.WriteLine($"[SkipLocalsInit]");
}
Expand Down Expand Up @@ -229,8 +228,9 @@ static void AppendHashtableContents(IndentedTextWriter writer, IEnumerable<Dicti
}

/// <summary>Emits the code for the RunnerFactory. This is the actual logic for the regular expression.</summary>
private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, RegexMethod rm, Dictionary<string, string[]> requiredHelpers, bool checkOverflow)
private static void EmitRegexDerivedTypeRunnerFactory(IndentedTextWriter writer, RegexMethod rm, Dictionary<string, string[]> requiredHelpers)
{
bool checkOverflow = rm.CompilationData.CheckOverflow;
void EnterCheckOverflow()
{
if (checkOverflow)
Expand Down
Loading
Loading