Skip to content

Commit

Permalink
REFL024 Prefer null over empty array. Fix #18.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanLarsson committed Sep 25, 2018
1 parent b4f9499 commit 8945c88
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,9 @@ Analyzers checking System.Reflection
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL023.md">REFL023</a></td>
<td>The type does not implement the interface.</td>
</tr>
<tr>
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL024.md">REFL024</a></td>
<td>Prefer null over empty array.</td>
</tr>
<table>
<!-- end generated table -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace ReflectionAnalyzers.Tests.REFL024PreferNullOverEmptyArrayTests
{
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;
using ReflectionAnalyzers.Codefixes;

public class CodeFix
{
private static readonly DiagnosticAnalyzer Analyzer = new InvokeAnalyzer();
private static readonly CodeFixProvider Fix = new PreferNullFix();
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(REFL024PreferNullOverEmptyArray.Descriptor);

[TestCase("Array.Empty<object>()")]
[TestCase("new object[0]")]
[TestCase("new object[0] { }")]
[TestCase("new object[] { }")]
public void MemberInfoInvoke(string emptyArray)
{
var code = @"
namespace RoslynSandbox
{
using System;
using System.Reflection;
public class Foo
{
public Foo(MethodInfo member)
{
_ = member.Invoke(null, Array.Empty<object>());
}
}
}".AssertReplace("Array.Empty<object>()", emptyArray);

var fixedCode = @"
namespace RoslynSandbox
{
using System;
using System.Reflection;
public class Foo
{
public Foo(MethodInfo member)
{
_ = member.Invoke(null, null);
}
}
}";

AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, code, fixedCode);
}
}
}
1 change: 1 addition & 0 deletions ReflectionAnalyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
documentation\REFL020.md = documentation\REFL020.md
documentation\REFL022.md = documentation\REFL022.md
documentation\REFL023.md = documentation\REFL023.md
documentation\REFL024.md = documentation\REFL024.md
RELEASE_NOTES.md = RELEASE_NOTES.md
EndProjectSection
EndProject
Expand Down
38 changes: 38 additions & 0 deletions ReflectionAnalyzers/Codefixes/PreferNullFix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace ReflectionAnalyzers.Codefixes
{
using System.Collections.Immutable;
using System.Composition;
using System.Threading.Tasks;
using Gu.Roslyn.CodeFixExtensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferNullFix))]
[Shared]
internal class PreferNullFix : DocumentEditorCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(
REFL024PreferNullOverEmptyArray.DiagnosticId);

protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context)
{
var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
.ConfigureAwait(false);
foreach (var diagnostic in context.Diagnostics)
{
if (syntaxRoot.TryFindNode(diagnostic, out ArgumentSyntax argument))
{
context.RegisterCodeFix(
"Prefer null.",
(editor, _) => editor.ReplaceNode(
argument.Expression,
x => SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)),
nameof(PreferNullFix),
diagnostic);
}
}
}
}
}
45 changes: 43 additions & 2 deletions ReflectionAnalyzers/NodeAnalzers/InvokeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ internal class InvokeAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
REFL002InvokeDiscardReturnValue.Descriptor);
REFL002InvokeDiscardReturnValue.Descriptor,
REFL024PreferNullOverEmptyArray.Descriptor);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
Expand All @@ -28,8 +29,48 @@ private static void Handle(SyntaxNodeAnalysisContext context)
context.Node is InvocationExpressionSyntax invocation &&
invocation.TryGetMethodName(out var name) &&
name == "Invoke" &&
context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var target))
context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var invoke) &&
invoke.ContainingType.IsAssignableTo(KnownSymbol.MemberInfo, context.Compilation))
{
if (invoke.TryFindParameter("parameters", out var parameter) &&
invocation.TryFindArgument(parameter, out var paramsArg) &&
IsEmptyArray(paramsArg, context))
{
context.ReportDiagnostic(Diagnostic.Create(REFL024PreferNullOverEmptyArray.Descriptor, paramsArg.GetLocation()));
}
}
}

private static bool IsEmptyArray(ArgumentSyntax argument, SyntaxNodeAnalysisContext context)
{
switch (argument.Expression)
{
case InvocationExpressionSyntax invocation when context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var symbol) &&
symbol == KnownSymbol.Array.Empty:
return true;
case ArrayCreationExpressionSyntax arrayCreation:
if (arrayCreation.Type is ArrayTypeSyntax arrayType)
{
foreach (var rankSpecifier in arrayType.RankSpecifiers)
{
foreach (var size in rankSpecifier.Sizes)
{
if (size is LiteralExpressionSyntax literal &&
literal.Token.ValueText != "0")
{
return false;
}
}
}

var initializer = arrayCreation.Initializer;
return initializer == null ||
initializer.Expressions.Count == 0;
}

return false;
default:
return false;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ internal static class REFL023TypeDoesNotImplementInterface
description: "The type does not implement the interface.",
helpLinkUri: HelpLink.ForId(DiagnosticId));
}
}
}
19 changes: 19 additions & 0 deletions ReflectionAnalyzers/REFL024PreferNullOverEmptyArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ReflectionAnalyzers
{
using Microsoft.CodeAnalysis;

internal static class REFL024PreferNullOverEmptyArray
{
public const string DiagnosticId = "REFL024";

internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
id: DiagnosticId,
title: "Prefer null over empty array.",
messageFormat: "Prefer null over empty array.",
category: AnalyzerCategory.SystemReflection,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Prefer null over empty array.",
helpLinkUri: HelpLink.ForId(DiagnosticId));
}
}
67 changes: 67 additions & 0 deletions documentation/REFL024.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# REFL024
## Prefer null over empty array.

<!-- start generated table -->
<table>
<tr>
<td>CheckId</td>
<td>REFL024</td>
</tr>
<tr>
<td>Severity</td>
<td>Warning</td>
</tr>
<tr>
<td>Enabled</td>
<td>true</td>
</tr>
<tr>
<td>Category</td>
<td>ReflectionAnalyzers.SystemReflection</td>
</tr>
<tr>
<td>Code</td>
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/blob/master/ReflectionAnalyzers/NodeAnalzers/InvokeAnalyzer.cs">InvokeAnalyzer</a></td>
</tr>
</table>
<!-- end generated table -->

## Description

Prefer null over empty array.

## Motivation

ADD MOTIVATION HERE

## How to fix violations

ADD HOW TO FIX VIOLATIONS HERE

<!-- start generated config severity -->
## Configure severity

### Via ruleset file.

Configure the severity per project, for more info see [MSDN](https://msdn.microsoft.com/en-us/library/dd264949.aspx).

### Via #pragma directive.
```C#
#pragma warning disable REFL024 // Prefer null over empty array.
Code violating the rule here
#pragma warning restore REFL024 // Prefer null over empty array.
```

Or put this at the top of the file to disable all instances.
```C#
#pragma warning disable REFL024 // Prefer null over empty array.
```

### Via attribute `[SuppressMessage]`.

```C#
[System.Diagnostics.CodeAnalysis.SuppressMessage("ReflectionAnalyzers.SystemReflection",
"REFL024:Prefer null over empty array.",
Justification = "Reason...")]
```
<!-- end generated config severity -->

0 comments on commit 8945c88

Please sign in to comment.