Skip to content

Commit 60b88d5

Browse files
Add params argument support (#21)
* Add generator support for `params` parameter * Update `Validator` analyzers * Update `Validate` analyzer
1 parent 62c743d commit 60b88d5

File tree

10 files changed

+688
-156
lines changed

10 files changed

+688
-156
lines changed

src/Immediate.Validations.Analyzers/ValidateClassAnalyzer.cs

Lines changed: 96 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -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
430440
file 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
}

src/Immediate.Validations.Analyzers/ValidatorClassAnalyzer.cs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,10 @@ IMethodSymbol methodSymbol
253253
break;
254254
}
255255

256-
properties[i] = p;
256+
if (i == properties.Count)
257+
properties.Add(p);
258+
else
259+
properties[i] = p;
257260
}
258261
}
259262

@@ -307,32 +310,44 @@ private static void CheckParameterAllowedType(
307310
ITypeSymbol propertyType
308311
)
309312
{
310-
if (
311-
propertyType is not { SpecialType: SpecialType.System_Object }
312-
|| !property.GetAttributes().Any(a => a.AttributeClass.IsTargetTypeAttribute())
313+
var parameterType = parameter.Type;
314+
315+
if (parameter.IsParams
316+
&& property is IParameterSymbol { IsParams: true }
313317
)
314318
{
315-
if (!SymbolEqualityComparer.IncludeNullability.Equals(propertyType, parameter.Type))
316-
{
317-
context.ReportDiagnostic(
318-
Diagnostic.Create(
319-
ValidateMethodParameterIsIncorrectType,
320-
parameter.Locations[0],
321-
parameter.Name,
322-
property.Name
323-
)
324-
);
319+
propertyType = ((IArrayTypeSymbol)propertyType).ElementType;
320+
parameterType = ((IArrayTypeSymbol)parameterType).ElementType;
321+
}
325322

326-
context.ReportDiagnostic(
327-
Diagnostic.Create(
328-
ValidateMethodParameterIsIncorrectType,
329-
property.Locations[0],
330-
parameter.Name,
331-
property.Name
332-
)
333-
);
334-
}
323+
if (
324+
propertyType is { SpecialType: SpecialType.System_Object }
325+
&& property.GetAttributes().Any(a => a.AttributeClass.IsTargetTypeAttribute())
326+
)
327+
{
328+
return;
335329
}
330+
331+
if (SymbolEqualityComparer.IncludeNullability.Equals(propertyType, parameterType))
332+
return;
333+
334+
context.ReportDiagnostic(
335+
Diagnostic.Create(
336+
ValidateMethodParameterIsIncorrectType,
337+
parameter.Locations[0],
338+
parameter.Name,
339+
property.Name
340+
)
341+
);
342+
343+
context.ReportDiagnostic(
344+
Diagnostic.Create(
345+
ValidateMethodParameterIsIncorrectType,
346+
property.Locations[0],
347+
parameter.Name,
348+
property.Name
349+
)
350+
);
336351
}
337352

338353
private static void CheckParameterIsRequired(SymbolAnalysisContext context, IParameterSymbol parameter, ISymbol property, bool isRequired)

src/Immediate.Validations.Generators/EquatableReadOnlyList.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ namespace Immediate.Validations.Generators;
66
[ExcludeFromCodeCoverage]
77
public static class EquatableReadOnlyList
88
{
9-
public static EquatableReadOnlyList<T> ToEquatableReadOnlyList<T>(this IEnumerable<T> enumerable)
10-
=> new((enumerable as IReadOnlyList<T>) ?? enumerable.ToArray());
9+
public static EquatableReadOnlyList<T> ToEquatableReadOnlyList<T>(this IEnumerable<T>? enumerable)
10+
=> new((enumerable as IReadOnlyList<T>) ?? enumerable?.ToArray());
1111
}
1212

1313
/// <summary>

0 commit comments

Comments
 (0)