Skip to content

Commit 13a1077

Browse files
Address [NotNull] on int? (#124)
1 parent d66ecfe commit 13a1077

18 files changed

+435
-56
lines changed

src/Common/ITypeSymbolExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,25 @@ typeSymbol is
130130
},
131131
};
132132

133+
public static bool IsNotNullAttribute([NotNullWhen(returnValue: true)] this INamedTypeSymbol? typeSymbol) =>
134+
typeSymbol is
135+
{
136+
Name: "NotNullAttribute",
137+
ContainingNamespace:
138+
{
139+
Name: "Shared",
140+
ContainingNamespace:
141+
{
142+
Name: "Validations",
143+
ContainingNamespace:
144+
{
145+
Name: "Immediate",
146+
ContainingNamespace.IsGlobalNamespace: true,
147+
},
148+
},
149+
},
150+
};
151+
133152
public static bool IsValidationResult(this INamedTypeSymbol? typeSymbol) =>
134153
typeSymbol is
135154
{

src/Immediate.Validations.Analyzers/AnalyzerReleases.Shipped.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,29 @@
33
### New Rules
44

55
Rule ID | Category | Severity | Notes
6-
--------|----------|----------|--------------------
6+
--------|----------|----------|-------
7+
IV0001 | ImmediateValidations | Error | ValidateMethodAnalyzer
8+
IV0002 | ImmediateValidations | Error | ValidatorClassAnalyzer
9+
IV0003 | ImmediateValidations | Error | ValidatorClassAnalyzer
10+
IV0004 | ImmediateValidations | Error | ValidatorClassAnalyzer
11+
IV0005 | ImmediateValidations | Error | ValidatorClassAnalyzer
12+
IV0006 | ImmediateValidations | Error | ValidatorClassAnalyzer
13+
IV0007 | ImmediateValidations | Error | ValidatorClassAnalyzer
14+
IV0008 | ImmediateValidations | Error | ValidatorClassAnalyzer
15+
IV0009 | ImmediateValidations | Error | ValidatorClassAnalyzer
16+
IV0010 | ImmediateValidations | Error | ValidatorClassAnalyzer
17+
IV0011 | ImmediateValidations | Warning | AssemblyBehaviorAnalyzer
18+
IV0012 | ImmediateValidations | Error | ValidateClassAnalyzer
19+
IV0013 | ImmediateValidations | Warning | ValidateClassAnalyzer
20+
IV0014 | ImmediateValidations | Warning | ValidateClassAnalyzer
21+
IV0015 | ImmediateValidations | Warning | ValidateClassAnalyzer
22+
IV0016 | ImmediateValidations | Warning | ValidateClassAnalyzer
23+
IV0017 | ImmediateValidations | Error | ValidateClassAnalyzer
24+
25+
## Release 1.1
26+
27+
### New Rules
28+
29+
Rule ID | Category | Severity | Notes
30+
--------|----------|----------|-------
31+
IV0018 | ImmediateValidations | Warning | ValidateClassAnalyzer
Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1 @@
11
### New Rules
2-
3-
Rule ID | Category | Severity | Notes
4-
--------|----------|----------|-------
5-
IV0001 | ImmediateValidations | Error | ValidateMethodAnalyzer
6-
IV0002 | ImmediateValidations | Error | ValidatorClassAnalyzer
7-
IV0003 | ImmediateValidations | Error | ValidatorClassAnalyzer
8-
IV0004 | ImmediateValidations | Error | ValidatorClassAnalyzer
9-
IV0005 | ImmediateValidations | Error | ValidatorClassAnalyzer
10-
IV0006 | ImmediateValidations | Error | ValidatorClassAnalyzer
11-
IV0007 | ImmediateValidations | Error | ValidatorClassAnalyzer
12-
IV0008 | ImmediateValidations | Error | ValidatorClassAnalyzer
13-
IV0009 | ImmediateValidations | Error | ValidatorClassAnalyzer
14-
IV0010 | ImmediateValidations | Error | ValidatorClassAnalyzer
15-
IV0011 | ImmediateValidations | Warning | AssemblyBehaviorAnalyzer
16-
IV0012 | ImmediateValidations | Error | ValidateClassAnalyzer
17-
IV0013 | ImmediateValidations | Warning | ValidateClassAnalyzer
18-
IV0014 | ImmediateValidations | Warning | ValidateClassAnalyzer
19-
IV0015 | ImmediateValidations | Warning | ValidateClassAnalyzer
20-
IV0016 | ImmediateValidations | Warning | ValidateClassAnalyzer
21-
IV0017 | ImmediateValidations | Error | ValidateClassAnalyzer

src/Immediate.Validations.Analyzers/DiagnosticIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ public static class DiagnosticIds
2222
public const string IV0015ValidateParameterIncompatibleType = "IV0015";
2323
public const string IV0016ValidateParameterPropertyIncompatibleType = "IV0016";
2424
public const string IV0017ValidateParameterNameofInvalid = "IV0017";
25+
public const string IV0018ValidateNotNullWhenInvalid = "IV0018";
2526
}

