Skip to content

Commit

Permalink
Merge pull request #92 from manfred-brands/CodeFixForIsDefaultT
Browse files Browse the repository at this point in the history
Add support for default(T)
  • Loading branch information
AArnott committed Jul 8, 2024
2 parents 948308b + f915dee commit 5db1dd3
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/CSharpIsNullAnalyzer.CodeFixes/IsFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private static Task<Document> ReplaceBinaryExpressionWithIsExpressionOrPattern<T
where T : ExpressionOrPatternSyntax
{
T expressionWithTrivia = expressionOrPattern.WithTriviaFrom(expr.Right);
ExpressionSyntax changedExpression = expr.Right is LiteralExpressionSyntax { RawKind: (int)SyntaxKind.NullLiteralExpression or (int)SyntaxKind.DefaultLiteralExpression }
ExpressionSyntax changedExpression = expr.Right is LiteralExpressionSyntax { RawKind: (int)SyntaxKind.NullLiteralExpression or (int)SyntaxKind.DefaultLiteralExpression } or DefaultExpressionSyntax
? create(expr.Left, expressionWithTrivia)
: create(expr.Right.WithoutTrailingTrivia().WithTrailingTrivia(Space), expressionWithTrivia);
SyntaxNode updatedSyntaxRoot = syntaxRoot.ReplaceNode(expr, changedExpression);
Expand Down
6 changes: 3 additions & 3 deletions src/CSharpIsNullAnalyzer/CSIsNull001.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ public override void Initialize(AnalysisContext context)
if (ctxt.Operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals } binaryOp)
{
Location? location = null;
if (binaryOp.RightOperand is IConversionOperation { ConstantValue: { HasValue: true, Value: null } })
if (binaryOp.RightOperand.IsNullCheck())
{
location = binaryOp.RightOperand.Syntax.GetLocation();
if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Right: { } right })
{
location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(operatorLocation.SpanStart, right.Span.End - operatorLocation.SpanStart));
}
}
else if (binaryOp.LeftOperand is IConversionOperation { ConstantValue: { HasValue: true, Value: null } })
else if (binaryOp.LeftOperand.IsNullCheck())
{
location = binaryOp.LeftOperand.Syntax.GetLocation();
if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Left: { } left })
Expand All @@ -76,7 +76,7 @@ public override void Initialize(AnalysisContext context)
}
}
if (location is object && !binaryOp.IsWithinExpressionTree(linqExpressionType))
if (location is not null && !binaryOp.IsWithinExpressionTree(linqExpressionType))
{
ctxt.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
}
Expand Down
6 changes: 3 additions & 3 deletions src/CSharpIsNullAnalyzer/CSIsNull002.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ public override void Initialize(AnalysisContext context)
if (ctxt.Operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.NotEquals } binaryOp)
{
Location? location = null;
if (binaryOp.RightOperand is IConversionOperation { ConstantValue: { HasValue: true, Value: null } } or ILiteralOperation { ConstantValue: { HasValue: true, Value: null } })
if (binaryOp.RightOperand.IsNullCheck())
{
location = binaryOp.RightOperand.Syntax.GetLocation();
if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Right: { } right })
{
location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(operatorLocation.SpanStart, right.Span.End - operatorLocation.SpanStart));
}
}
else if (binaryOp.LeftOperand is IConversionOperation { ConstantValue: { HasValue: true, Value: null } } or ILiteralOperation { ConstantValue: { HasValue: true, Value: null } })
else if (binaryOp.LeftOperand.IsNullCheck())
{
location = binaryOp.LeftOperand.Syntax.GetLocation();
if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Left: { } left })
Expand All @@ -81,7 +81,7 @@ public override void Initialize(AnalysisContext context)
}
}
if (location is object)
if (location is not null)
{
ImmutableDictionary<string, string?> properties = ImmutableDictionary<string, string?>.Empty;
if (!binaryOp.IsWithinExpressionTree(linqExpressionType))
Expand Down
9 changes: 9 additions & 0 deletions src/CSharpIsNullAnalyzer/IOperationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace CSharpIsNullAnalyzer;

Expand Down Expand Up @@ -60,4 +61,12 @@ internal static bool IsWithinExpressionTree(this IOperation operation, [NotNullW
return default;
}
}

/// <summary>
/// Checks if this <paramref name="operation"/> is a null check.
/// </summary>
/// <param name="operation">The operation to check.</param>
/// <returns><see langword="true"/> if this operation checks for null, <see langword="false"/> otherwise.</returns>
internal static bool IsNullCheck(this IOperation operation) =>
operation is (IConversionOperation or ILiteralOperation or IDefaultValueOperation) and { ConstantValue: { HasValue: true, Value: null } };
}
56 changes: 56 additions & 0 deletions test/CSharpIsNullAnalyzer.Tests/CSIsNull001Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,62 @@ void Method(object o)
await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
}

[Fact]
public async Task EqualsDefaultKeywordInIfExpression_ProducesDiagnostic()
{
string source = @"
class Test
{
void Method(string s)
{
if (s [|== default(string)|])
{
}
}
}";

string fixedSource = @"
class Test
{
void Method(string s)
{
if (s is null)
{
}
}
}";

await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
}

[Fact]
public async Task EqualsDefaultTInIfExpression_ProducesDiagnostic()
{
string source = @"
class Test
{
void Method(Test t)
{
if (t [|== default(Test)|])
{
}
}
}";

string fixedSource = @"
class Test
{
void Method(Test t)
{
if (t is null)
{
}
}
}";

await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
}

[Fact]
public async Task EqualsDefaultValueType_ProducesNoDiagnostic()
{
Expand Down
80 changes: 80 additions & 0 deletions test/CSharpIsNullAnalyzer.Tests/CSIsNull002Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,86 @@ void Method(object o)
await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
}

[Fact]
public async Task NotEqualsDefaultKeywordInIfExpression_ProducesDiagnostic()
{
string source = @"
class Test
{
void Method(string s)
{
if (s [|!= default(string)|])
{
}
}
}";

string fixedSource1 = @"
class Test
{
void Method(string s)
{
if (s is object)
{
}
}
}";

string fixedSource2 = @"
class Test
{
void Method(string s)
{
if (s is not null)
{
}
}
}";

await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
}

[Fact]
public async Task NotEqualsDefaultTInIfExpression_ProducesDiagnostic()
{
string source = @"
class Test
{
void Method(Test t)
{
if (t [|!= default(Test)|])
{
}
}
}";

string fixedSource1 = @"
class Test
{
void Method(Test t)
{
if (t is object)
{
}
}
}";

string fixedSource2 = @"
class Test
{
void Method(Test t)
{
if (t is not null)
{
}
}
}";

await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
}

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

0 comments on commit 5db1dd3

Please sign in to comment.