From 55e6f891427d5ab1da836c8a2048fbbe37c99985 Mon Sep 17 00:00:00 2001 From: "jeroen.vannevel" Date: Sun, 25 Feb 2018 01:16:45 +0000 Subject: [PATCH] Throw null diagnostic --- README.md | 1 + .../Tests/Exceptions/ThrowNullTests.cs | 60 +++++++++++++++++++ .../VSDiagnostics.Test.csproj | 1 + .../AsyncMethodWithVoidReturnTypeCodeFix.cs | 2 +- .../Exceptions/ThrowNull/ThrowNullAnalyzer.cs | 42 +++++++++++++ .../VSDiagnostics/Utilities/DiagnosticId.cs | 1 + .../VSDiagnostics/VSDiagnostics.csproj | 1 + .../VSDiagnosticsResources.Designer.cs | 18 ++++++ .../VSDiagnostics/VSDiagnosticsResources.resx | 6 ++ 9 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ThrowNullTests.cs create mode 100644 VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ThrowNull/ThrowNullAnalyzer.cs diff --git a/README.md b/README.md index 35e2353..1e2736f 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Currently these diagnostics are implemented: | Exceptions | VSD0058 | An exception is thrown from a finalizer method. | Exceptions | VSD0059 | An exception is thrown from a `GetHashCode()` method. | Exceptions | VSD0060 | An exception is thrown from an `Equals()` method. +| Exceptions | VSD0065 | A `null` object is attempted to get thrown. | General | VSD0013 | Changes an `as` expression to a cast. | General | VSD0014 | Changes a cast expression to `as`. | General | VSD0015 | A boolean expression comparing to `false` can be simplified. diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ThrowNullTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ThrowNullTests.cs new file mode 100644 index 0000000..07a59f0 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ThrowNullTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Exceptions.ThrowNull; + +namespace VSDiagnostics.Test.Tests.Exceptions +{ + [TestClass] + public class ThrowNullTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ThrowNullAnalyzer(); + + [TestMethod] + public void ThrowNull_ThrowsNull() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + void Method(string input) + { + throw null; + } + } + }"; + + VerifyDiagnostic(original, "Throwing null will always result in a runtime exception"); + } + + [TestMethod] + public void ThrowNull_DoesNotThrowNull() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + void Method(string input) + { + throw new Exception(); + } + } + }"; + + VerifyDiagnostic(original); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj index 238a32c..c8e7970 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj @@ -118,6 +118,7 @@ + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs index f3e84dc..3af654d 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs @@ -33,7 +33,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeTitle, + CodeAction.Create(VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeCodeFixTitle, x => ChangeReturnTypeAsync(context.Document, methodDeclaration, root, x), AsyncMethodWithVoidReturnTypeAnalyzer.Rule.Id), diagnostic); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ThrowNull/ThrowNullAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ThrowNull/ThrowNullAnalyzer.cs new file mode 100644 index 0000000..a3fe2d1 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ThrowNull/ThrowNullAnalyzer.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Exceptions.ThrowNull +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ThrowNullAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Error; + + private static readonly string Category = VSDiagnosticsResources.ExceptionsCategory; + private static readonly string Message = VSDiagnosticsResources.ThrowNullMessage; + private static readonly string Title = VSDiagnosticsResources.ThrowNullMessage; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.ThrowNull, Title, Message, Category, Severity, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ThrowStatement); + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var throwStatement = (ThrowStatementSyntax) context.Node; + + var throwValue = context.SemanticModel.GetConstantValue(throwStatement.Expression); + if (throwValue.HasValue && throwValue.Value == null) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, throwStatement.Expression.GetLocation())); + } + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs index c178bf6..4c7497a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs @@ -63,5 +63,6 @@ public static class DiagnosticId public const string TestMethodWithoutTestAttribute = "VSD0062"; public const string GetHashCodeRefersToMutableField = "VSD0063"; public const string AsyncMethodWithVoidReturnType = "VSD0064"; + public const string ThrowNull = "VSD0065"; } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj index 07273fd..f9102f6 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj @@ -55,6 +55,7 @@ + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.Designer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.Designer.cs index af9d9fd..3308f20 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.Designer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.Designer.cs @@ -160,6 +160,15 @@ internal static string AsyncMethodWithoutAsyncSuffixCodeFixTitle { } } + /// + /// Looks up a localized string similar to Use Task as return type. + /// + internal static string AsyncMethodWithVoidReturnTypeCodeFixTitle { + get { + return ResourceManager.GetString("AsyncMethodWithVoidReturnTypeCodeFixTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Method {0} is marked as async but has a void return type. /// @@ -1510,6 +1519,15 @@ internal static string TestsCategory { } } + /// + /// Looks up a localized string similar to Throwing null will always result in a runtime exception. + /// + internal static string ThrowNullMessage { + get { + return ResourceManager.GetString("ThrowNullMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Variable {0} can be cast using as/null.. /// diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.resx b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.resx index 9c73ec8..72ac241 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.resx +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnosticsResources.resx @@ -677,4 +677,10 @@ Async methods should return a Task to make them awaitable + + Use Task as return type + + + Throwing null will always result in a runtime exception + \ No newline at end of file