src/Immediate.Validations.Analyzers/ValidateClassAnalyzer.cs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ public sealed class ValidateClassAnalyzer : DiagnosticAnalyzer
7676
description: "Invalid nameof will generate incorrect code."
7777
);
7878

79+
public static readonly DiagnosticDescriptor ValidateNotNullWhenInvalid =
80+
new(
81+
id: DiagnosticIds.IV0018ValidateNotNullWhenInvalid,
82+
title: "`[NotNull]` applied to not-null property",
83+
messageFormat: "`[NotNull]` is applied to property `{0}`, but type `{1}` cannot be null",
84+
category: "ImmediateValidations",
85+
defaultSeverity: DiagnosticSeverity.Warning,
86+
isEnabledByDefault: true,
87+
description: "Invalid `[NotNull]` will generate incorrect code."
88+
);
89+
7990
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
8091
ImmutableArray.Create(
8192
[
@@ -85,6 +96,7 @@ public sealed class ValidateClassAnalyzer : DiagnosticAnalyzer
8596
ValidateParameterIncompatibleType,
8697
ValidateParameterPropertyIncompatibleType,
8798
ValidateParameterNameofInvalid,
99+
ValidateNotNullWhenInvalid,
88100
]);
89101

90102
public override void Initialize(AnalysisContext context)
@@ -168,7 +180,7 @@ or IMethodSymbol
168180
{
169181
var status = ValidateAttribute(context.Compilation, property.Type, attribute.AttributeClass!, token);
170182

171-
if (status.Report)
183+
if (status.IncompatibleType)
172184
{
173185
context.ReportDiagnostic(
174186
Diagnostic.Create(
@@ -179,6 +191,17 @@ or IMethodSymbol
179191
)
180192
);
181193
}
194+
else if (status.InvalidNotNull)
195+
{
196+
context.ReportDiagnostic(
197+
Diagnostic.Create(
198+
ValidateNotNullWhenInvalid,
199+
attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation(),
200+
property.Name,
201+
property.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)
202+
)
203+
);
204+
}
182205
else if (status.ValidateArguments)
183206
{
184207
ValidateArguments(
@@ -195,7 +218,8 @@ or IMethodSymbol
195218

196219
private sealed record AttributeValidationStatus
197220
{
198-
public required bool Report { get; init; }
221+
public bool IncompatibleType { get; init; }
222+
public bool InvalidNotNull { get; init; }
199223
public bool ValidateArguments { get; init; }
200224
public ImmutableArray<IParameterSymbol> ValidateParameterSymbols { get; init; }
201225
}
@@ -210,7 +234,7 @@ CancellationToken token
210234
token.ThrowIfCancellationRequested();
211235

212236
if (!attributeSymbol.ImplementsValidatorAttribute())
213-
return new() { Report = false };
237+
return new();
214238

215239
token.ThrowIfCancellationRequested();
216240

@@ -225,16 +249,23 @@ CancellationToken token
225249
)
226250
{
227251
// covered by other analyzers
228-
return new() { Report = false };
252+
return new();
229253
}
230254

255+
token.ThrowIfCancellationRequested();
256+
231257
if (targetParameterType is ITypeParameterSymbol tps)
232258
{
259+
if (attributeSymbol.IsNotNullAttribute()
260+
&& propertyType is { IsReferenceType: false, OriginalDefinition.SpecialType: not SpecialType.System_Nullable_T })
261+
{
262+
return new() { InvalidNotNull = true };
263+
}
264+
233265
if (tps.SatisfiesConstraints(propertyType, compilation))
234266
{
235267
return new()
236268
{
237-
Report = false,
238269
ValidateArguments = true,
239270
ValidateParameterSymbols = validateMethod.Parameters,
240271
};
@@ -255,13 +286,14 @@ CancellationToken token
255286
{
256287
return new()
257288
{
258-
Report = false,
259289
ValidateArguments = true,
260290
ValidateParameterSymbols = validateMethod.Parameters,
261291
};
262292
}
263293
}
264294

295+
token.ThrowIfCancellationRequested();
296+
265297
return propertyType switch
266298
{
267299
IArrayTypeSymbol ats =>
@@ -285,7 +317,7 @@ CancellationToken token
285317
token
286318
),
287319

288-
_ => new() { Report = true, },
320+
_ => new() { IncompatibleType = true, },
289321
};
290322
}
291323

