2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Collections . Immutable ;
5
+ using System . Diagnostics ;
5
6
using System . Globalization ;
6
7
using System . Linq ;
7
8
using System . Threading ;
@@ -25,7 +26,16 @@ public partial class RegexGenerator
25
26
private static object ? GetRegexMethodDataOrFailureDiagnostic (
26
27
GeneratorAttributeSyntaxContext context , CancellationToken cancellationToken )
27
28
{
28
- var methodSyntax = ( MethodDeclarationSyntax ) context . TargetNode ;
29
+ if ( context . TargetNode is IndexerDeclarationSyntax or AccessorDeclarationSyntax )
30
+ {
31
+ // We allow these to be used as a target node for the sole purpose
32
+ // of being able to flag invalid use when [GeneratedRegex] is applied incorrectly.
33
+ // Otherwise, if the ForAttributeWithMetadataName call excluded these, [GeneratedRegex]
34
+ // could be applied to them and we wouldn't be able to issue a diagnostic.
35
+ return new DiagnosticData ( DiagnosticDescriptors . RegexMemberMustHaveValidSignature , GetComparableLocation ( context . TargetNode ) ) ;
36
+ }
37
+
38
+ var memberSyntax = ( MemberDeclarationSyntax ) context . TargetNode ;
29
39
SemanticModel sm = context . SemanticModel ;
30
40
31
41
Compilation compilation = sm . Compilation ;
@@ -37,34 +47,34 @@ public partial class RegexGenerator
37
47
return null ;
38
48
}
39
49
40
- TypeDeclarationSyntax ? typeDec = methodSyntax . Parent as TypeDeclarationSyntax ;
50
+ TypeDeclarationSyntax ? typeDec = memberSyntax . Parent as TypeDeclarationSyntax ;
41
51
if ( typeDec is null )
42
52
{
43
53
return null ;
44
54
}
45
55
46
- IMethodSymbol ? regexMethodSymbol = context . TargetSymbol as IMethodSymbol ;
47
- if ( regexMethodSymbol is null )
56
+ ISymbol ? regexMemberSymbol = context . TargetSymbol is IMethodSymbol or IPropertySymbol ? context . TargetSymbol : null ;
57
+ if ( regexMemberSymbol is null )
48
58
{
49
59
return null ;
50
60
}
51
61
52
62
ImmutableArray < AttributeData > boundAttributes = context . Attributes ;
53
63
if ( boundAttributes . Length != 1 )
54
64
{
55
- return new DiagnosticData ( DiagnosticDescriptors . MultipleGeneratedRegexAttributes , GetComparableLocation ( methodSyntax ) ) ;
65
+ return new DiagnosticData ( DiagnosticDescriptors . MultipleGeneratedRegexAttributes , GetComparableLocation ( memberSyntax ) ) ;
56
66
}
57
67
AttributeData generatedRegexAttr = boundAttributes [ 0 ] ;
58
68
59
69
if ( generatedRegexAttr . ConstructorArguments . Any ( ca => ca . Kind == TypedConstantKind . Error ) )
60
70
{
61
- return new DiagnosticData ( DiagnosticDescriptors . InvalidGeneratedRegexAttribute , GetComparableLocation ( methodSyntax ) ) ;
71
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidGeneratedRegexAttribute , GetComparableLocation ( memberSyntax ) ) ;
62
72
}
63
73
64
74
ImmutableArray < TypedConstant > items = generatedRegexAttr . ConstructorArguments ;
65
75
if ( items . Length is 0 or > 4 )
66
76
{
67
- return new DiagnosticData ( DiagnosticDescriptors . InvalidGeneratedRegexAttribute , GetComparableLocation ( methodSyntax ) ) ;
77
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidGeneratedRegexAttribute , GetComparableLocation ( memberSyntax ) ) ;
68
78
}
69
79
70
80
string ? pattern = items [ 0 ] . Value as string ;
@@ -96,16 +106,36 @@ public partial class RegexGenerator
96
106
97
107
if ( pattern is null || cultureName is null )
98
108
{
99
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , "(null)" ) ;
109
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , "(null)" ) ;
100
110
}
101
111
102
- if ( ! regexMethodSymbol . IsPartialDefinition ||
103
- regexMethodSymbol . IsAbstract ||
104
- regexMethodSymbol . Parameters . Length != 0 ||
105
- regexMethodSymbol . Arity != 0 ||
106
- ! SymbolEqualityComparer . Default . Equals ( regexMethodSymbol . ReturnType , regexSymbol ) )
112
+ bool nullableRegex ;
113
+ if ( regexMemberSymbol is IMethodSymbol regexMethodSymbol )
107
114
{
108
- return new DiagnosticData ( DiagnosticDescriptors . RegexMethodMustHaveValidSignature , GetComparableLocation ( methodSyntax ) ) ;
115
+ if ( ! regexMethodSymbol . IsPartialDefinition ||
116
+ regexMethodSymbol . IsAbstract ||
117
+ regexMethodSymbol . Parameters . Length != 0 ||
118
+ regexMethodSymbol . Arity != 0 ||
119
+ ! SymbolEqualityComparer . Default . Equals ( regexMethodSymbol . ReturnType , regexSymbol ) )
120
+ {
121
+ return new DiagnosticData ( DiagnosticDescriptors . RegexMemberMustHaveValidSignature , GetComparableLocation ( memberSyntax ) ) ;
122
+ }
123
+
124
+ nullableRegex = regexMethodSymbol . ReturnNullableAnnotation == NullableAnnotation . Annotated ;
125
+ }
126
+ else
127
+ {
128
+ Debug . Assert ( regexMemberSymbol is IPropertySymbol ) ;
129
+ IPropertySymbol regexPropertySymbol = ( IPropertySymbol ) regexMemberSymbol ;
130
+ if ( ! memberSyntax . Modifiers . Any ( SyntaxKind . PartialKeyword ) || // TODO: Switch to using regexPropertySymbol.IsPartialDefinition when available
131
+ regexPropertySymbol . IsAbstract ||
132
+ regexPropertySymbol . SetMethod is not null ||
133
+ ! SymbolEqualityComparer . Default . Equals ( regexPropertySymbol . Type , regexSymbol ) )
134
+ {
135
+ return new DiagnosticData ( DiagnosticDescriptors . RegexMemberMustHaveValidSignature , GetComparableLocation ( memberSyntax ) ) ;
136
+ }
137
+
138
+ nullableRegex = regexPropertySymbol . NullableAnnotation == NullableAnnotation . Annotated ;
109
139
}
110
140
111
141
RegexOptions regexOptions = options is not null ? ( RegexOptions ) options : RegexOptions . None ;
@@ -124,15 +154,15 @@ public partial class RegexGenerator
124
154
}
125
155
catch ( Exception e )
126
156
{
127
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , e . Message ) ;
157
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , e . Message ) ;
128
158
}
129
159
130
160
if ( ( regexOptionsWithPatternOptions & RegexOptions . IgnoreCase ) != 0 && ! string . IsNullOrEmpty ( cultureName ) )
131
161
{
132
162
if ( ( regexOptions & RegexOptions . CultureInvariant ) != 0 )
133
163
{
134
164
// User passed in both a culture name and set RegexOptions.CultureInvariant which causes an explicit conflict.
135
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , "cultureName" ) ;
165
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , "cultureName" ) ;
136
166
}
137
167
138
168
try
@@ -141,7 +171,7 @@ public partial class RegexGenerator
141
171
}
142
172
catch ( CultureNotFoundException )
143
173
{
144
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , "cultureName" ) ;
174
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , "cultureName" ) ;
145
175
}
146
176
}
147
177
@@ -159,17 +189,17 @@ public partial class RegexGenerator
159
189
RegexOptions . Singleline ;
160
190
if ( ( regexOptions & ~ SupportedOptions ) != 0 )
161
191
{
162
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , "options" ) ;
192
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , "options" ) ;
163
193
}
164
194
165
195
// Validate the timeout
166
196
if ( matchTimeout is 0 or < - 1 )
167
197
{
168
- return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( methodSyntax ) , "matchTimeout" ) ;
198
+ return new DiagnosticData ( DiagnosticDescriptors . InvalidRegexArguments , GetComparableLocation ( memberSyntax ) , "matchTimeout" ) ;
169
199
}
170
200
171
201
// Determine the namespace the class is declared in, if any
172
- string ? ns = regexMethodSymbol . ContainingType ? . ContainingNamespace ? . ToDisplayString (
202
+ string ? ns = regexMemberSymbol . ContainingType ? . ContainingNamespace ? . ToDisplayString (
173
203
SymbolDisplayFormat . FullyQualifiedFormat . WithGlobalNamespaceStyle ( SymbolDisplayGlobalNamespaceStyle . Omitted ) ) ;
174
204
175
205
var regexType = new RegexType (
@@ -183,9 +213,11 @@ public partial class RegexGenerator
183
213
184
214
var result = new RegexPatternAndSyntax (
185
215
regexType ,
186
- GetComparableLocation ( methodSyntax ) ,
187
- regexMethodSymbol . Name ,
188
- methodSyntax . Modifiers . ToString ( ) ,
216
+ IsProperty : regexMemberSymbol is IPropertySymbol ,
217
+ GetComparableLocation ( memberSyntax ) ,
218
+ regexMemberSymbol . Name ,
219
+ memberSyntax . Modifiers . ToString ( ) ,
220
+ nullableRegex ,
189
221
pattern ,
190
222
regexOptions ,
191
223
matchTimeout ,
@@ -217,18 +249,18 @@ SyntaxKind.RecordStructDeclaration or
217
249
218
250
// Get a Location object that doesn't store a reference to the compilation.
219
251
// That allows it to compare equally across compilations.
220
- static Location GetComparableLocation ( MethodDeclarationSyntax method )
252
+ static Location GetComparableLocation ( SyntaxNode syntax )
221
253
{
222
- var location = method . GetLocation ( ) ;
254
+ var location = syntax . GetLocation ( ) ;
223
255
return Location . Create ( location . SourceTree ? . FilePath ?? string . Empty , location . SourceSpan , location . GetLineSpan ( ) . Span ) ;
224
256
}
225
257
}
226
258
227
259
/// <summary>Data about a regex directly from the GeneratedRegex attribute.</summary>
228
- internal sealed record RegexPatternAndSyntax ( RegexType DeclaringType , Location DiagnosticLocation , string MethodName , string Modifiers , string Pattern , RegexOptions Options , int ? MatchTimeout , CultureInfo Culture , CompilationData CompilationData ) ;
260
+ 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 ) ;
229
261
230
262
/// <summary>Data about a regex, including a fully parsed RegexTree and subsequent analysis.</summary>
231
- internal sealed record RegexMethod ( RegexType DeclaringType , Location DiagnosticLocation , string MethodName , string Modifiers , string Pattern , RegexOptions Options , int ? MatchTimeout , RegexTree Tree , AnalysisResults Analysis , CompilationData CompilationData )
263
+ 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 )
232
264
{
233
265
public string ? GeneratedName { get ; set ; }
234
266
public bool IsDuplicate { get ; set ; }
0 commit comments