Skip to content
Merged
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
45 changes: 35 additions & 10 deletions src/Common/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,22 +303,47 @@ typeSymbol is INamedTypeSymbol
SemanticModel semanticModel
)
{
var propertySyntax = (PropertyDeclarationSyntax)propertySymbol.DeclaringSyntaxReferences.First().GetSyntax();
switch (propertySymbol.DeclaringSyntaxReferences.First().GetSyntax())
{
case PropertyDeclarationSyntax propertySyntax:
{
var list = new List<(string? Target, IObjectCreationOperation AttributeOperation)>();

var list = new List<(string? Target, IObjectCreationOperation AttributeOperation)>();
foreach (var attributeList in propertySyntax.AttributeLists)
{
var target = attributeList.Target?.Identifier.ValueText;

foreach (var attributeList in propertySyntax.AttributeLists)
{
var target = attributeList.Target?.Identifier.ValueText;
foreach (var attribute in attributeList.Attributes)
{
if (semanticModel.GetOperation(attribute) is IAttributeOperation { Operation: IObjectCreationOperation operation })
list.Add((target, operation));
}
}

return list;
}

foreach (var attribute in attributeList.Attributes)
case ParameterSyntax parameterSyntax:
{
if (semanticModel.GetOperation(attribute) is IAttributeOperation { Operation: IObjectCreationOperation operation })
list.Add((target, operation));
var list = new List<(string? Target, IObjectCreationOperation AttributeOperation)>();

foreach (var attributeList in parameterSyntax.AttributeLists)
{
var target = attributeList.Target?.Identifier.ValueText.NullIf("property");

foreach (var attribute in attributeList.Attributes)
{
if (semanticModel.GetOperation(attribute) is IAttributeOperation { Operation: IObjectCreationOperation operation })
list.Add((target, operation));
}
}

return list;
}
}

return list;
case var syntax:
throw new InvalidOperationException($"Property declared using a `{syntax.GetType().FullName}`.");
}
}

public static bool IsTargetTypeSymbol(this ISymbol symbol) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,31 @@ public override void ReportSuppressions(SuppressionAnalysisContext context)

if (syntaxTree
?.GetRoot(token)
.FindNode(diagnostic.Location.SourceSpan) is
not AttributeTargetSpecifierSyntax
.FindNode(diagnostic.Location.SourceSpan) is not AttributeTargetSpecifierSyntax
{
Identifier.ValueText: "element",
Parent.Parent: PropertyDeclarationSyntax propertyDeclarationSyntax
Parent.Parent: SyntaxNode declarationSyntax
})
{
continue;
}

if (context.GetSemanticModel(syntaxTree)
.GetDeclaredSymbol(propertyDeclarationSyntax, token) is
not IPropertySymbol
{
ContainingType: INamedTypeSymbol containerSymbol,
Type: ITypeSymbol propertyTypeSymbol
})
if (context.GetSemanticModel(syntaxTree).GetDeclaredSymbol(declarationSyntax, token) switch
{
IPropertySymbol
{
ContainingType: INamedTypeSymbol ct1,
Type: ITypeSymbol pt1
} => (true, ct1, pt1),

IParameterSymbol
{
ContainingType: INamedTypeSymbol ct1,
Type: ITypeSymbol pt1
} => (true, ct1, pt1),

_ => (false, null, null),
} is not (true, { } containerSymbol, { } propertyTypeSymbol))
{
continue;
}
Expand Down Expand Up @@ -79,6 +87,8 @@ not IPropertySymbol
diagnostic
)
);

break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,36 @@ public sealed class InvalidAttributeTargetSuppressorTests
public static readonly DiagnosticResult CS0658 =
DiagnosticResult.CompilerWarning("CS0658");

[Fact]
public async Task ElementAttributeInValidatorListForParameterIsSuppressed() =>
await AnalyzerTestHelpers
.CreateSuppressorTest<InvalidAttributeTargetSuppressor>(
"""
#nullable enable

using System.Collections.Generic;
using Immediate.Validations.Shared;

[Validate]
public record Target(
[{|#0:element|}: MaxLength(3)]
List<string> Strings
): IValidationTarget<Target>
{

public ValidationResult Validate() => [];
public ValidationResult Validate(ValidationResult errors) => [];
public static ValidationResult Validate(Target target) => [];
public static ValidationResult Validate(Target target, ValidationResult errors) => [];
}
"""
)
.WithSpecificDiagnostics([CS0658])
.WithExpectedDiagnosticsResults([
CS0658.WithLocation(0).WithIsSuppressed(true),
])
.RunAsync(TestContext.Current.CancellationToken);

