diff --git a/src/libraries/System.Text.RegularExpressions/gen/DiagnosticDescriptors.cs b/src/libraries/System.Text.RegularExpressions/gen/DiagnosticDescriptors.cs index fa5330ea96953e..3860261b5b2a6f 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/DiagnosticDescriptors.cs @@ -37,10 +37,10 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, customTags: WellKnownDiagnosticTags.NotConfigurable); - public static DiagnosticDescriptor RegexMethodMustHaveValidSignature { get; } = DiagnosticDescriptorHelper.Create( + public static DiagnosticDescriptor RegexMemberMustHaveValidSignature { get; } = DiagnosticDescriptorHelper.Create( id: "SYSLIB1043", title: new LocalizableResourceString(nameof(SR.InvalidGeneratedRegexAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.RegularExpressions.Generator.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.RegexMethodMustHaveValidSignatureMessage), SR.ResourceManager, typeof(FxResources.System.Text.RegularExpressions.Generator.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.RegexMemberMustHaveValidSignatureMessage), SR.ResourceManager, typeof(FxResources.System.Text.RegularExpressions.Generator.SR)), category: Category, DiagnosticSeverity.Error, isEnabledByDefault: true, diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs index b9a4b80a70bacb..33755cfd62c8af 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs @@ -71,7 +71,12 @@ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedText writer.WriteLine($"/// "); writer.WriteLine($"/// "); writer.WriteLine($"[global::System.CodeDom.Compiler.{s_generatedCodeAttribute}]"); - writer.WriteLine($"{regexMethod.Modifiers} global::System.Text.RegularExpressions.Regex {regexMethod.MethodName}() => global::{GeneratedNamespace}.{regexMethod.GeneratedName}.Instance;"); + writer.Write($"{regexMethod.Modifiers} global::System.Text.RegularExpressions.Regex{(regexMethod.NullableRegex ? "?" : "")} {regexMethod.MemberName}"); + if (!regexMethod.IsProperty) + { + writer.Write("()"); + } + writer.WriteLine($" => global::{GeneratedNamespace}.{regexMethod.GeneratedName}.Instance;"); // Unwind all scopes while (writer.Indent != 0) @@ -89,7 +94,7 @@ private static void EmitRegexLimitedBoilerplate( if (langVer >= LanguageVersion.CSharp11) { visibility = "file"; - writer.WriteLine($"/// Caches a instance for the {rm.MethodName} method."); + writer.WriteLine($"/// Caches a instance for the {rm.MemberName} method."); } else { @@ -119,7 +124,7 @@ private static void EmitRegexLimitedBoilerplate( private static void EmitRegexDerivedImplementation( IndentedTextWriter writer, RegexMethod rm, string runnerFactoryImplementation, bool allowUnsafe) { - writer.WriteLine($"/// Custom -derived type for the {rm.MethodName} method."); + writer.WriteLine($"/// Custom -derived type for the {rm.MemberName} method."); writer.WriteLine($"[{s_generatedCodeAttribute}]"); if (allowUnsafe) { diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs index 8f8c6b38ad0323..947d0bc0b049b5 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading; @@ -25,7 +26,16 @@ public partial class RegexGenerator private static object? GetRegexMethodDataOrFailureDiagnostic( GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { - var methodSyntax = (MethodDeclarationSyntax)context.TargetNode; + if (context.TargetNode is IndexerDeclarationSyntax or AccessorDeclarationSyntax) + { + // We allow these to be used as a target node for the sole purpose + // of being able to flag invalid use when [GeneratedRegex] is applied incorrectly. + // Otherwise, if the ForAttributeWithMetadataName call excluded these, [GeneratedRegex] + // could be applied to them and we wouldn't be able to issue a diagnostic. + return new DiagnosticData(DiagnosticDescriptors.RegexMemberMustHaveValidSignature, GetComparableLocation(context.TargetNode)); + } + + var memberSyntax = (MemberDeclarationSyntax)context.TargetNode; SemanticModel sm = context.SemanticModel; Compilation compilation = sm.Compilation; @@ -37,14 +47,14 @@ public partial class RegexGenerator return null; } - TypeDeclarationSyntax? typeDec = methodSyntax.Parent as TypeDeclarationSyntax; + TypeDeclarationSyntax? typeDec = memberSyntax.Parent as TypeDeclarationSyntax; if (typeDec is null) { return null; } - IMethodSymbol? regexMethodSymbol = context.TargetSymbol as IMethodSymbol; - if (regexMethodSymbol is null) + ISymbol? regexMemberSymbol = context.TargetSymbol is IMethodSymbol or IPropertySymbol ? context.TargetSymbol : null; + if (regexMemberSymbol is null) { return null; } @@ -52,19 +62,19 @@ public partial class RegexGenerator ImmutableArray boundAttributes = context.Attributes; if (boundAttributes.Length != 1) { - return new DiagnosticData(DiagnosticDescriptors.MultipleGeneratedRegexAttributes, GetComparableLocation(methodSyntax)); + return new DiagnosticData(DiagnosticDescriptors.MultipleGeneratedRegexAttributes, GetComparableLocation(memberSyntax)); } AttributeData generatedRegexAttr = boundAttributes[0]; if (generatedRegexAttr.ConstructorArguments.Any(ca => ca.Kind == TypedConstantKind.Error)) { - return new DiagnosticData(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, GetComparableLocation(methodSyntax)); + return new DiagnosticData(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, GetComparableLocation(memberSyntax)); } ImmutableArray items = generatedRegexAttr.ConstructorArguments; if (items.Length is 0 or > 4) { - return new DiagnosticData(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, GetComparableLocation(methodSyntax)); + return new DiagnosticData(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, GetComparableLocation(memberSyntax)); } string? pattern = items[0].Value as string; @@ -96,16 +106,36 @@ public partial class RegexGenerator if (pattern is null || cultureName is null) { - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), "(null)"); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), "(null)"); } - if (!regexMethodSymbol.IsPartialDefinition || - regexMethodSymbol.IsAbstract || - regexMethodSymbol.Parameters.Length != 0 || - regexMethodSymbol.Arity != 0 || - !SymbolEqualityComparer.Default.Equals(regexMethodSymbol.ReturnType, regexSymbol)) + bool nullableRegex; + if (regexMemberSymbol is IMethodSymbol regexMethodSymbol) { - return new DiagnosticData(DiagnosticDescriptors.RegexMethodMustHaveValidSignature, GetComparableLocation(methodSyntax)); + if (!regexMethodSymbol.IsPartialDefinition || + regexMethodSymbol.IsAbstract || + regexMethodSymbol.Parameters.Length != 0 || + regexMethodSymbol.Arity != 0 || + !SymbolEqualityComparer.Default.Equals(regexMethodSymbol.ReturnType, regexSymbol)) + { + return new DiagnosticData(DiagnosticDescriptors.RegexMemberMustHaveValidSignature, GetComparableLocation(memberSyntax)); + } + + nullableRegex = regexMethodSymbol.ReturnNullableAnnotation == NullableAnnotation.Annotated; + } + else + { + Debug.Assert(regexMemberSymbol is IPropertySymbol); + IPropertySymbol regexPropertySymbol = (IPropertySymbol)regexMemberSymbol; + if (!memberSyntax.Modifiers.Any(SyntaxKind.PartialKeyword) || // TODO: Switch to using regexPropertySymbol.IsPartialDefinition when available + regexPropertySymbol.IsAbstract || + regexPropertySymbol.SetMethod is not null || + !SymbolEqualityComparer.Default.Equals(regexPropertySymbol.Type, regexSymbol)) + { + return new DiagnosticData(DiagnosticDescriptors.RegexMemberMustHaveValidSignature, GetComparableLocation(memberSyntax)); + } + + nullableRegex = regexPropertySymbol.NullableAnnotation == NullableAnnotation.Annotated; } RegexOptions regexOptions = options is not null ? (RegexOptions)options : RegexOptions.None; @@ -124,7 +154,7 @@ public partial class RegexGenerator } catch (Exception e) { - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), e.Message); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), e.Message); } if ((regexOptionsWithPatternOptions & RegexOptions.IgnoreCase) != 0 && !string.IsNullOrEmpty(cultureName)) @@ -132,7 +162,7 @@ public partial class RegexGenerator if ((regexOptions & RegexOptions.CultureInvariant) != 0) { // User passed in both a culture name and set RegexOptions.CultureInvariant which causes an explicit conflict. - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), "cultureName"); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), "cultureName"); } try @@ -141,7 +171,7 @@ public partial class RegexGenerator } catch (CultureNotFoundException) { - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), "cultureName"); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), "cultureName"); } } @@ -159,17 +189,17 @@ public partial class RegexGenerator RegexOptions.Singleline; if ((regexOptions & ~SupportedOptions) != 0) { - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), "options"); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), "options"); } // Validate the timeout if (matchTimeout is 0 or < -1) { - return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(methodSyntax), "matchTimeout"); + return new DiagnosticData(DiagnosticDescriptors.InvalidRegexArguments, GetComparableLocation(memberSyntax), "matchTimeout"); } // Determine the namespace the class is declared in, if any - string? ns = regexMethodSymbol.ContainingType?.ContainingNamespace?.ToDisplayString( + string? ns = regexMemberSymbol.ContainingType?.ContainingNamespace?.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)); var regexType = new RegexType( @@ -183,9 +213,11 @@ public partial class RegexGenerator var result = new RegexPatternAndSyntax( regexType, - GetComparableLocation(methodSyntax), - regexMethodSymbol.Name, - methodSyntax.Modifiers.ToString(), + IsProperty: regexMemberSymbol is IPropertySymbol, + GetComparableLocation(memberSyntax), + regexMemberSymbol.Name, + memberSyntax.Modifiers.ToString(), + nullableRegex, pattern, regexOptions, matchTimeout, @@ -217,18 +249,18 @@ SyntaxKind.RecordStructDeclaration or // Get a Location object that doesn't store a reference to the compilation. // That allows it to compare equally across compilations. - static Location GetComparableLocation(MethodDeclarationSyntax method) + static Location GetComparableLocation(SyntaxNode syntax) { - var location = method.GetLocation(); + var location = syntax.GetLocation(); return Location.Create(location.SourceTree?.FilePath ?? string.Empty, location.SourceSpan, location.GetLineSpan().Span); } } /// Data about a regex directly from the GeneratedRegex attribute. - internal sealed record RegexPatternAndSyntax(RegexType DeclaringType, Location DiagnosticLocation, string MethodName, string Modifiers, string Pattern, RegexOptions Options, int? MatchTimeout, CultureInfo Culture, CompilationData CompilationData); + internal sealed record RegexPatternAndSyntax(RegexType DeclaringType, bool IsProperty, Location DiagnosticLocation, string MemberName, string Modifiers, bool NullableRegex, string Pattern, RegexOptions Options, int? MatchTimeout, CultureInfo Culture, CompilationData CompilationData); /// Data about a regex, including a fully parsed RegexTree and subsequent analysis. - internal sealed record RegexMethod(RegexType DeclaringType, Location DiagnosticLocation, string MethodName, string Modifiers, string Pattern, RegexOptions Options, int? MatchTimeout, RegexTree Tree, AnalysisResults Analysis, CompilationData CompilationData) + internal sealed record RegexMethod(RegexType DeclaringType, bool IsProperty, Location DiagnosticLocation, string MemberName, string Modifiers, bool NullableRegex, string Pattern, RegexOptions Options, int? MatchTimeout, RegexTree Tree, AnalysisResults Analysis, CompilationData CompilationData) { public string? GeneratedName { get; set; } public bool IsDuplicate { get; set; } diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs index 5ed5eebc073313..09062dc78c2490 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs @@ -57,7 +57,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // if there are no changes. .ForAttributeWithMetadataName( GeneratedRegexAttributeName, - (node, _) => node is MethodDeclarationSyntax, + (node, _) => node is MethodDeclarationSyntax or PropertyDeclarationSyntax or IndexerDeclarationSyntax or AccessorDeclarationSyntax, GetRegexMethodDataOrFailureDiagnostic) // Filter out any parsing errors that resulted in null objects being returned. @@ -73,7 +73,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { RegexTree regexTree = RegexParser.Parse(method.Pattern, method.Options | RegexOptions.Compiled, method.Culture); // make sure Compiled is included to get all optimizations applied to it AnalysisResults analysis = RegexTreeAnalyzer.Analyze(regexTree); - return new RegexMethod(method.DeclaringType, method.DiagnosticLocation, method.MethodName, method.Modifiers, method.Pattern, method.Options, method.MatchTimeout, regexTree, analysis, method.CompilationData); + return new RegexMethod(method.DeclaringType, method.IsProperty, method.DiagnosticLocation, method.MemberName, method.Modifiers, method.NullableRegex, method.Pattern, method.Options, method.MatchTimeout, regexTree, analysis, method.CompilationData); } catch (Exception e) { @@ -201,7 +201,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) else { regexMethod.IsDuplicate = false; - regexMethod.GeneratedName = $"{regexMethod.MethodName}_{id++}"; + regexMethod.GeneratedName = $"{regexMethod.MemberName}_{id++}"; emittedExpressions.Add(key, regexMethod); } diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/Strings.resx b/src/libraries/System.Text.RegularExpressions/gen/Resources/Strings.resx index 0b2904b0bfac07..730a2f05415fc8 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/Strings.resx @@ -131,8 +131,8 @@ The specified regex is invalid. '{0}' - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. Regex generator limitation reached. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.cs.xlf index d39c7ec6b56a64..0b181eed2a754a 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.cs.xlf @@ -192,9 +192,9 @@ Vypršel časový limit modulu Regex při pokusu o porovnání vzoru se vstupním řetězcem. K tomu může dojít z celé řady důvodů, mezi které patří velká velikost vstupních dat nebo nadměrné zpětné navracení způsobené vloženými kvantifikátory, zpětnými odkazy a dalšími faktory. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - Metoda GeneratedRegexAttribute musí být částečná, bez parametrů, neobecná, neabstraktní a návratová metoda Regex. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + Metoda GeneratedRegexAttribute musí být částečná, bez parametrů, neobecná, neabstraktní a návratová metoda Regex. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.de.xlf index 8e267826a243ab..3e257c863e4c58 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.de.xlf @@ -192,9 +192,9 @@ Zeitüberschreitung des RegEx-Moduls beim Versuch, ein Muster mit einer Eingabezeichenfolge in Übereinstimmung zu bringen. Dies kann viele Ursachen haben, darunter sehr große Eingaben oder übermäßige Rückverfolgung aufgrund von geschachtelten Quantifizierern, Rückverweisen und anderen Faktoren. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - Die GeneratedRegexAttribute-Methode muss partiell, parameterlos, nicht generisch, nicht abstrakt sein und RegEx zurückgeben. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + Die GeneratedRegexAttribute-Methode muss partiell, parameterlos, nicht generisch, nicht abstrakt sein und RegEx zurückgeben. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.es.xlf index e423171d0c07f7..69d6170dece96e 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.es.xlf @@ -192,9 +192,9 @@ Se agotó el tiempo de espera mientras el motor de Regex intentaba comparar una cadena de entrada con un patrón. Esto puede deberse a muchos motivos, como la especificación de cadenas de entrada muy grandes o búsquedas hacia atrás excesivas causadas por cuantificadores anidados, referencias inversas y otros factores. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - El método GeneratedRegexAttribute debe ser parcial, sin parámetros, no genérico, no abstracto y devolver Regex + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + El método GeneratedRegexAttribute debe ser parcial, sin parámetros, no genérico, no abstracto y devolver Regex diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.fr.xlf index dbc77bfb476858..6049ad85a67b64 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.fr.xlf @@ -192,9 +192,9 @@ Le délai d’attente du moteur Regex a expiré pendant la tentative de mise en correspondance d’un modèle avec une chaîne d’entrée. Ce problème peut se produire pour de nombreuses raisons, notamment en cas d’entrées volumineuses ou de retour sur trace excessif causé par les quantificateurs imbriqués, les références arrière et d’autres facteurs. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - La méthode GeneratedRegexAttribute doit être partielle, sans paramètre, non générique, non abstraite et renvoyer Regex. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + La méthode GeneratedRegexAttribute doit être partielle, sans paramètre, non générique, non abstraite et renvoyer Regex. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.it.xlf index 58b1524da3d7bd..f03a43ef84f24d 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.it.xlf @@ -192,9 +192,9 @@ Timeout del motore Regex durante il tentativo di trovare una corrispondenza tra un criterio e una stringa di input. Il timeout può verificarsi per diversi motivi, tra cui input molto grandi o un eccessivo backtracking causato da quantificatori annidati, backreference e altri fattori. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - Il metodo GeneratedRegexAttribute deve essere parziale, senza parametri, non generico, non astratto e restituire Regex. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + Il metodo GeneratedRegexAttribute deve essere parziale, senza parametri, non generico, non astratto e restituire Regex. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ja.xlf index ac87dd3a813637..c0b948e2989b66 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ja.xlf @@ -192,9 +192,9 @@ パターンと入力文字列との照合中に、Regex エンジンがタイムアウトしました。これは、非常に大きな入力、入れ子になった量指定子によって生じた過剰なバックトラッキング、前方参照などの要因を含む、さまざまな原因によって発生する可能性があります。 - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - GeneratedRegexAttribute メソッドは、部分、パラメーターなし、非ジェネリック、非抽象、および正規表現を返す必要があります。 + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + GeneratedRegexAttribute メソッドは、部分、パラメーターなし、非ジェネリック、非抽象、および正規表現を返す必要があります。 diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ko.xlf index b2c87b5648d590..8dece6af2fdcfc 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ko.xlf @@ -192,9 +192,9 @@ Regex 엔진이 패턴을 입력 문자열에 일치시키는 동안 시간 초과되었습니다. 이 오류는 많은 입력, 중첩 수량자로 인한 과도한 역추적, 역참조, 기타 요인 등의 다양한 이유로 인해 발생할 수 있습니다. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - GeneratedRegexAttribute 메서드는 부분적이고 매개 변수가 없으며 제네릭이 아닌 비추상 메서드여야 하며 regex를 반환해야 합니다. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + GeneratedRegexAttribute 메서드는 부분적이고 매개 변수가 없으며 제네릭이 아닌 비추상 메서드여야 하며 regex를 반환해야 합니다. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pl.xlf index bd8c1ffc42832c..ff70df54c702fc 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pl.xlf @@ -192,9 +192,9 @@ Upłynął limit czasu podczas próby dopasowania przez aparat wyrażeń regularnych wzorca do ciągu wejściowego. Mogło to być spowodowane wieloma przyczynami, w tym bardzo dużą ilością danych wejściowych, nadmiernym wykorzystaniem algorytmu wycofywania związanym z kwantyfikatorami zagnieżdżonymi, odwołaniami wstecznymi i innymi czynnikami. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - Metoda GeneratedRegexAttribute musi być częściowa, bez parametrów, niestanowa, nieabstrakcyjna i zwracać wyrażenia regularne. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + Metoda GeneratedRegexAttribute musi być częściowa, bez parametrów, niestanowa, nieabstrakcyjna i zwracać wyrażenia regularne. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pt-BR.xlf index 4bfcf7e55eae3c..9e080e4a390f4b 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.pt-BR.xlf @@ -192,9 +192,9 @@ O mecanismo Regex atingiu o tempo limite ao tentar combinar um padrão com uma cadeia de caracteres de entrada. Isso pode ocorrer por vários motivos, incluindo entradas muito grandes ou retrocessos excessivos causados por quantificadores aninhados, referências anteriores e outros fatores. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - O método GeneratedRegexAttribute deve ser parcial, sem parâmetros, não genérico, não abstrato e retornar Regex. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + O método GeneratedRegexAttribute deve ser parcial, sem parâmetros, não genérico, não abstrato e retornar Regex. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ru.xlf index 4f8b97ac870f25..cb663cbd61a5ac 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.ru.xlf @@ -192,9 +192,9 @@ Истекло время ожидания модуля регулярного выражения при попытке сравнить шаблон со входной строкой. Это могло произойти по многим причинам, в том числе из-за очень большого объема входных данных или излишнего обратного отслеживания, вызванного вложенными квантификаторами, обратными ссылками и прочими факторами. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - Метод GeneratedRegexAttribute должен быть частичным, без параметров, неуниверсальным, неабстрактным и возвращать регулярное выражение. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + Метод GeneratedRegexAttribute должен быть частичным, без параметров, неуниверсальным, неабстрактным и возвращать регулярное выражение. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.tr.xlf index 0f90dc4e1b9e98..ffad46d7c3a7d8 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.tr.xlf @@ -192,9 +192,9 @@ Normal ifade altyapısı bir deseni bir giriş dizesiyle eşleştirmeye çalışırken zaman aşımına uğradı. Bu durum, çok büyük girişler veya iç içe niceleyiciler, geri başvurular ve diğer faktörler nedeniyle oluşan aşırı geri izleme gibi birçok nedenle oluşabilir. - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - GeneratedRegexAttribute yöntemi kısmi ve parametresiz olmalı, genel amaçlı veya soyut olmamalı ve Normal İfade döndürmelidir. + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + GeneratedRegexAttribute yöntemi kısmi ve parametresiz olmalı, genel amaçlı veya soyut olmamalı ve Normal İfade döndürmelidir. diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hans.xlf index ba9d314ccca2a5..6d43631c5b7160 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -192,9 +192,9 @@ 尝试将模式与输入字符串匹配时,Regex 引擎超时。许多原因均可能导致出现这种情况,包括由嵌套限定符、反向引用和其他因素引起的大量输入或过度回溯。 - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - GeneratedRegexAttribute 方法必须是分部、无参数、非泛型、非抽象且返回 Regex。 + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + GeneratedRegexAttribute 方法必须是分部、无参数、非泛型、非抽象且返回 Regex。 diff --git a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hant.xlf index 070b36b4125bea..7e23491c00eede 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.RegularExpressions/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -192,9 +192,9 @@ 嘗試將模式對應至輸入字串時,Regex 引擎發生逾時。有很多原因會導致這個情形,其中包括巢狀數量詞、反向參考及其他因素所造成的極大量輸入或過度使用回溯法。 - - GeneratedRegexAttribute method must be partial, parameterless, non-generic, non-abstract, and return Regex. - GeneratedRegexAttribute 方法必須是部分、無參數、非泛型、非抽象,並且傳回 Regex。 + + GeneratedRegexAttribute method or property must be partial, parameterless, non-generic, non-abstract, and return Regex. If a property, it must also be get-only. + GeneratedRegexAttribute 方法必須是部分、無參數、非泛型、非抽象,並且傳回 Regex。 diff --git a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs index 949f4939062bf6..0730da675126c9 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -256,7 +256,7 @@ public RegexCompilationInfo(string pattern, System.Text.RegularExpressions.Regex public System.Text.RegularExpressions.RegexOptions Options { get { throw null; } set { } } public string Pattern { get { throw null; } set { } } } - [System.AttributeUsageAttribute(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + [System.AttributeUsageAttribute(System.AttributeTargets.Method | System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed partial class GeneratedRegexAttribute : System.Attribute { public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex)] string pattern) { } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs index 9e44d0f92258d2..9e41bb898336d2 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs @@ -9,8 +9,8 @@ namespace System.Text.RegularExpressions; /// Instructs the System.Text.RegularExpressions source generator to generate an implementation of the specified regular expression. /// /// -/// The generator associated with this attribute only supports C#. It only supplies an implementation when applied to static, partial, parameterless, non-generic methods that -/// are typed to return . +/// The generator associated with this attribute only supports C#. It only supplies an implementation when applied to partial, parameterless, non-generic methods +/// or get-only properties that are typed to return . /// /// /// When the supports case-insensitive matches (either by passing or using the inline `(?i)` switch in the pattern) the regex @@ -21,7 +21,7 @@ namespace System.Text.RegularExpressions; /// they will always use casing table for the current runtime. /// /// -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed class GeneratedRegexAttribute : Attribute { /// Initializes a new instance of the with the specified pattern. diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index 489c4c164a642b..fe0e793819f646 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -177,7 +177,9 @@ internal static async Task SourceGenRegexAsync( { code.Append($", {SymbolDisplay.FormatLiteral(regex.culture.Name, quote: true)}"); } - code.AppendLine($")] public static partial Regex Get{count}();"); + + bool useProp = count % 2 == 0; // validate both methods and properties by alternating between them + code.AppendLine($")] public static partial Regex Get{count}{(useProp ? " { get; }" : "();")}"); count++; } @@ -238,12 +240,13 @@ internal static async Task SourceGenRegexAsync( var alc = new RegexLoadContext(Environment.CurrentDirectory); Assembly a = alc.LoadFromStream(dll); - // Instantiate each regex using the newly created static Get method that was source generated. + // Instantiate each regex using the newly created static Get member that was source generated. var instances = new Regex[count]; Type c = a.GetType("C")!; for (int i = 0; i < instances.Length; i++) { - instances[i] = (Regex)c.GetMethod($"Get{i}")!.Invoke(null, null)!; + string memberName = $"Get{i}"; + instances[i] = (Regex)(c.GetMethod(memberName) ?? c.GetProperty(memberName).GetGetMethod())!.Invoke(null, null)!; } // Issue an unload on the ALC, so it'll be collected once the Regex instance is collected diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs index 28fd8bfbc36147..b4d46d8274f57a 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs @@ -19,7 +19,7 @@ namespace System.Text.RegularExpressions.Tests public class RegexGeneratorParserTests { [Fact] - public async Task Diagnostic_MultipleAttributes() + public async Task Diagnostic_Method_MultipleAttributes() { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; @@ -34,6 +34,22 @@ partial class C Assert.Equal("SYSLIB1041", Assert.Single(diagnostics).Id); } + [Fact] + public async Task Diagnostic_Property_MultipleAttributes() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + [GeneratedRegex(""abc"")] + private static partial Regex MultipleAttributes { get; }; + } + "); + + Assert.Equal("SYSLIB1041", Assert.Single(diagnostics).Id); + } + public static IEnumerable Diagnostic_MalformedCtor_MemberData() { const string Pre = "[GeneratedRegex"; @@ -54,7 +70,7 @@ public static IEnumerable Diagnostic_MalformedCtor_MemberData() [Theory] [MemberData(nameof(Diagnostic_MalformedCtor_MemberData))] - public async Task Diagnostic_MalformedCtor(string attribute) + public async Task Diagnostic_Method_MalformedCtor(string attribute) { // Validate the generator doesn't crash with an incomplete attribute @@ -73,10 +89,31 @@ partial class C } } + [Theory] + [MemberData(nameof(Diagnostic_MalformedCtor_MemberData))] + public async Task Diagnostic_Property_MalformedCtor(string attribute) + { + // Validate the generator doesn't crash with an incomplete attribute + + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator($@" + using System.Text.RegularExpressions; + partial class C + {{ + {attribute} + private static partial Regex MultipleAttributes {{ get; }}; + }} + "); + + if (diagnostics.Count != 0) + { + Assert.Contains(Assert.Single(diagnostics).Id, new[] { "SYSLIB1040", "SYSLIB1042" }); + } + } + [Theory] [InlineData("null")] [InlineData("\"ab[]\"")] - public async Task Diagnostic_InvalidRegexPattern(string pattern) + public async Task Diagnostic_Method_InvalidRegexPattern(string pattern) { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; @@ -90,9 +127,26 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData("null")] + [InlineData("\"ab[]\"")] + public async Task Diagnostic_Property_InvalidRegexPattern(string pattern) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator($@" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex({pattern})] + private static partial Regex InvalidPattern {{ get; }}; + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Theory] [InlineData(0x800)] - public async Task Diagnostic_InvalidRegexOptions(int options) + public async Task Diagnostic_Method_InvalidRegexOptions(int options) { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; @@ -106,10 +160,26 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData(0x800)] + public async Task Diagnostic_Property_InvalidRegexOptions(int options) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""ab"", (RegexOptions){options})] + private static partial Regex InvalidPattern {{ get; }}; + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Theory] [InlineData(-2)] [InlineData(0)] - public async Task Diagnostic_InvalidRegexTimeout(int matchTimeout) + public async Task Diagnostic_Method_InvalidRegexTimeout(int matchTimeout) { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; @@ -123,10 +193,27 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData(-2)] + [InlineData(0)] + public async Task Diagnostic_Property_InvalidRegexTimeout(int matchTimeout) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""ab"", RegexOptions.None, {matchTimeout.ToString(CultureInfo.InvariantCulture)})] + private static partial Regex InvalidPattern {{ get; }}; + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Theory] [InlineData("null")] [InlineData("\"xxxxxxxxxxxxxxxxxxxx-ThisIsNotAValidCultureName-xxxxxxxxxxxxxxxxxxxx\"")] - public async Task Diagnostic_InvalidCultureName(string cultureName) + public async Task Diagnostic_Method_InvalidCultureName(string cultureName) { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; @@ -140,10 +227,27 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData("null")] + [InlineData("\"xxxxxxxxxxxxxxxxxxxx-ThisIsNotAValidCultureName-xxxxxxxxxxxxxxxxxxxx\"")] + public async Task Diagnostic_Property_InvalidCultureName(string cultureName) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""(?i)ab"", RegexOptions.None, {cultureName})] + private static partial Regex InvalidPattern {{ get; }}; + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Theory] [InlineData("(?i)abc", "RegexOptions.CultureInvariant")] [InlineData("abc", "RegexOptions.CultureInvariant | RegexOptions.IgnoreCase")] - public async Task Diagnostic_InvalidOptionsForCaseInsensitive(string pattern, string options) + public async Task Diagnostic_Method_InvalidOptionsForCaseInsensitive(string pattern, string options) { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; @@ -157,6 +261,23 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData("(?i)abc", "RegexOptions.CultureInvariant")] + [InlineData("abc", "RegexOptions.CultureInvariant | RegexOptions.IgnoreCase")] + public async Task Diagnostic_Property_InvalidOptionsForCaseInsensitive(string pattern, string options) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""{pattern}"", {options}, ""en-US"")] + private static partial Regex InvalidPattern {{ get; }}; + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Fact] public async Task Diagnostic_MethodMustReturnRegex() { @@ -172,6 +293,21 @@ partial class C Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); } + [Fact] + public async Task Diagnostic_PropertyMustReturnRegex() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + private static partial int MethodMustReturnRegex { get; }; + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + [Fact] public async Task Diagnostic_MethodMustNotBeGeneric() { @@ -202,6 +338,21 @@ partial class C Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); } + [Fact] + public async Task Diagnostic_PropertyMustBeParameterless() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + private static partial Regex this[int i] { get; } + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + [Fact] public async Task Diagnostic_MethodMustBePartial() { @@ -217,6 +368,65 @@ partial class C Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); } + [Fact] + public async Task Diagnostic_PropertyMustBePartial() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + private static Regex MethodMustBePartial => null; + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + + [Fact] + public async Task Diagnostic_PropertyMustNotHaveSetter() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + private static partial Regex MethodMustBePartial { get; set; } + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + + [Fact] + public async Task Diagnostic_PropertyMustHaveGetter() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(""ab"")] + private static partial Regex MethodMustBePartial { set; } + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + + [Fact] + public async Task Diagnostic_AttributeMustNotBeOnAccessor() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + partial class C + { + private static partial Regex MethodMustBePartial { [GeneratedRegex(""ab"")] get; } + } + "); + + Assert.Equal("SYSLIB1043", Assert.Single(diagnostics).Id); + } + [Fact] public async Task Diagnostic_MethodMustBeNonAbstract() { @@ -240,6 +450,29 @@ partial interface I Assert.All(diagnostics, d => Assert.Equal("SYSLIB1043", d.Id)); } + [Fact] + public async Task Diagnostic_PropertyMustBeNonAbstract() + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" + using System.Text.RegularExpressions; + + partial class C + { + [GeneratedRegex(""ab"")] + public abstract partial Regex MethodMustBeNonAbstract { get; }; + } + + partial interface I + { + [GeneratedRegex(""ab"")] + public static abstract partial Regex MethodMustBeNonAbstract { get; }; + } + "); + + Assert.Equal(2, diagnostics.Count); + Assert.All(diagnostics, d => Assert.Equal("SYSLIB1043", d.Id)); + } + [Theory] [InlineData(LanguageVersion.CSharp9)] [InlineData(LanguageVersion.CSharp10)] @@ -265,7 +498,7 @@ public async Task Diagnostic_NonBacktracking_LimitedSupport() partial class C { [GeneratedRegex(""ab"", RegexOptions.NonBacktracking)] - private static partial Regex RightToLeftNotSupported(); + private static partial Regex RightToLeftNotSupported { get; } } "); @@ -280,7 +513,7 @@ public async Task Diagnostic_CaseInsensitiveBackreference_LimitedSupport() partial class C { [GeneratedRegex(@""(?i)(ab)\1"")] - private static partial Regex CaseInsensitiveBackreferenceNotSupported(); + private static partial Regex CaseInsensitiveBackreferenceNotSupported { get; } } "); @@ -357,7 +590,10 @@ public async Task Valid_ClassWithoutNamespace() partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } ", compile: true)); } @@ -373,7 +609,10 @@ public async Task Valid_PatternOptions(string options) partial class C {{ [GeneratedRegex(""ab"", {options})] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"", {options})] + private static partial Regex Valid2 {{ get; }} }} ", compile: true)); } @@ -389,7 +628,10 @@ public async Task Valid_PatternOptionsTimeout(string timeout) partial class C {{ [GeneratedRegex(""ab"", RegexOptions.None, {timeout})] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"", RegexOptions.None, {timeout})] + private static partial Regex Valid2 {{ get; }} }} ", compile: true)); } @@ -402,7 +644,10 @@ public async Task Valid_NamedArguments() partial class C {{ [GeneratedRegex(pattern: ""ab"", options: RegexOptions.None, matchTimeoutMilliseconds: -1)] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(pattern: ""ab"", options: RegexOptions.None, matchTimeoutMilliseconds: -1)] + private static partial Regex Valid2 {{ get; }} }} ", compile: true)); } @@ -419,6 +664,12 @@ partial class C [GeneratedRegex(matchTimeoutMilliseconds: -1, pattern: ""ab"", options: RegexOptions.None)] private static partial Regex Valid2(); + + [GeneratedRegex(options: RegexOptions.None, matchTimeoutMilliseconds: -1, pattern: ""ab"")] + private static partial Regex Valid3 {{ get; }} + + [GeneratedRegex(matchTimeoutMilliseconds: -1, pattern: ""ab"", options: RegexOptions.None)] + private static partial Regex Valid4 {{ get; }} }} ", compile: true)); } @@ -434,7 +685,12 @@ partial class C [SuppressMessage(""CATEGORY1"", ""SOMEID1"")] [GeneratedRegex(""abc"")] [SuppressMessage(""CATEGORY2"", ""SOMEID2"")] - private static partial Regex AdditionalAttributes(); + private static partial Regex AdditionalAttributes1(); + + [SuppressMessage(""CATEGORY1"", ""SOMEID1"")] + [GeneratedRegex(""abc"")] + [SuppressMessage(""CATEGORY2"", ""SOMEID2"")] + private static partial Regex AdditionalAttributes2 {{ get; }} }} ", compile: true)); } @@ -457,7 +713,10 @@ namespace A partial class C {{ [GeneratedRegex(""{pattern}"", RegexOptions.{options})] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""{pattern}"", RegexOptions.{options})] + private static partial Regex Valid2 {{ get; }} }} }} ", compile: true, allowUnsafe: allowUnsafe, checkOverflow: checkOverflow)); @@ -472,7 +731,10 @@ namespace A; partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } ", compile: true)); } @@ -489,7 +751,10 @@ namespace B partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } } @@ -506,7 +771,10 @@ partial class B partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } ", compile: true)); @@ -524,7 +792,10 @@ partial class B partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } } @@ -542,7 +813,10 @@ partial class B partial class C { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } ", compile: true)); @@ -566,7 +840,10 @@ private protected partial class E private partial class F { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } } @@ -581,11 +858,31 @@ public async Task Valid_NullableRegex() { Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" #nullable enable + using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; partial class C { [GeneratedRegex(""ab"")] - private static partial Regex? Valid(); + private static partial Regex? Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex? Valid2 { get; } + + [GeneratedRegex(""ab"")] + [MaybeNull] + private static partial Regex Valid3 { get; } + + [GeneratedRegex(""ab"")] + [MaybeNull] + private static partial Regex? Valid4 { get; } + + [GeneratedRegex(""ab"")] + [NotNull] + private static partial Regex Vali5 { get; } + + [GeneratedRegex(""ab"")] + [NotNull] + private static partial Regex? Valid6 { get; } } ", compile: true)); } @@ -603,7 +900,10 @@ public partial class B private partial class C where T : IBlah { [GeneratedRegex(""ab"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + private static partial Regex Valid2 { get; } } } } @@ -623,25 +923,37 @@ public async Task Valid_InterfaceStatics() partial interface INonGeneric { [GeneratedRegex("".+?"")] - public static partial Regex Test(); + public static partial Regex Test1(); + + [GeneratedRegex("".+?"")] + public static partial Regex Test2 { get; } } partial interface IGeneric { [GeneratedRegex("".+?"")] - public static partial Regex Test(); + public static partial Regex Test1(); + + [GeneratedRegex("".+?"")] + public static partial Regex Test2 { get; } } partial interface ICovariantGeneric { [GeneratedRegex("".+?"")] - public static partial Regex Test(); + public static partial Regex Test1(); + + [GeneratedRegex("".+?"")] + public static partial Regex Test2 { get; } } partial interface IContravariantGeneric { [GeneratedRegex("".+?"")] - public static partial Regex Test(); + public static partial Regex Test1(); + + [GeneratedRegex("".+?"")] + public static partial Regex Test2 { get; } } ", compile: true)); } @@ -655,13 +967,16 @@ public async Task Valid_VirtualBaseImplementations() partial class C { [GeneratedRegex(""ab"")] - public virtual partial Regex Valid(); + public virtual partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + public virtual partial Regex Valid2 { get; } } ", compile: true)); } [Fact] - public async Task Valid_SameMethodNameInMultipleTypes() + public async Task Valid_SameMemberNameInMultipleTypes() { Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; @@ -672,25 +987,37 @@ public partial class B private partial class C { [GeneratedRegex(""1"")] - public partial Regex Valid(); + public partial Regex Valid1(); + + [GeneratedRegex(""1"")] + public partial Regex Valid2 { get; } } private partial class C { [GeneratedRegex(""2"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""2"")] + private static partial Regex Valid2 { get; } private partial class D { [GeneratedRegex(""3"")] - internal partial Regex Valid(); + internal partial Regex Valid1(); + + [GeneratedRegex(""3"")] + internal partial Regex Valid2 { get; } } } private partial class E { [GeneratedRegex(""4"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""4"")] + private static partial Regex Valid2 { get; } } } } @@ -702,6 +1029,9 @@ partial class F [GeneratedRegex(""6"")] public partial Regex Valid2(); + + [GeneratedRegex(""7"")] + public partial Regex Valid3 { get; } } ", compile: true)); } @@ -744,13 +1074,16 @@ public async Task Valid_Modifiers(string type, string typeModifier, bool instanc {typeModifier} partial {type} C {{ [GeneratedRegex(""ab"")] - {methodVisibility} {(instance ? "" : "static")} partial Regex Valid(); + {methodVisibility} {(instance ? "" : "static")} partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + {methodVisibility} {(instance ? "" : "static")} partial Regex Valid2 {{ get; }} }} ", compile: true)); } [Fact] - public async Task Valid_MultiplRegexMethodsPerClass() + public async Task Valid_MultipleRegexMembersPerClass() { Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; @@ -764,14 +1097,23 @@ partial class C1 [GeneratedRegex(""b"")] public static partial Regex C(); + + [GeneratedRegex(""c"")] + public static partial Regex D { get; } + + [GeneratedRegex(""c"")] + public static partial Regex E { get; } + + [GeneratedRegex(""d"")] + public static partial Regex F { get; } } partial class C2 { - [GeneratedRegex(""d"")] - public static partial Regex D(); + [GeneratedRegex(""e"")] + public static partial Regex G(); - [GeneratedRegex(""d"")] - public static partial Regex E(); + [GeneratedRegex(""e"")] + public static partial Regex H(); } ", compile: true)); } @@ -792,7 +1134,10 @@ public partial record D public partial struct E { [GeneratedRegex(""ab"")] - public static partial Regex Valid(); + public static partial Regex Valid1(); + + [GeneratedRegex(""ab"")] + public static partial Regex Valid2 { get; } } } } @@ -818,7 +1163,10 @@ public GeneratedRegexAttribute(string pattern){} partial class C { [GeneratedRegex(""abc"")] - private static partial Regex Valid(); + private static partial Regex Valid1(); + + [GeneratedRegex(""abc"")] + private static partial Regex Valid2 { get; } }", compile: true, additionalRefs: new[] { MetadataReference.CreateFromImage(referencedAssembly) })); } @@ -831,7 +1179,10 @@ public async Task Valid_ConcatenatedLiteralsArgument() partial class C { [GeneratedRegex(""ab"" + ""[cd]"")] - public static partial Regex Valid(); + public static partial Regex Valid1(); + + [GeneratedRegex(""ab"" + ""[cd]"")] + public static partial Regex Valid2 { get; } } ", compile: true)); } @@ -845,7 +1196,10 @@ public async Task Valid_InterpolatedLiteralsArgument() partial class C { [GeneratedRegex($""{""ab""}{""cd""}"")] - public static partial Regex Valid(); + public static partial Regex Valid1(); + + [GeneratedRegex($""{""ab""}{""cd""}"")] + public static partial Regex Valid2 { get; } }", compile: true)); } diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index 16090c5ea420de..05336ecc0ae916 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -469,6 +469,12 @@ net8.0/System.dll net9.0/System.dll + + CP0015 + T:System.Text.RegularExpressions.GeneratedRegexAttribute:[T:System.AttributeUsageAttribute] + net8.0/System.Text.RegularExpressions.dll + net9.0/System.Text.RegularExpressions.dll + CP0021 T:System.Diagnostics.Metrics.MeasurementCallback`1``0:struct @@ -529,4 +535,4 @@ net8.0/System.Text.Json.dll net9.0/System.Text.Json.dll - \ No newline at end of file +