@@ -66,7 +66,7 @@ public sealed class ValidateClassAnalyzer : DiagnosticAnalyzer
6666 ) ;
6767
6868 public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics { get ; } =
69- ImmutableArray . Create < DiagnosticDescriptor > (
69+ ImmutableArray . Create (
7070 [
7171 ValidateAttributeMissing ,
7272 IValidationTargetMissing ,
@@ -83,15 +83,22 @@ public override void Initialize(AnalysisContext context)
8383 context . ConfigureGeneratedCodeAnalysis ( GeneratedCodeAnalysisFlags . None ) ;
8484 context . EnableConcurrentExecution ( ) ;
8585
86- context . RegisterSymbolAction ( AnalyzeSymbol , SymbolKind . NamedType ) ;
86+ context . RegisterSyntaxNodeAction (
87+ AnalyzeSymbol ,
88+ SyntaxKind . ClassDeclaration ,
89+ SyntaxKind . RecordDeclaration ,
90+ SyntaxKind . StructDeclaration ,
91+ SyntaxKind . RecordStructDeclaration ,
92+ SyntaxKind . InterfaceDeclaration
93+ ) ;
8794 }
8895
89- private static void AnalyzeSymbol ( SymbolAnalysisContext context )
96+ private static void AnalyzeSymbol ( SyntaxNodeAnalysisContext context )
9097 {
9198 var token = context . CancellationToken ;
9299 token . ThrowIfCancellationRequested ( ) ;
93100
94- var symbol = ( INamedTypeSymbol ) context . Symbol ;
101+ var symbol = ( INamedTypeSymbol ) context . ContainingSymbol ! ;
95102
96103 var hasValidateAttribute = symbol
97104 . GetAttributes ( )
@@ -280,88 +287,87 @@ CancellationToken token
280287 }
281288
282289 private static void ValidateArguments (
283- SymbolAnalysisContext context ,
290+ SyntaxNodeAnalysisContext context ,
284291 List < IPropertySymbol > properties ,
285292 AttributeData attribute ,
286293 ImmutableArray < IParameterSymbol > validateParameterSymbols ,
287- ITypeSymbol propertyType
294+ ITypeSymbol typeArgumentType
288295 )
289296 {
290297 var attributeSyntax = ( AttributeSyntax ) attribute . ApplicationSyntaxReference ! . GetSyntax ( ) ;
291298 var argumentListSyntax = attributeSyntax . ArgumentList ? . Arguments ?? [ ] ;
292299
300+ if ( argumentListSyntax . Count == 0 )
301+ return ;
302+
293303 var attributeParameters = attribute . AttributeConstructor ! . Parameters ;
294- var attributeArguments = attribute . ConstructorArguments ;
295- var attributeNamedArguments = attribute . NamedArguments ;
304+ var attributeParameterIndex = 0 ;
305+
296306 List < IPropertySymbol > ? attributeProperties = null ;
297307
298308 for ( var i = 0 ; i < argumentListSyntax . Count ; i ++ )
299309 {
300310 switch ( argumentListSyntax [ i ] )
301311 {
312+ case { NameEquals . Name . Identifier . ValueText : var name } :
313+ {
314+ if ( name is "Message" )
315+ break ;
316+
317+ attributeProperties ??= attribute . AttributeClass ! . GetMembers ( )
318+ . OfType < IPropertySymbol > ( )
319+ . ToList ( ) ;
320+ var property = attributeProperties . First ( a => a . Name == name ) ;
321+
322+ ValidateArgument (
323+ context ,
324+ argumentListSyntax [ i ] ,
325+ property ,
326+ validateParameterSymbols ,
327+ typeArgumentType ,
328+ properties
329+ ) ;
330+
331+ break ;
332+ }
333+
302334 case { NameColon . Name . Identifier . ValueText : var name } :
303335 {
304- for ( var j = 0 ; j < attributeArguments . Length ; j ++ )
336+ for ( var j = 0 ; j < attributeParameters . Length ; j ++ )
305337 {
306338 if ( attributeParameters [ j ] . Name == name )
307339 {
308340 ValidateArgument (
309341 context ,
310342 argumentListSyntax [ i ] ,
311343 attributeParameters [ j ] ,
312- attributeArguments [ j ] ,
313344 validateParameterSymbols ,
314- propertyType ,
345+ typeArgumentType ,
315346 properties
316347 ) ;
317348
318349 break ;
319350 }
320351 }
321352
353+ attributeParameterIndex ++ ;
322354 break ;
323355 }
324356
325- case { NameEquals . Name . Identifier . ValueText : var name } :
357+ default :
326358 {
327- if ( name is "Message" )
328- break ;
329-
330- var argument = attributeNamedArguments . First ( a => a . Key == name ) . Value ;
331-
332- attributeProperties ??= attribute . AttributeClass ! . GetMembers ( )
333- . OfType < IPropertySymbol > ( )
334- . ToList ( ) ;
335- var property = attributeProperties . First ( a => a . Name == name ) ;
336-
359+ var attributeParameter = attributeParameters [ attributeParameterIndex ] ;
337360 ValidateArgument (
338361 context ,
339362 argumentListSyntax [ i ] ,
340- property ,
341- argument ,
363+ attributeParameter ,
342364 validateParameterSymbols ,
343- propertyType ,
365+ typeArgumentType ,
344366 properties
345367 ) ;
346368
347- break ;
348- }
349-
350- default :
351- {
352- if ( i < attributeParameters . Length
353- && i < attributeArguments . Length )
354- {
355- ValidateArgument (
356- context ,
357- argumentListSyntax [ i ] ,
358- attributeParameters [ i ] ,
359- attributeArguments [ i ] ,
360- validateParameterSymbols ,
361- propertyType ,
362- properties
363- ) ;
364- }
369+ if ( ! attributeParameter . IsParams )
370+ attributeParameterIndex ++ ;
365371
366372 break ;
367373 }
@@ -370,58 +376,62 @@ ITypeSymbol propertyType
370376 }
371377
372378 private static void ValidateArgument (
373- SymbolAnalysisContext context ,
379+ SyntaxNodeAnalysisContext context ,
374380 AttributeArgumentSyntax syntax ,
375381 ISymbol parameter ,
376- TypedConstant argument ,
377382 ImmutableArray < IParameterSymbol > validateParameterSymbols ,
378- ITypeSymbol propertyType ,
383+ ITypeSymbol typeArgumentType ,
379384 List < IPropertySymbol > properties
380385 )
381386 {
382- if ( parameter . IsTargetTypeSymbol ( )
383- && argument . Type is not null
384- )
385- {
386- var validateParameter = validateParameterSymbols . First ( p => p . Name == parameter . Name ) ;
387- var targetType = validateParameter . Type ;
388- if ( targetType is ITypeParameterSymbol )
389- targetType = propertyType ;
387+ if ( ! parameter . IsTargetTypeSymbol ( ) )
388+ return ;
390389
391- if ( syntax . Expression . IsNameOfExpression ( out var propertyName ) )
392- {
393- var property = properties
394- . FirstOrDefault ( p => p . Name == propertyName ) ;
390+ var validateParameter = validateParameterSymbols . First ( p => p . Name == parameter . Name ) ;
391+ var targetType = validateParameter . Type ;
395392
396- if ( property is null )
397- return ;
393+ if ( validateParameter . IsParams && targetType is IArrayTypeSymbol { ElementType : { } et } )
394+ targetType = et ;
398395
399- if ( ! context . Compilation . ClassifyConversion ( property . Type , targetType ) . IsValidConversion ( ) )
400- {
401- context . ReportDiagnostic (
402- Diagnostic . Create (
403- ValidateParameterPropertyIncompatibleType ,
404- syntax . GetLocation ( ) ,
405- parameter . Name ,
406- property . Name ,
407- targetType . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat )
408- )
409- ) ;
410- }
396+ if ( targetType is ITypeParameterSymbol )
397+ targetType = typeArgumentType ;
398+
399+ if ( syntax . Expression . IsNameOfExpression ( out var propertyName ) )
400+ {
401+ var property = properties
402+ . FirstOrDefault ( p => p . Name == propertyName ) ;
403+
404+ if ( property is null )
405+ return ;
406+
407+ if ( ! context . Compilation . ClassifyConversion ( property . Type , targetType ) . IsValidConversion ( ) )
408+ {
409+ context . ReportDiagnostic (
410+ Diagnostic . Create (
411+ ValidateParameterPropertyIncompatibleType ,
412+ syntax . GetLocation ( ) ,
413+ parameter . Name ,
414+ property . Name ,
415+ targetType . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat )
416+ )
417+ ) ;
411418 }
412- else
419+ }
420+ else
421+ {
422+ if ( context . SemanticModel . GetOperation ( syntax . Expression ) ? . Type is not ITypeSymbol argumentType )
423+ return ;
424+
425+ if ( ! context . Compilation . ClassifyConversion ( argumentType , targetType ) . IsValidConversion ( ) )
413426 {
414- if ( ! context . Compilation . ClassifyConversion ( argument . Type , targetType ) . IsValidConversion ( ) )
415- {
416- context . ReportDiagnostic (
417- Diagnostic . Create (
418- ValidateParameterIncompatibleType ,
419- syntax . GetLocation ( ) ,
420- parameter . Name ,
421- targetType . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat )
422- )
423- ) ;
424- }
427+ context . ReportDiagnostic (
428+ Diagnostic . Create (
429+ ValidateParameterIncompatibleType ,
430+ syntax . GetLocation ( ) ,
431+ parameter . Name ,
432+ targetType . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat )
433+ )
434+ ) ;
425435 }
426436 }
427437 }
@@ -430,13 +440,10 @@ List<IPropertySymbol> properties
430440file static class Extensions
431441{
432442 public static bool IsTargetTypeSymbol ( this ISymbol symbol ) =>
433- symbol is IParameterSymbol { Type . SpecialType : SpecialType . System_Object }
434- or IPropertySymbol { Type . SpecialType : SpecialType . System_Object }
435- && symbol . GetAttributes ( ) . Any ( a => a . AttributeClass . IsTargetTypeAttribute ( ) ) ;
443+ symbol . GetAttributes ( ) . Any ( a => a . AttributeClass . IsTargetTypeAttribute ( ) ) ;
436444
437445 public static bool IsNameOfExpression ( this ExpressionSyntax syntax , out string ? name )
438446 {
439- name = null ;
440447 if ( syntax is InvocationExpressionSyntax
441448 {
442449 Expression : SimpleNameSyntax { Identifier . ValueText : "nameof" } ,
@@ -449,6 +456,7 @@ public static bool IsNameOfExpression(this ExpressionSyntax syntax, out string?
449456 }
450457 else
451458 {
459+ name = null ;
452460 return false ;
453461 }
454462 }
0 commit comments