[Fact]
public async Task ElementAttributeInValidatorListIsSuppressed() =>
await AnalyzerTestHelpers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//HintName: IV...ValidateClass.g.cs
using System.Collections.Generic;
using Immediate.Validations.Shared;

#nullable enable
#pragma warning disable CS1591


partial record ValidateClass : IValidationTarget
{
ValidationResult IValidationTarget.Validate() =>
Validate(this, []);

ValidationResult IValidationTarget.Validate(ValidationResult errors) =>
Validate(this, errors);

static ValidationResult IValidationTarget<ValidateClass>.Validate(ValidateClass? target) =>
Validate(target, []);

static ValidationResult IValidationTarget<ValidateClass>.Validate(ValidateClass? target, ValidationResult errors) =>
Validate(target, errors);

public static ValidationResult Validate(ValidateClass? target) =>
Validate(target, []);

public static ValidationResult Validate(ValidateClass? target, ValidationResult errors)
{
if (target is not { } t)
{
return new()
{
{ ".self", "`target` must not be `null`." },
};
}

if (!errors.VisitType(typeof(ValidateClass)))
return errors;


__ValidateTesting1(errors, t, t.Testing1);
__Validatedata(errors, t, t.data);


return errors;
}



private static void __ValidateTesting1(
ValidationResult errors, ValidateClass instance, string target
)
{

if (target is not { } t)
{
errors.Add(
$"Testing1",
global::Immediate.Validations.Shared.NotNullAttribute.DefaultMessage,
new()
{
["PropertyName"] = $"Testing1",
["PropertyValue"] = null,
}
);

return;
}



{
if (!global::Immediate.Validations.Shared.MaxLengthAttribute.ValidateProperty(
t
, maxLength: 3
)
)
{
errors.Add(
$"Testing1",
global::Immediate.Validations.Shared.MaxLengthAttribute.DefaultMessage,
new()
{
["PropertyName"] = $"Testing1",
["PropertyValue"] = t,
["MaxLengthName"] = "",
["MaxLengthValue"] = 3,
}
);
}
}
}

private static void __Validatedata0(
ValidationResult errors, ValidateClass instance, int target, int counter0
)
{

var t = target;



{
if (!global::Immediate.Validations.Shared.GreaterThanAttribute.ValidateProperty(
t
, comparison: 0
)
)
{
errors.Add(
$"data[{counter0}]",
global::Immediate.Validations.Shared.GreaterThanAttribute.DefaultMessage,
new()
{
["PropertyName"] = $"data[{counter0}]",
["PropertyValue"] = t,
["ComparisonName"] = "",
["ComparisonValue"] = 0,
}
);
}
}
}

private static void __Validatedata(
ValidationResult errors, ValidateClass instance, global::System.Collections.Generic.List<int> target
)
{

if (target is not { } t)
{
errors.Add(
$"data",
global::Immediate.Validations.Shared.NotNullAttribute.DefaultMessage,
new()
{
["PropertyName"] = $"data",
["PropertyValue"] = null,
}
);

return;
}


var counter0 = 0;
foreach (var item0 in t)
{
__Validatedata0(
errors, instance, item0, counter0
);
counter0++;
}

{
if (!global::Immediate.Validations.Shared.NotEmptyAttribute.ValidateProperty(
t
)
)
{
errors.Add(
$"data",
global::Immediate.Validations.Shared.NotEmptyAttribute.DefaultMessage,
new()
{
["PropertyName"] = $"data",
["PropertyValue"] = t,
}
);
}
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@ namespace Immediate.Validations.Tests.GeneratorTests;

public sealed class MiscellaneousTests
{
[Fact]
public async Task PropertyValidationOnRecord()
{
var result = GeneratorTestHelper.RunGenerator(
"""
using System.ComponentModel;
using System.Collections.Generic;
using Immediate.Validations.Shared;

[Validate]
public sealed partial record ValidateClass(
[property: MaxLength(3)]
string Testing1,

[property: NotEmpty]
[element: GreaterThan(0)]
List<int> data
): IValidationTarget<ValidateClass>;
"""
);

Assert.Equal(
[
@"Immediate.Validations.Generators/Immediate.Validations.Generators.ImmediateValidationsGenerator/IV...ValidateClass.g.cs",
],
result.GeneratedTrees.Select(t => t.FilePath.Replace('\\', '/'))
);

_ = await Verify(result);
}

[Fact]
public async Task FilledDescriptionChangesName()
{
Expand Down