Skip to content

Commit 8945c88

Browse files
committed
REFL024 Prefer null over empty array. Fix #18.
1 parent b4f9499 commit 8945c88

File tree

9 files changed

+227
-3
lines changed

9 files changed

+227
-3
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,9 @@ Analyzers checking System.Reflection
8888
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL023.md">REFL023</a></td>
8989
<td>The type does not implement the interface.</td>
9090
</tr>
91+
<tr>
92+
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL024.md">REFL024</a></td>
93+
<td>Prefer null over empty array.</td>
94+
</tr>
9195
<table>
9296
<!-- end generated table -->
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace ReflectionAnalyzers.Tests.REFL024PreferNullOverEmptyArrayTests
2+
{
3+
using Gu.Roslyn.Asserts;
4+
using Microsoft.CodeAnalysis.CodeFixes;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
using NUnit.Framework;
7+
using ReflectionAnalyzers.Codefixes;
8+
9+
public class CodeFix
10+
{
11+
private static readonly DiagnosticAnalyzer Analyzer = new InvokeAnalyzer();
12+
private static readonly CodeFixProvider Fix = new PreferNullFix();
13+
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(REFL024PreferNullOverEmptyArray.Descriptor);
14+
15+
[TestCase("Array.Empty<object>()")]
16+
[TestCase("new object[0]")]
17+
[TestCase("new object[0] { }")]
18+
[TestCase("new object[] { }")]
19+
public void MemberInfoInvoke(string emptyArray)
20+
{
21+
var code = @"
22+
namespace RoslynSandbox
23+
{
24+
using System;
25+
using System.Reflection;
26+
27+
public class Foo
28+
{
29+
public Foo(MethodInfo member)
30+
{
31+
_ = member.Invoke(null, Array.Empty<object>());
32+
}
33+
}
34+
}".AssertReplace("Array.Empty<object>()", emptyArray);
35+
36+
var fixedCode = @"
37+
namespace RoslynSandbox
38+
{
39+
using System;
40+
using System.Reflection;
41+
42+
public class Foo
43+
{
44+
public Foo(MethodInfo member)
45+
{
46+
_ = member.Invoke(null, null);
47+
}
48+
}
49+
}";
50+
51+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, code, fixedCode);
52+
}
53+
}
54+
}

ReflectionAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
5252
documentation\REFL020.md = documentation\REFL020.md
5353
documentation\REFL022.md = documentation\REFL022.md
5454
documentation\REFL023.md = documentation\REFL023.md
55+
documentation\REFL024.md = documentation\REFL024.md
5556
RELEASE_NOTES.md = RELEASE_NOTES.md
5657
EndProjectSection
5758
EndProject
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace ReflectionAnalyzers.Codefixes
2+
{
3+
using System.Collections.Immutable;
4+
using System.Composition;
5+
using System.Threading.Tasks;
6+
using Gu.Roslyn.CodeFixExtensions;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CodeFixes;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
12+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferNullFix))]
13+
[Shared]
14+
internal class PreferNullFix : DocumentEditorCodeFixProvider
15+
{
16+
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(
17+
REFL024PreferNullOverEmptyArray.DiagnosticId);
18+
19+
protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context)
20+
{
21+
var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
22+
.ConfigureAwait(false);
23+
foreach (var diagnostic in context.Diagnostics)
24+
{
25+
if (syntaxRoot.TryFindNode(diagnostic, out ArgumentSyntax argument))
26+
{
27+
context.RegisterCodeFix(
28+
"Prefer null.",
29+
(editor, _) => editor.ReplaceNode(
30+
argument.Expression,
31+
x => SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)),
32+
nameof(PreferNullFix),
33+
diagnostic);
34+
}
35+
}
36+
}
37+
}
38+
}

ReflectionAnalyzers/NodeAnalzers/InvokeAnalyzer.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ internal class InvokeAnalyzer : DiagnosticAnalyzer
1212
{
1313
/// <inheritdoc/>
1414
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
15-
REFL002InvokeDiscardReturnValue.Descriptor);
15+
REFL002InvokeDiscardReturnValue.Descriptor,
16+
REFL024PreferNullOverEmptyArray.Descriptor);
1617