src/Immediate.Validations.CodeFixes/AddAdditionalValidationsCodeRefactoringProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ TypeDeclarationSyntax typeDeclarationSyntax
5959
]))
6060
.WithParameterList(
6161
ParameterList(
62-
SeparatedList(new ParameterSyntax[]
63-
{
62+
SeparatedList(
63+
[
6464
Parameter(Identifier("errors"))
6565
.WithType(IdentifierName("ValidationResult")),
6666
Parameter(Identifier("target"))
6767
.WithType(IdentifierName(typeDeclarationSyntax.Identifier)),
68-
})))
68+
])))
6969
.WithBody(
7070
Block())
7171
.WithAdditionalAnnotations(Formatter.Annotation);

src/Immediate.Validations.Generators/Models.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public sealed record ValidationTargetProperty
2525
public required string TypeFullName { get; init; }
2626
public required bool IsReferenceType { get; init; }
2727
public required bool IsNullable { get; init; }
28+
public required bool ValidateNotNull { get; init; }
2829
public required bool IsValidationProperty { get; init; }
2930
public required string? ValidationTypeFullName { get; init; }
3031
public required ValidationTargetProperty? CollectionPropertyDetails { get; init; }

src/Immediate.Validations.Generators/Templates/Validations.sbntxt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ partial {{ class.type }} {{ class.name }}
119119
{{~ if p.is_reference_type || p.is_nullable ~}}
120120
if (target is not { } t)
121121
{
122-
{{~ if !p.is_nullable ~}}
122+
{{~ if p.validate_not_null ~}}
123123
errors.Add(
124124
$"{{ get_prop_name(p.property_name, depth) }}",
125125
$"'{{ get_prop_name(p.name, depth) }}' must not be null."

src/Immediate.Validations.Generators/ValidateTargetTransformer.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ ImmutableArray<AttributeData> attributes
190190
|| attributes.Any(a => a.AttributeClass.IsAllowNullAttribute()))
191191
: propertyType.IsNullableType();
192192

193-
var baseType = !isReferenceType && isNullable
193+
var validateNotNull = !isNullable || attributes.Any(a => a.AttributeClass.IsNotNullAttribute());
194+
195+
var baseType = propertyType.IsNullableType()
194196
? ((INamedTypeSymbol)propertyType).TypeArguments[0]
195197
: propertyType;
196198

@@ -261,7 +263,8 @@ ImmutableArray<AttributeData> attributes
261263
};
262264

263265
if (
264-
(isNullable || !isReferenceType)
266+
(!validateNotNull
267+
|| (!isReferenceType && !propertyType.IsNullableType()))
265268
&& !isValidationProperty
266269
&& collectionPropertyDetails is null
267270
&& validations is []
@@ -277,6 +280,7 @@ ImmutableArray<AttributeData> attributes
277280
TypeFullName = propertyType.ToDisplayString(s_fullyQualifiedPlusNullable),
278281
IsReferenceType = isReferenceType,
279282
IsNullable = isNullable,
283+
ValidateNotNull = validateNotNull,
280284

281285
IsValidationProperty = isValidationProperty,
282286
ValidationTypeFullName = isValidationProperty
@@ -303,7 +307,7 @@ AttributeData attribute
303307
_token.ThrowIfCancellationRequested();
304308

305309
var @class = attribute.AttributeClass;
306-
if (!@class.ImplementsValidatorAttribute())
310+
if (!@class.ImplementsValidatorAttribute() || @class.IsNotNullAttribute())
307311
return null;
308312

309313
_token.ThrowIfCancellationRequested();

src/Immediate.Validations.Shared/Validators/NotNullAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ public sealed class NotNullAttribute : ValidatorAttribute
1717
/// <returns>
1818
/// <see langword="true" /> if the property is valid; <see langword="false" /> otherwise.
1919
/// </returns>
20-
public static bool ValidateProperty<T>(T value)
21-
where T : class => value is not null;
20+
public static bool ValidateProperty<T>(T value) =>
21+
value is not null;
2222

2323
/// <summary>
2424
/// The default message template when the property is invalid.

0 commit comments

Comments
 (0)