1718
/// <inheritdoc/>
1819
public override void Initialize(AnalysisContext context)
@@ -28,8 +29,48 @@ private static void Handle(SyntaxNodeAnalysisContext context)
2829
context.Node is InvocationExpressionSyntax invocation &&
2930
invocation.TryGetMethodName(out var name) &&
3031
name == "Invoke" &&
31-
context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var target))
32+
context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var invoke) &&
33+
invoke.ContainingType.IsAssignableTo(KnownSymbol.MemberInfo, context.Compilation))
3234
{
35+
if (invoke.TryFindParameter("parameters", out var parameter) &&
36+
invocation.TryFindArgument(parameter, out var paramsArg) &&
37+
IsEmptyArray(paramsArg, context))
38+
{
39+
context.ReportDiagnostic(Diagnostic.Create(REFL024PreferNullOverEmptyArray.Descriptor, paramsArg.GetLocation()));
40+
}
41+
}
42+
}
43+
44+
private static bool IsEmptyArray(ArgumentSyntax argument, SyntaxNodeAnalysisContext context)
45+
{
46+
switch (argument.Expression)
47+
{
48+
case InvocationExpressionSyntax invocation when context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var symbol) &&
49+
symbol == KnownSymbol.Array.Empty:
50+
return true;
51+
case ArrayCreationExpressionSyntax arrayCreation:
52+
if (arrayCreation.Type is ArrayTypeSyntax arrayType)
53+
{
54+
foreach (var rankSpecifier in arrayType.RankSpecifiers)
55+
{
56+
foreach (var size in rankSpecifier.Sizes)
57+
{
58+
if (size is LiteralExpressionSyntax literal &&
59+
literal.Token.ValueText != "0")
60+
{
61+
return false;
62+
}
63+
}
64+
}
65+
66+
var initializer = arrayCreation.Initializer;
67+
return initializer == null ||
68+
initializer.Expressions.Count == 0;
69+
}
70+
71+
return false;
72+
default:
73+
return false;
3374
}
3475
}
3576
}

ReflectionAnalyzers/REFL023TypeDoesNotImplementInterface.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ internal static class REFL023TypeDoesNotImplementInterface
1616
description: "The type does not implement the interface.",
1717
helpLinkUri: HelpLink.ForId(DiagnosticId));
1818
}
19-
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace ReflectionAnalyzers
2+
{
3+
using Microsoft.CodeAnalysis;
4+
5+
internal static class REFL024PreferNullOverEmptyArray
6+
{
7+
public const string DiagnosticId = "REFL024";
8+
9+
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
10+
id: DiagnosticId,
11+
title: "Prefer null over empty array.",
12+
messageFormat: "Prefer null over empty array.",
13+
category: AnalyzerCategory.SystemReflection,
14+
defaultSeverity: DiagnosticSeverity.Warning,
15+
isEnabledByDefault: true,
16+
description: "Prefer null over empty array.",
17+
helpLinkUri: HelpLink.ForId(DiagnosticId));
18+
}
19+
}

documentation/REFL024.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# REFL024
2+
## Prefer null over empty array.
3+
4+
<!-- start generated table -->
5+
<table>
6+
<tr>
7+
<td>CheckId</td>
8+
<td>REFL024</td>
9+
</tr>
10+
<tr>
11+
<td>Severity</td>
12+
<td>Warning</td>
13+
</tr>
14+
<tr>
15+
<td>Enabled</td>
16+
<td>true</td>
17+
</tr>
18+
<tr>
19+
<td>Category</td>
20+
<td>ReflectionAnalyzers.SystemReflection</td>
21+
</tr>
22+
<tr>
23+
<td>Code</td>
24+
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/blob/master/ReflectionAnalyzers/NodeAnalzers/InvokeAnalyzer.cs">InvokeAnalyzer</a></td>
25+
</tr>
26+
</table>
27+
<!-- end generated table -->
28+
29+
## Description
30+
31+
Prefer null over empty array.
32+
33+
## Motivation
34+
35+
ADD MOTIVATION HERE
36+
37+
## How to fix violations
38+
39+
ADD HOW TO FIX VIOLATIONS HERE
40+
41+
<!-- start generated config severity -->
42+
## Configure severity
43+
44+
### Via ruleset file.
45+
46+
Configure the severity per project, for more info see [MSDN](https://msdn.microsoft.com/en-us/library/dd264949.aspx).
47+
48+
### Via #pragma directive.
49+
```C#
50+
#pragma warning disable REFL024 // Prefer null over empty array.
51+
Code violating the rule here
52+
#pragma warning restore REFL024 // Prefer null over empty array.
53+
```
54+
55+
Or put this at the top of the file to disable all instances.
56+
```C#
57+
#pragma warning disable REFL024 // Prefer null over empty array.
58+
```
59+
60+
### Via attribute `[SuppressMessage]`.
61+
62+
```C#
63+
[System.Diagnostics.CodeAnalysis.SuppressMessage("ReflectionAnalyzers.SystemReflection",
64+
"REFL024:Prefer null over empty array.",
65+
Justification = "Reason...")]
66+
```
67+
<!-- end generated config severity -->

0 commit comments

Comments
 (0)