diff --git a/README.md b/README.md index 3dfef54..1e2736f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -# VSDiagnostics -A collection of code-quality analyzers based on the new Roslyn platform. This project aims to ensure code-quality as you type it in your editor rather than having to do this as a separate build-step. Likewise it also tries to help avoid some common pitfalls. +# VSDiagnostics +A collection of code-quality analyzers based on the Roslyn compiler platform. This project aims to ensure code-quality as you type it in your editor rather than having to do this as a separate build-step. +By performing static analysis while you're writing code, certain convention violations and hidden pitfalls can be avoided as early in the process as possible. @@ -24,47 +25,68 @@ Currently these diagnostics are implemented: | Category | Name | Description |:-:|:-:|:-: -| Async | VSD0001 | Asynchronous methods should end with -Async. -| Async | VSD0041 | A non-async, non-Task method should not end with -Async. -| Attributes | VSD0002 | Attributes with empty argument lists can have the argument list removed. -| Attributes | VSD0003 | Gives an enum the [Flags] attribute. +| Arithmetic | VSD0045 | The operands of a divisive expression are both integers and result in an implicit rounding. +| Async | VSD0001 | Asynchronous methods should end with the -Async suffix. +| Async | VSD0041 | A non-`async`, non-`Task` method should not end with -Async. +| Async | VSD0064 | Async methods should return a `Task` to make them awaitable. +| Attributes | VSD0002 | An attribute should not have an empty argument list. +| Attributes | VSD0003 | Gives an enum the `[Flags]` attribute. | Attributes | VSD0004 | A `[Flags]` enum its values are not explicit powers of 2 -| Attributes | VSD0039 | A `[Flags]` enum its values are not explicit powers of 2 and does not fit in the specified enum type. -| Attributes | VSD0005 | Complains if the [Obsolete] attribute is used without an explicit reason. -| Attributes | VSD0006 | The `OnPropertyChanged()` method can automatically get the caller member name. -| Exceptions | VSD0007 | `ArgumentException` and its subclasses should use `nameof()` when they refer to a method parameter. -| Exceptions | VSD0008 | Guards against catching a NullReferenceException. -| Exceptions | VSD0009 | Guards against using an `ArgumentException` without specifying which argument. +| Attributes | VSD0039 | A `[Flags]` enum its values are not explicit powers of 2 and its values dont fit in the specified enum type. +| Attributes | VSD0005 | The `[Obsolete]` attribute doesn't have a reason. +| Attributes | VSD0006 | `OnPropertyChanged()` can use the `[CallerMemberName]` attribute to automatically pass the property name. +| Exceptions | VSD0007 | An `ArgumentException` should use `nameof()` to refer to a variable. +| Exceptions | VSD0008 | Verifies no `NullReferenceException` is caught. +| Exceptions | VSD0009 | Verifies whether an `ArgumentException` is thrown with a message. | Exceptions | VSD0010 | Warns when an exception catch block is empty. | Exceptions | VSD0011 | Warns when an exception is rethrown in a way that it loses the stacktrace. -| Exceptions | VSD0012 | Guards against using a catch-all clause. -| General | VSD0013 | Allows you to change as statements to cast statements. -| General | VSD0014 | Allows you to change cast statements to as statements. -| General | VSD0015 | A boolean expression doesn't have to be compared to `false`. -| General | VSD0016 | A boolean expression doesn't have to be compared to `true`. -| General | VSD0017 | The conditional operator shouldn't return redundant `true` and `false` literals. -| General | VSD0018 | The conditional operator shouldn't return redundant `false` and `true` literals. -| General | VSD0019 | Complains about `if` statements of the form `if (statement) { /* body */ }`, where "statement" always evaluates to `false`. -| General | VSD0020 | Complains about `if` statements of the form `if (statement) { /* body */ }`, where "statement" always evaluates to `true`. +| Exceptions | VSD0012 | Verifies whether a try-catch block does not defer all exception handling to a single `Exception` clause. +| Exceptions | VSD0052 | An exception is thrown from an implicit operator. +| Exceptions | VSD0053 | An exception is thrown from a property getter. +| Exceptions | VSD0054 | An exception is thrown from a static constructor. +| Exceptions | VSD0055 | An exception is thrown from a finally block. +| Exceptions | VSD0056 | An exception is thrown from an equality operator. +| Exceptions | VSD0057 | An exception is thrown from a `Dispose` method. +| 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. +| General | VSD0016 | A boolean expression comparing to `true` can be simplified. +| General | VSD0017 | The conditional operator shouldn't return redundant default options. +| General | VSD0018 | The conditional operator shouldn't return redundant inverted default options. +| General | VSD0019 | The condition is a constant (false) and thus unnecessary. +| General | VSD0020 | The condition is a constant (true) and thus unnecessary. | General | VSD0021 | Inserts the default access modifier for a declaration. | General | VSD0022 | Detects usage of the `goto` keyword. -| General | VSD0023 | Changes one-liner `if` and `else` statements to be surrounded in a block. -| General | VSD0024 | Loop blocks should use braces to denote start and end. +| General | VSD0023 | Requires braces for `if`, `else`, `for`, `foreach`, `while`, `do`, `using`, `lock`, `fixed` and `switch` constructs. | General | VSD0025 | Implements the most common configuration of naming conventions. | General | VSD0026 | A `public`, `internal` or `protected internal` non-`const`, non-`readonly` field should be used as a property. | General | VSD0027 | Changes `Nullable` to `T?`. | General | VSD0028 | Use the `nameof()` operator in conjunction with `OnPropertyChanged`. | General | VSD0029 | Simplify the expression using an expression-bodied member. -| General | VSD0030 | Warns about using a redundant default constructor. -| General | VSD0031 | A conversion can be done using `as` + a `null` comparison. -| General | VSD0032 | Use `var` instead of an explicit type. -| General | VSD0033 | Use the built-in type aliases instead of the concrete type. -| Strings | VSD0034 | Use `string.Empty` instead of `""`. -| Strings | VSD0035 | Adjusts the placeholders in `string.Format()` calls to be in numerical order. -| Strings | VSD0042 | A `string.Format()` call lacks arguments and will cause a runtime exception -| Structs | VSD0036 | Warns when a struct attempts to assign 'this' to a new instance of the struct. -| Tests | VSD0037 | Test methods do not need to use the "Test" suffic. -| Tests | VSD0038 | Change the access modifier to `public` for all methods annotated as test. Supports NUnit, MSTest and xUnit.net. +| General | VSD0030 | A constructor is the same as a default constructor and can be removed. +| General | VSD0031 | Use `as`/`null` instead of `is`/`as`. +| General | VSD0032 | Use `var` instead of the explicit type. +| General | VSD0033 | Use the built-in type alias instead of the concrete type. +| General | VSD0044 | Add cases for missing enum members. +| General | VSD0043 | An instance of type `System.Random` is created in a loop. +| General | VSD0046 | `Equals()` and `GetHashCode()` must be implemented together. +| General | VSD0047 | Implement elementary methods for a type used in a collection. +| General | VSD0048 | A property with a private setter can become a read-only property instead. +| General | VSD0049 | A `switch` is missing a `default` label.\ +| General | VSD0052 | Implement `Equals()` and `GetHashCode()` using existing fields and properties. +| General | VSD0063 | A `GetHashCode` implementation refers to a mutable field. +| Strings | VSD0034 | Replaces an empty string literal with the more expressive `string.Empty`. +| Strings | VSD0035 | Orders the arguments of a `string.Format()` call in ascending order according to index. +| Strings | VSD0042 | A `string.Format()` call lacks arguments and will cause a runtime exception. +| Structs | VSD0036 | Warns when a struct replaces `this` with a new instance. +| Structs | VSD0050 | Structs should implement `Equals()`, `GetHashCode()`, and `ToString()`. +| Tests | VSD0037 | A test method should not end with -Test. +| Tests | VSD0038 | Verifies whether a test method has the `public` modifier. +| Tests | VSD0062 | A method might be missing a test attribute. ## How do I use this? @@ -72,7 +94,7 @@ Simply head over to [NuGet](https://www.nuget.org/packages/VSDiagnostics/) and i ## Can I request diagnostics? -Yes, you can! Create an issue and we'll take a look at your proposal. +Yes, you can! Create an issue and we'll take a look at your proposal. ## What if I don't like a diagnostic? @@ -106,4 +128,4 @@ Release 2.0.0 will come with a website where we document every diagnostic includ ## How can I get in contact? -You're always free to open an issue but if you would like something more direct you can drop by in [the StackExchange chat channel](http://chat.stackexchange.com/rooms/26639/vsdiagnostics) where the main contributors reside. +You're always free to open an issue but if you would like something more direct you can drop by in [the StackExchange chat channel](http://chat.stackexchange.com/rooms/26639/vsdiagnostics) where the main contributors reside or send an email to jer_vannevel@outlook.com. diff --git a/VSDiagnostics/VSDiagnostics.sln b/VSDiagnostics/VSDiagnostics.sln index beec2dc..3e9980c 100644 --- a/VSDiagnostics/VSDiagnostics.sln +++ b/VSDiagnostics/VSDiagnostics.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VSDiagnostics", "VSDiagnostics\VSDiagnostics\VSDiagnostics.csproj", "{C7BF0415-8F01-4FF1-8EC3-71695197EF5B}" EndProject @@ -31,4 +31,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {43378104-E964-4E48-B654-B07D1FBFD96F} + EndGlobalSection EndGlobal diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Arithmetic/DivideIntegerByIntegerTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Arithmetic/DivideIntegerByIntegerTests.cs new file mode 100644 index 0000000..7c5930b --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Arithmetic/DivideIntegerByIntegerTests.cs @@ -0,0 +1,425 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Arithmetic.DivideIntegerByInteger; + +namespace VSDiagnostics.Test.Tests.Arithmetic +{ + [TestClass] + public class DivideIntegerByIntegerTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new DivideIntegerByIntegerAnalyzer(); + + [TestMethod] + public void DivideIntegerByInteger_TwoIntegers() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + int result = 5 / 6; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "5 / 6")); + } + + [TestMethod] + public void DivideIntegerByInteger_IntegerAndDouble() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5 / 6.0; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_DoubleAndInteger() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5.0 / 6; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_DoubleAndDouble() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5.0 / 6.0; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_ThreeIntegerOperands() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5 / 6 / 2; + } + } + }"; + + VerifyDiagnostic(original, + string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "5 / 6 / 2"), + string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "5 / 6")); + } + + [TestMethod] + public void DivideIntegerByInteger_ThreeIntegerOperands_TwoSubsequentIntegers_IntegerDivisionEvaluatedFirst() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5 / 6 / 2.0; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "5 / 6")); + } + + /// + /// This scenario is less important because 5.0 / 6 is evaluated first and results in a double. + /// In practice there will be no integer division here. + /// + [TestMethod] + public void DivideIntegerByInteger_ThreeIntegerOperands_TwoSubsequentIntegers_IntegerDivisionEvaluatedLast() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5.0 / 6 / 2; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_ThreeIntegerOperands_NoSubsequentIntegerOperands() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + double result = 5 / 6.0 / 2; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_DynamicOperand() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + dynamic x = 5; + double result = x / 3; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void DivideIntegerByInteger_TwoIntegers_OneAsVariable() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + var x = 5; + double result = x / 3; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / 3")); + } + + [TestMethod] + public void DivideIntegerByInteger_TwoIntegers_TwoAsVariables() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + var x = 5; + var y = 6; + double result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_TwoIntegers_MethodReference() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + var y = 6; + double result = IntMethod() / y; + } + + int IntMethod() + { + return 5; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "IntMethod() / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_TwoIntegers_InsideExpression() + { + var original = @" + using System; + + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + Console.WriteLine(5 / 6); + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "5 / 6")); + } + + [TestMethod] + public void DivideIntegerByInteger_ShortAndShort() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + short x = 5; + short y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_LongAndLong() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + long x = 5; + long y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_LongAndInt() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + long x = 5; + int y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_ULongAndULong() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + ulong x = 5; + ulong y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_UIntAndUInt() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + uint x = 5; + uint y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_UShortAndUShort() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + ushort x = 5; + ushort y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_ByteAndByte() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + byte x = 5; + byte y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + + [TestMethod] + public void DivideIntegerByInteger_SByteAndSByte() + { + var original = @" + namespace ConsoleApplication1 + { + class MyClass + { + void Method() + { + sbyte x = 5; + sbyte y = 6; + var result = x / y; + } + } + }"; + + VerifyDiagnostic(original, string.Format(DivideIntegerByIntegerAnalyzer.Rule.MessageFormat.ToString(), "x / y")); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/AsyncMethodWithVoidReturnTypeTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/AsyncMethodWithVoidReturnTypeTests.cs new file mode 100644 index 0000000..b97d1c3 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/AsyncMethodWithVoidReturnTypeTests.cs @@ -0,0 +1,262 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Async.AsyncMethodWithVoidReturnType; + +namespace VSDiagnostics.Test.Tests.Async +{ + [TestClass] + public class AsyncMethodWithVoidReturnTypeTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new AsyncMethodWithVoidReturnTypeAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new AsyncMethodWithVoidReturnTypeCodeFix(); + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncAndTask() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async Task MyMethod() + { + await Task.Run(() => { }); + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_NoAsync() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + void MyMethod() + { + + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncAndTaskGeneric() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async Task MyMethod() + { + return 32; + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncAndEventHandlerArguments() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async void MyHandler(object o, EventArgs e) + { + await Task.Run(() => { }); + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncAndEventHandlerSubClassArguments() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async void MyHandler(object o, MyEventArgs e) + { + await Task.Run(() => { }); + } + } + + class MyEventArgs : EventArgs + { + + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncDelegate() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + public void MyMethod() + { + TestMethod(async () => await Task.Run(() => {})); + } + + public void TestMethod(Action callback) + { + callback(); + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncVoidAndArbitraryArguments() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async void MyHandler(object o, int e) + { + await Task.Run(() => { }); + } + } + }"; + + var result = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async Task MyHandler(object o, int e) + { + await Task.Run(() => { }); + } + } + }"; + + VerifyDiagnostic(original, "Method MyHandler is marked as async but has a void return type"); + VerifyFix(original, result); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithAsyncAndVoid() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async void MyMethod() + { + await Task.Run(() => { }); + } + } + }"; + + var result = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + class MyClass + { + async Task MyMethod() + { + await Task.Run(() => { }); + } + } + }"; + + VerifyDiagnostic(original, "Method MyMethod is marked as async but has a void return type"); + VerifyFix(original, result); + } + + [TestMethod] + public void AsyncMethodWithVoidReturnType_WithPartialMethod() + { + var original = @" + using System; + using System.Text; + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + partial class A + { + partial void OnSomethingHappened(); + } + + partial class A + { + async partial void OnSomethingHappened() + { + await Task.Run(() => { }); + } + } + }"; + + VerifyDiagnostic(original); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/SyncMethodWithAsyncSuffixTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/SyncMethodWithAsyncSuffixTests.cs index ab5248b..30871ff 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/SyncMethodWithAsyncSuffixTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Async/SyncMethodWithAsyncSuffixTests.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.Async.SyncMethodWithSyncSuffix; +using VSDiagnostics.Diagnostics.Async.SyncMethodWithAsyncSuffix; namespace VSDiagnostics.Test.Tests.Async { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListCSharpTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListTests.cs similarity index 89% rename from VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListCSharpTests.cs rename to VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListTests.cs index c3b0a66..5f5720b 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListCSharpTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListTests.cs @@ -7,7 +7,7 @@ namespace VSDiagnostics.Test.Tests.Attributes { [TestClass] - public class AttributeWithEmptyArgumentListCSharpTests : CSharpCodeFixVerifier + public class AttributeWithEmptyArgumentListTests : CSharpCodeFixVerifier { protected override DiagnosticAnalyzer DiagnosticAnalyzer => new AttributeWithEmptyArgumentListAnalyzer(); @@ -44,7 +44,7 @@ void Method(string input) } }"; - VerifyDiagnostic(original, AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString()); + VerifyDiagnostic(original, string.Format(AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString(), "Obsolete")); VerifyFix(original, result); } @@ -122,7 +122,7 @@ enum Foo } }"; - VerifyDiagnostic(original, AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString()); + VerifyDiagnostic(original, string.Format(AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString(), "Flags")); VerifyFix(original, result); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListVisualBasicTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListVisualBasicTests.cs deleted file mode 100644 index eb85d81..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/AttributeWithEmptyArgumentListVisualBasicTests.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.VisualBasic; -using VSDiagnostics.Diagnostics.Attributes.AttributeWithEmptyArgumentList; - -namespace VSDiagnostics.Test.Tests.Attributes -{ - [TestClass] - public class AttributeWithEmptyArgumentListVisualBasicTests : VisualBasicCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new AttributeWithEmptyArgumentListAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new AttributeWithEmptyArgumentListCodeFix(); - - [TestMethod] - public void AttributeWithEmptyArgumentList_AttributeWithEmptyArgumentList() - { - var original = @" -Imports System - -Module Module1 - - - Sub Foo() - - End Sub - -End Module"; - - var result = @" -Imports System - -Module Module1 - - - Sub Foo() - - End Sub - -End Module"; - - VerifyDiagnostic(original, AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void AttributeWithEmptyArgumentList_WithoutArgumentList() - { - var original = @" -Imports System - -Module Module1 - - - Sub Foo() - - End Sub - -End Module"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void AttributeWithEmptyArgumentList_WithArgumentList() - { - var original = @" -Imports System - -Module Module1 - - - Sub Foo() - - End Sub - -End Module"; - - VerifyDiagnostic(original); - } - - // make sure it works on other attributes besides [Obsolete] - [TestMethod] - public void AttributeWithEmptyArgumentList_AttributeWithEmptyArgumentList_FlagsAttribute() - { - var original = @" -Imports System - -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - var result = @" -Imports System - -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original, AttributeWithEmptyArgumentListAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - // make sure it works on other attributes besides [Obsolete] - [TestMethod] - public void AttributeWithEmptyArgumentList_WithoutArgumentList_FlagsAttribute() - { - var original = @" -Imports System - -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeCSharpTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeTests.cs similarity index 98% rename from VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeCSharpTests.cs rename to VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeTests.cs index 0b41ab3..3d2cc06 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeCSharpTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeTests.cs @@ -7,7 +7,7 @@ namespace VSDiagnostics.Test.Tests.Attributes { [TestClass] - public class EnumCanHaveFlagsAttributeCSharpTests : CSharpCodeFixVerifier + public class EnumCanHaveFlagsAttributeTests : CSharpCodeFixVerifier { protected override DiagnosticAnalyzer DiagnosticAnalyzer => new EnumCanHaveFlagsAttributeAnalyzer(); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeVisualBasicTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeVisualBasicTests.cs deleted file mode 100644 index 3094c25..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/EnumCanHaveFlagsAttributeVisualBasicTests.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.VisualBasic; -using VSDiagnostics.Diagnostics.Attributes.EnumCanHaveFlagsAttribute; - -namespace VSDiagnostics.Test.Tests.Attributes -{ - [TestClass] - public class EnumCanHaveFlagsAttributeVisualBasicTests : VisualBasicCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new EnumCanHaveFlagsAttributeAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new EnumCanHaveFlagsAttributeCodeFix(); - - [TestMethod] - public void EnumCanHaveFlagsAttribute_AddsFlagsAttribute() - { - var original = @" -Module Module1 - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - var result = @" -Imports System -Module Module1 - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original, EnumCanHaveFlagsAttributeAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void EnumCanHaveFlagsAttribute_AddsFlagsAttribute_OnlyAddsFlagsAttribute() - { - var original = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - var result = @" -Imports System -Module Module1 - - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original, EnumCanHaveFlagsAttributeAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void EnumCanHaveFlagsAttribute_EnumHasXmlDocComment_OnlyAddsFlagsAttribute() - { - var original = @" -Module Module1 - - ''' - ''' Doc comment for Foo... - ''' - Enum Foo - Bar - Baz - End Enum - -End Module"; - - var result = @" -Imports System -Module Module1 - - ''' - ''' Doc comment for Foo... - ''' - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original, EnumCanHaveFlagsAttributeAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void EnumCanHaveFlagsAttribute_InspectionDoesNotReturnWhenFlagsAlreadyApplied() - { - var original = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void EnumCanHaveFlagsAttribute_InspectionDoesNotReturnWhenFlagsAttributeAlreadyApplied() - { - var original = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void EnumCanHaveFlagsAttribute_InspectionDoesNotReturnWhenFlagsAlreadyAppliedAsChain() - { - var original = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(original); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonCSharpTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonTests.cs similarity index 97% rename from VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonCSharpTests.cs rename to VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonTests.cs index 5266f07..7447827 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonCSharpTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonTests.cs @@ -6,7 +6,7 @@ namespace VSDiagnostics.Test.Tests.Attributes { [TestClass] - public class ObsoleteAttributeWithoutReasonCSharpTests : CSharpDiagnosticVerifier + public class ObsoleteAttributeWithoutReasonTests : CSharpDiagnosticVerifier { protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ObsoleteAttributeWithoutReasonAnalyzer(); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonVisualBasicTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonVisualBasicTests.cs deleted file mode 100644 index 655b86b..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/ObsoleteAttributeWithoutReasonVisualBasicTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.VisualBasic; -using VSDiagnostics.Diagnostics.Attributes.ObsoleteAttributeWithoutReason; - -namespace VSDiagnostics.Test.Tests.Attributes -{ - [TestClass] - public class ObsoleteAttributeWithoutReasonVisualBasicTests : VisualBasicDiagnosticVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ObsoleteAttributeWithoutReasonAnalyzer(); - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteWithNullArgumentList() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test, ObsoleteAttributeWithoutReasonAnalyzer.Rule.MessageFormat.ToString()); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteAttributeWithoutReason() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test, ObsoleteAttributeWithoutReasonAnalyzer.Rule.MessageFormat.ToString()); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteWithEmptyArgumentList() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test, ObsoleteAttributeWithoutReasonAnalyzer.Rule.MessageFormat.ToString()); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteAttributeWithEmptyArgumentList() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test, ObsoleteAttributeWithoutReasonAnalyzer.Rule.MessageFormat.ToString()); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteWithArgument() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteAttributeWithArgument() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteWithArguments() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_WithObsoleteAttributeWithArguments() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test); - } - - [TestMethod] - public void ObsoleteAttributeWithoutReason_NonObsoleteAttribute() - { - var test = @" -Imports System -Module Module1 - - - Enum Foo - Bar - Baz - End Enum - -End Module"; - - VerifyDiagnostic(test); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/OnPropertyChangedWithoutCallerMemberNameTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/OnPropertyChangedWithoutCallerMemberNameTests.cs index f7c1cd7..85d6d56 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/OnPropertyChangedWithoutCallerMemberNameTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Attributes/OnPropertyChangedWithoutCallerMemberNameTests.cs @@ -454,5 +454,43 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName VerifyDiagnostic(original, OnPropertyChangedWithoutCallerMemberNameAnalyzer.Rule.MessageFormat.ToString()); VerifyFix(original, result); } + + [TestMethod] + public void OnPropertyChangedWithoutCallerMemberName_StructImplementsINotifyPropertyChanged() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + struct Foo : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +}"; + + var result = @" +using System; +using System.Runtime.CompilerServices; + +namespace ConsoleApplication1 +{ + struct Foo : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public void OnPropertyChanged([CallerMemberName] string propertyName = """") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +}"; + + VerifyDiagnostic(original, OnPropertyChangedWithoutCallerMemberNameAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ArgumentExceptionWithoutNameofOperatorTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ArgumentExceptionWithoutNameofOperatorTests.cs index 464c70b..ff37d74 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ArgumentExceptionWithoutNameofOperatorTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ArgumentExceptionWithoutNameofOperatorTests.cs @@ -434,5 +434,127 @@ public MyException(string message) : base(message) VerifyDiagnostic(original, string.Format(ArgumentExceptionWithoutNameofOperatorAnalyzer.Rule.MessageFormat.ToString(), "input")); VerifyFix(original, result); } + + [TestMethod] + public void ArgumentExceptionWithoutNameofOperator_PropertySetter() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty + { + get { return 5; } + set { throw new ArgumentException(""value""); } + } + } + }"; + + var result = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty + { + get { return 5; } + set { throw new ArgumentException(nameof(value)); } + } + } + }"; + + VerifyDiagnostic(original, string.Format(ArgumentExceptionWithoutNameofOperatorAnalyzer.Rule.MessageFormat.ToString(), "value")); + VerifyFix(original, result); + } + + [TestMethod] + public void ArgumentExceptionWithoutNameofOperator_PropertySetter_UsesNameofValue() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty + { + get { return 5; } + set { throw new ArgumentException(nameof(value)); } + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ArgumentExceptionWithoutNameofOperator_PropertyGetterOnly() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty + { + get { return 5; } + } + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ArgumentExceptionWithoutNameofOperator_PropertyExpressionBodied() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty => 5; + } + }"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ArgumentExceptionWithoutNameofOperator_PropertySetterDoesNotReferToValue() + { + var original = @" + using System; + using System.Text; + + namespace ConsoleApplication1 + { + class MyClass + { + public int MyProperty + { + get { return 5; } + set { throw new ArgumentNullException(""test""); } + } + } + }"; + + VerifyDiagnostic(original); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ExceptionThrownFromProhibitedContextTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ExceptionThrownFromProhibitedContextTests.cs new file mode 100644 index 0000000..ef2a0cd --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/ExceptionThrownFromProhibitedContextTests.cs @@ -0,0 +1,701 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Exceptions.ExceptionThrownFromProhibitedContext; + +namespace VSDiagnostics.Test.Tests.Exceptions +{ + [TestClass] + public class ExceptionThrownFromProhibitedContextTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ExceptionThrownFromProhibitedContextAnalyzer(); + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_ImplicitOperator_ToType() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static implicit operator MyClass(double d) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from implicit operator MyClass in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_ImplicitOperator_FromType() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static implicit operator double(MyClass d) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from implicit operator double in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_ImplicitOperator_ExplicitOperator() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static explicit operator double(MyClass d) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_PropertyGetter() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int MyProp + { + get + { + throw new ArgumentException(); + } + set + { + + } + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the getter of property MyProp"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_PropertyGetter_Setter() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int MyProp + { + get + { + return 5; + } + set + { + throw new ArgumentException(); + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_PropertyGetter_AutoProperty() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int MyProp { get; set; } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_PropertyGetter_ExpressionBodiedProperty() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int MyProp => 5; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_PropertyGetter_NoSetter() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int MyProp + { + get + { + throw new ArgumentException(); + } + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the getter of property MyProp"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_StaticConstructor_Class() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + static MyClass() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from MyClass its static constructor"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_StaticConstructor_Struct() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyStruct + { + static MyStruct() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from MyStruct its static constructor"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_StaticConstructor_InstanceConstructor() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + MyClass() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_FinallyBlock() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + void MyMethod() + { + try { } finally { throw new ArgumentException(); } + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from a finally block"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_FinallyBlock_NoException() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + void MyMethod() + { + try { } finally { } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_FinallyBlock_NoFinally() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + void MyMethod() + { + try { } catch { } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_EqualityOperator_EqualOperator() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static bool operator ==(double d, MyClass mc) + { + throw new ArgumentException(); + } + + public static bool operator !=(double d, MyClass mc) + { + return false; + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the == operator between double and MyClass in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_EqualityOperator_NotEqualOperator() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static bool operator ==(double d, MyClass mc) + { + return false; + } + + public static bool operator !=(double d, MyClass mc) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the != operator between double and MyClass in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_EqualityOperator_NoThrow() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public static bool operator ==(double d, MyClass mc) + { + return false; + } + + public static bool operator !=(double d, MyClass mc) + { + return false; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Dispose() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass : IDisposable + { + public void Dispose() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Dispose() method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Dispose_BoolArgument() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass : IDisposable + { + public void Dispose() + { + Dispose(true); + } + + public void Dispose(bool dispose) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Dispose(bool) method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Dispose_NoIDisposable() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public void Dispose() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Dispose() method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Dispose_DifferentMethod() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass : IDisposable + { + public void OtherName() + { + throw new ArgumentException(); + } + + public void Dispose() + { + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Finalize() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + ~MyClass() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the finalizer method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Finalize_NoThrow() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + ~MyClass() + { + + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_GetHashCode() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public override int GetHashCode() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the GetHashCode() method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_GetHashCode_NoThrow() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public override int GetHashCode() + { + return 5; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_GetHashCode_HidingMember() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public int GetHashCode() + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Equals() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public override bool Equals(object o) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Equals(object) method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Equals_IEquatable() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass : IEquatable + { + public bool Equals(MyClass o) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Equals(MyClass) method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Equals_NoThrow() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public override bool Equals(object o) + { + return false; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Equals_HidingMember() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + public class MyClass + { + public bool Equals(object o) + { + throw new ArgumentException(); + } + } +}"; + + VerifyDiagnostic(original, "An exception is thrown from the Equals(object) method in type MyClass"); + } + + [TestMethod] + public void ExceptionThrownFromProhibitedContext_Indexer() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public string this[int i] + { + get + { + throw new ArgumentException(); + } + + set { } + } + } +}"; + + VerifyDiagnostic(original); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/SingleGeneralExceptionTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/SingleGeneralExceptionTests.cs index e21cc71..47b5df4 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/SingleGeneralExceptionTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Exceptions/SingleGeneralExceptionTests.cs @@ -10,7 +10,11 @@ public class SingleGeneralExceptionTests : CSharpDiagnosticVerifier { protected override DiagnosticAnalyzer DiagnosticAnalyzer => new SingleGeneralExceptionAnalyzer(); + /// + /// Ignored until https://github.com/Vannevelj/RoslynTester/issues/32 + /// [TestMethod] + [Ignore] public void SingleGeneralException_WithSingleGeneralException() { var test = @" 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/Tests/General/AsToCastTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/AsToCastTests.cs index dddc345..c26c940 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/AsToCastTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/AsToCastTests.cs @@ -14,7 +14,7 @@ public class AsToCastTests : CSharpCodeFixVerifier protected override CodeFixProvider CodeFixProvider => new AsToCastCodeFix(); [TestMethod] - public void AsToCast_PredefinedType() + public void AsToCast_NullableValueType() { var original = @" namespace ConsoleApplication1 @@ -190,5 +190,50 @@ void Method() VerifyDiagnostic(original, AsToCastAnalyzer.Rule.MessageFormat.ToString()); VerifyFix(original, result); } + + [TestMethod] + public void AsToCast_Generics() + { + var original = @" +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + static class Extensions + { + public static void Method(this IEnumerable input) where T : class + { + var list = new List(); + + foreach (var node in input) + { + list.Add(node as T); + } + } + } +}"; + + var result = @" +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + static class Extensions + { + public static void Method(this IEnumerable input) where T : class + { + var list = new List(); + + foreach (var node in input) + { + list.Add((T)node); + } + } + } +}"; + + VerifyDiagnostic(original, AsToCastAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CastToAsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CastToAsTests.cs index bf6a979..327fb64 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CastToAsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CastToAsTests.cs @@ -23,12 +23,11 @@ class MyClass { void Method() { - var ch = 'r'; - var i = (int) ch; + char ch = 'r'; + int i = (int) ch; } } }"; - VerifyDiagnostic(original); } @@ -197,5 +196,74 @@ void Main() VerifyDiagnostic(original, CastToAsAnalyzer.Rule.MessageFormat.ToString()); VerifyFix(original, result); } + + [TestMethod] + public void CastToAs_Generics() + { + var original = @" +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + static class Extensions + { + public static void Method(this IEnumerable input) where T : class + { + var list = new List(); + + foreach (var node in input) + { + list.Add((T)node); + } + } + } +}"; + + var result = @" +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + static class Extensions + { + public static void Method(this IEnumerable input) where T : class + { + var list = new List(); + + foreach (var node in input) + { + list.Add(node as T); + } + } + } +}"; + + VerifyDiagnostic(original, CastToAsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void CastToAs_Generics_ValueType() + { + var original = @" +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + static class Extensions + { + public static void Method(this IEnumerable input) where T : struct + { + var list = new List(); + + foreach (var node in input) + { + list.Add((T)node); + } + } + } +}"; + VerifyDiagnostic(original); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToFalseLiteralTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToFalseLiteralTests.cs index 59e783a..92bfeb3 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToFalseLiteralTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToFalseLiteralTests.cs @@ -966,5 +966,44 @@ void Method() VerifyDiagnostic(original, CompareBooleanToFalseLiteralAnalyzer.Rule.MessageFormat.ToString()); VerifyFix(original, result); } + + [TestMethod] + public void CompareBooleanToFalseLiteral_DoesNotReformatEntireDocment() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatting removes space before parens + { + bool isAwesome = false; + if(isAwesome == false) // formatting adds space before parens + { + System.Console.WriteLine(""awesome""); + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatting removes space before parens + { + bool isAwesome = false; + if(!isAwesome) // formatting adds space before parens + { + System.Console.WriteLine(""awesome""); + } + } + } +}"; + + VerifyDiagnostic(original, CompareBooleanToFalseLiteralAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToTrueLiteralTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToTrueLiteralTests.cs index f743225..4233269 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToTrueLiteralTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/CompareBooleanToTrueLiteralTests.cs @@ -1055,5 +1055,50 @@ void Method() VerifyDiagnostic(original, CompareBooleanToTrueLiteralAnalyzer.Rule.MessageFormat.ToString()); VerifyFix(original, result); } + + [TestMethod] + public void CompareBooleanToTrueLiteral_DoesNotReformatEntireDoc() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatting removes space before parens + { + bool isAwesome = true; + if(isAwesome == true) // formatting adds space before parens + { + Console.WriteLine(""awesome""); + } + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatting removes space before parens + { + bool isAwesome = true; + if(isAwesome) // formatting adds space before parens + { + Console.WriteLine(""awesome""); + } + } + } +}"; + + VerifyDiagnostic(original, CompareBooleanToTrueLiteralAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysFalseTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysFalseTests.cs deleted file mode 100644 index f1dc790..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysFalseTests.cs +++ /dev/null @@ -1,379 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.General.ConditionIsAlwaysFalse; - -namespace VSDiagnostics.Test.Tests.General -{ - [TestClass] - public class ConditionIsAlwaysFalseTests : CSharpCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ConditionIsAlwaysFalseAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new ConditionIsAlwaysFalseCodeFix(); - - [TestMethod] - public void ConditionIsAlwaysFalse_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (false) - { - var b = true; - b = false; - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (false) - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_ConditionContainsTrueLiteral() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (b && false) - b = false; - } - } -}"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElse_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (false) - { - var b = true; - b = false; - } - else - { - var b = true; - - if (b) { } // prevent variable is never used warning for fix - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElse_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (false) - b = false; - else - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElseIf_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (false) - { - var b = true; - b = false; - } - else if (9 == 8) - { - var b = true; - b = false; - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (9 == 8) - { - var b = true; - b = false; - } - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElseIf_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (false) - b = false; - else if (9 == 8) - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - if (9 == 8) - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElseIfElse_ConditionHasBraces() - { - var original = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (false) - { - var b = true; - b = false; - } - else if (9 == 8) - { - Console.WriteLine(""""); - } - else - { - Console.WriteLine(""""); - } - } - } -}"; - - var result = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (9 == 8) - { - Console.WriteLine(""""); - } - else - { - Console.WriteLine(""""); - } - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysFalse_WithElseIfElse_ConditionDoesNotHaveBraces() - { - var original = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (false) - b = false; - else if (9 == 8) - b = false; - else - Console.WriteLine(""""); - } - } -}"; - - var result = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - if (9 == 8) - b = false; - else - Console.WriteLine(""""); - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysFalseAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysTrueTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysTrueTests.cs deleted file mode 100644 index 3c4e3dd..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsAlwaysTrueTests.cs +++ /dev/null @@ -1,362 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.General.ConditionIsAlwaysTrue; - -namespace VSDiagnostics.Test.Tests.General -{ - [TestClass] - public class ConditionIsAlwaysTrueTests : CSharpCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ConditionIsAlwaysTrueAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new ConditionIsAlwaysTrueCodeFix(); - - [TestMethod] - public void ConditionIsAlwaysTrue_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (true) - { - var b = true; - b = false; - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (true) - b = false; - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_ConditionContainsTrueLiteral() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (b && true) - b = false; - } - } -}"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElse_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (true) - { - var b = true; - b = false; - } - else - { - var b = true; - - if (b) { } // prevent variable is never used warning for fix - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElse_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (true) - b = false; - else - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElseIf_ConditionHasBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (true) - { - var b = true; - b = false; - } - else if (9 == 8) - { - var b = true; - b = false; - } - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElseIf_ConditionDoesNotHaveBraces() - { - var original = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (true) - b = false; - else if (9 == 8) - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - var result = @" -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - b = false; - - if (b) { } // prevent variable is never used warning for fix - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElseIfElse_ConditionHasBraces() - { - var original = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if (true) - { - Console.WriteLine(""""); - } - else if (9 == 8) - { - Console.WriteLine(""""); - } - else - { - Console.WriteLine(""""); - } - } - } -}"; - - var result = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - Console.WriteLine(""""); - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void ConditionIsAlwaysTrue_WithElseIfElse_ConditionDoesNotHaveBraces() - { - var original = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - - if (true) - Console.WriteLine(""""); - else if (9 == 8) - b = false; - else - Console.WriteLine(""""); - } - } -}"; - - var result = @" -using System; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var b = true; - Console.WriteLine(""""); - } - } -}"; - - VerifyDiagnostic(original, ConditionIsAlwaysTrueAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsConstantTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsConstantTests.cs new file mode 100644 index 0000000..5a6c2e3 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionIsConstantTests.cs @@ -0,0 +1,1261 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.ConditionIsConstant; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class ConditionIsConstantTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ConditionIsConstantAnalyzer(); + + protected override CodeFixProvider CodeFixProvider => new ConditionIsConstantCodeFix(); + + [TestMethod] + public void ConditionIsAlwaysTrue_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (true) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (true) + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConditionContainsTrueLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (b && true) + b = false; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElse_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (true) + { + var b = true; + b = false; + } + else + { + var b = true; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElse_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (true) + b = false; + else + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIf_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var variable = true; + if (true) + { + var b = true; + b = false; + } + else if (variable) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var variable = true; + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIf_ChildIfIsAlwaysTrue_IfIsInBlockNode() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else + { + if (true) + { + var b = true; + b = false; + } + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else + { + var b = true; + b = false; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIf_ChildIfIsAlwaysTrue_IfIsInElseNode() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else if (true) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else + { + var b = true; + b = false; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIf_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (true) + b = false; + else if (b) + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIfElse_ConditionHasBraces() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + if (true) + { + Console.WriteLine(""""); + } + else if (v) + { + Console.WriteLine(""""); + } + else + { + Console.WriteLine(""""); + } + } + } +}"; + + var result = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + Console.WriteLine(""""); + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_WithElseIfElse_ConditionDoesNotHaveBraces() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (true) + Console.WriteLine(""""); + else if (b) + b = false; + else + Console.WriteLine(""""); + } + } +}"; + + var result = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + Console.WriteLine(""""); + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConstantBooleanComparedToBooleanLiteralTrue() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + if (lit == true) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + var dummyVal = 0; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConstantBooleanComparedToBooleanLiteralFalse() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + if (lit == false) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + var dummyVal = 0; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConstantBoolean() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + if (lit) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + var dummyVal = 0; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_ConstantInteger() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const int lit = 5; + if (lit == 5) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const int lit = 5; + var dummyVal = 0; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysTrue_CompareIdenticalLiterals() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (5 == 5) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var dummyVal = 0; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "true")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (false) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (false) + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConditionContainsTrueLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (b && false) + b = false; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElse_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (false) + { + var b = true; + b = false; + } + else + { + var b = true; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElse_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (false) + b = false; + else + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIf_ConditionHasBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + if (false) + { + var b = true; + b = false; + } + else if (v) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + if (v) + { + var b = true; + b = false; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIf_ConditionDoesNotHaveBraces() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (false) + b = false; + else if (b) + b = false; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + if (b) + b = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIfElse_ConditionHasBraces() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + if (false) + { + var b = true; + b = false; + } + else if (v) + { + Console.WriteLine(""""); + } + else + { + Console.WriteLine(""""); + } + } + } +}"; + + var result = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var v = true; + if (v) + { + Console.WriteLine(""""); + } + else + { + Console.WriteLine(""""); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIfElse_ConditionDoesNotHaveBraces() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + + if (false) + b = false; + else if (b) + b = false; + else + Console.WriteLine(""""); + } + } +}"; + + var result = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var b = true; + if (b) + b = false; + else + Console.WriteLine(""""); + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConstantBooleanComparedToBooleanLiteralTrue() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + if (lit == true) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConstantBooleanComparedToBooleanLiteralFalse() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + if (lit == false) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = true; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConstantBoolean() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + if (lit) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const bool lit = false; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_ConstantInteger() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const int lit = 5; + if (lit == 4) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + const int lit = 5; + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_CompareNonidenticalLiterals() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if (5 == 4) + { + var dummyVal = 0; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS0219"); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIf_ChildIfIsAlwaysFalse_IfIsInBlockNode() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else + { + if (false) + { + var b = true; + b = false; + } + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else + { + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + + [TestMethod] + public void ConditionIsAlwaysFalse_WithElseIf_ChildIfIsAlwaysFalse_IfIsInElseNode() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + else if (false) + { + var b = true; + b = false; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var potentiallyTrue = true; + if (potentiallyTrue) + { + var b = true; + b = false; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(ConditionIsConstantAnalyzer.Rule.MessageFormat.ToString(), "false")); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsDefaultOptionsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsDefaultOptionsTests.cs index 38c4013..53e1fb9 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsDefaultOptionsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsDefaultOptionsTests.cs @@ -252,5 +252,46 @@ void Method() VerifyDiagnostic(original); } + + [TestMethod] + public void ConditionalOperatorReturnsDefaultOptions_DoesNotReformatEntireDocument() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatter removes space before parens + { + int legalAge = 18; + int myAge = 22; + bool canDrink = myAge >= legalAge ? true : false; + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatter removes space before parens + { + int legalAge = 18; + int myAge = 22; + bool canDrink = myAge >= legalAge; + } + } +}"; + + VerifyDiagnostic(original, ConditionalOperatorReturnsDefaultOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsInvertedDefaultOptionsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsInvertedDefaultOptionsTests.cs index f84e4b4..6d24f16 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsInvertedDefaultOptionsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ConditionalOperatorReturnsInvertedDefaultOptionsTests.cs @@ -252,5 +252,40 @@ void Method() VerifyDiagnostic(original); } + + [TestMethod] + public void ConditionalOperatorReturnsInvertedDefaultOptions_DoesNotReformatEntireDocument() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatter removes space before parens + { + int legalAge = 18; + int myAge = 22; + bool canDrink = myAge >= legalAge ? false : true; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method () // formatter removes space before parens + { + int legalAge = 18; + int myAge = 22; + bool canDrink = !(myAge >= legalAge); + } + } +}"; + + VerifyDiagnostic(original, ConditionalOperatorReturnsInvertedDefaultOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ElementaryMethodsOfTypeInCollectionNotOverriddenTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ElementaryMethodsOfTypeInCollectionNotOverriddenTests.cs new file mode 100644 index 0000000..b32cef9 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ElementaryMethodsOfTypeInCollectionNotOverriddenTests.cs @@ -0,0 +1,354 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.ElementaryMethodsOfTypeInCollectionNotOverridden; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class ElementaryMethodsOfTypeInCollectionNotOverriddenTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer(); + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithReferenceType() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + class MyCollectionItem {} +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithInterfaceType() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + interface MyCollectionItem {} +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithValueType() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + struct MyCollectionItem {} +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithReferenceType_ImplementsEquals() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + class MyCollectionItem + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithValueType_ImplementsEquals() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + struct MyCollectionItem + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithReferenceType_ImplementsGetHashCode() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + class MyCollectionItem + { + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithValueType_ImplementsGetHashCode() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + struct MyCollectionItem + { + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithReferenceType_ImplementsMethods() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + class MyCollectionItem + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_WithValueType_ImplementsMethods() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + } + } + + struct MyCollectionItem + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_Dictionary_BothDoNotImplementMethods() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new Dictionary(); + } + } + + class MyCollectionItem {} +}"; + + VerifyDiagnostic(original, + ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString(), + ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_Dictionary_OneDoesNotImplementMethods() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new Dictionary(); + } + } + + class MyCollectionItem {} +}"; + + VerifyDiagnostic(original, + ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.Rule.MessageFormat.ToString()); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_TypeParameterWithoutObjectCreation() + { + var original = @" +using System.Linq; +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = Enumerable.Empty(); + } + } + + class MyCollectionItem {} +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_GenericTypeFromClass() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + internal class MyClass + { + public static List Method() + { + var newList = new List(); + return newList; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ElementaryMethodsOfTypeInCollectionNotOverridden_GenericTypeFromMethod() + { + var original = @" +using System.Collections.Generic; +namespace ConsoleApplication1 +{ + internal class MyClass + { + public static List Method() + { + var newList = new List(); + return newList; + } + } +}"; + + VerifyDiagnostic(original); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/EqualsAndGetHashcodeNotImplementedTogetherTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/EqualsAndGetHashcodeNotImplementedTogetherTests.cs new file mode 100644 index 0000000..55fc134 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/EqualsAndGetHashcodeNotImplementedTogetherTests.cs @@ -0,0 +1,300 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.EqualsAndGetHashcodeNotImplementedTogether; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class EqualsAndGetHashcodeNotImplementedTogetherTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new EqualsAndGetHashcodeNotImplementedTogetherAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new EqualsAndGetHashcodeNotImplementedTogetherCodeFix(); + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_BothImplemented_NoWarning() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_EqualsImplemented() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_GetHashcodeImplemented() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_NeitherImplemented_NoWarning() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_NonOverridingEqualsImplemented_NoWarning() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_NonOverridingGetHashcodeImplemented_NoWarning() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int GetHashCode() + { + throw new System.NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_EqualsImplemented_SimplifiesNameWhenUsingSystem() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + class MyClass + { + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +namespace ConsoleApplication1 +{ + class MyClass + { + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_GetHashcodeImplemented_SimplifiesNameWhenUsingSystem() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + class MyClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +namespace ConsoleApplication1 +{ + class MyClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void EqualsAndGetHashcodeNotImplemented_GetHashcodeImplemented_BaseClassImplementsBoth() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + } + + class MyClass : MyBaseClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + } + + class MyClass : MyBaseClass + { + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/GetHashCodeRefersToMutableFieldTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/GetHashCodeRefersToMutableFieldTests.cs new file mode 100644 index 0000000..de383a6 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/GetHashCodeRefersToMutableFieldTests.cs @@ -0,0 +1,361 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.GetHashCodeRefersToMutableMember; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class GetHashCodeRefersToMutableMemberTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new GetHashCodeRefersToMutableMemberAnalyzer(); + + [TestMethod] + public void GetHashCodeRefersToMutableMember_ConstantField() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + private const char Boo = '1'; + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to const field Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_NonReadonlyField() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + private char _boo = '1'; + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to non-readonly field _boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_StaticField() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + private static readonly char _boo = '1'; + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to static field _boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_NonReadonlyStaticNonValueTypeField() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + private static Type _boo = typeof(Foo); + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to static non-readonly non-value type, non-string field _boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_NonValueTypeNonStringField() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + private readonly Type _boo = typeof(Foo); + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to non-value type, non-string field _boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_ImmutableMember_NoWarning() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + private readonly char _boo = '1'; + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_ImmutableStringMember_NoWarning() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + private readonly string _boo = ""1""; + + public override int GetHashCode() + { + return _boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_StaticProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public static char Boo { get; } = '1'; + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to static property Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_NonValueTypeNonStringProperty() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + public Type Boo { get; } = typeof(Foo); + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to non-value type, non-string property Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_SettableProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public char Boo { get; set; } = '1'; + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to settable property Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_PropertyWithBodiedGetter() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public char Boo { get { return '1'; } } + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to property with bodied getter Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_StaticNonValueTypeSettablePropertyWithBodiedGetter() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + public class Foo + { + public static Type Boo { get { return typeof(Foo); } set { } } + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to static non-value type, non-string settable property with bodied getter Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_PropertyWithExpressionBodiedGetter() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public char Boo => '1'; + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to property with bodied getter Boo"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_ImmutableProperty_NoDiagnostic() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public char Boo { get; } + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_ImmutableStringProperty_NoDiagnostic() + { + var original = @" +namespace ConsoleApplication1 +{ + public class Foo + { + public string Boo { get; } + + public override int GetHashCode() + { + return Boo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_InOtherType_PropertyWithExpressionBodiedGetter() + { + var original = @" +namespace ConsoleApplication1 +{ + public struct Bar + { + public int Fuzz => 0; + } + + public class Foo + { + private readonly Bar _bar = new Bar(); + public override int GetHashCode() => _bar.Fuzz.GetHashCode(); + } +}"; + + VerifyDiagnostic(original, "GetHashCode() refers to property with bodied getter Fuzz"); + } + + [TestMethod] + public void GetHashCodeRefersToMutableMember_InOtherType_ImmutableProperty_NoDiagnostic() + { + var original = @" +namespace ConsoleApplication1 +{ + public struct Bar + { + public int Fizz { get; } + } + + public class Foo + { + private readonly Bar _bar = new Bar(); + public override int GetHashCode() => _bar.Fizz.GetHashCode(); + } +}"; + + VerifyDiagnostic(original); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/IfStatementWithoutBracesTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/IfStatementWithoutBracesTests.cs deleted file mode 100644 index 44d9abd..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/IfStatementWithoutBracesTests.cs +++ /dev/null @@ -1,413 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.General.IfStatementWithoutBraces; - -namespace VSDiagnostics.Test.Tests.General -{ - [TestClass] - public class IfStatementWithoutBracesTests : CSharpCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new IfStatementWithoutBracesAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new IfStatementWithoutBracesCodeFix(); - - [TestMethod] - public void IfStatementWithoutBraces_WithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_WithoutBraces_OnSameLine_WithIntermittentComment() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) /* comments */ Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) /* comments */ - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_WithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_WithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - } - } -}"; - VerifyDiagnostic(original); - } - - [TestMethod] - public void IfStatementWithoutBraces_ElseStatementWithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else Console.WriteLine(""false""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else - { - Console.WriteLine(""false""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_ElseStatementWithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else - Console.WriteLine(""false""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else - { - Console.WriteLine(""false""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_ElseStatementWithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else - { - Console.WriteLine(""false""); - } - } - } -}"; - VerifyDiagnostic(original); - } - - [TestMethod] - public void IfStatementWithoutBraces_IfAndElseWithoutBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - Console.WriteLine(""true""); - else - Console.WriteLine(""false""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - if(true) - { - Console.WriteLine(""true""); - } - else - { - Console.WriteLine(""false""); - } - } - } -}"; - - VerifyDiagnostic(original, - IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString(), - IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void IfStatementWithoutBraces_ElseIfWithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var i = 5; - if (i == 5) - { - Console.WriteLine(""true""); - } - else if (i == 4) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original); - } - - [TestMethod] - public void IfStatementWithoutBraces_ElseIfWithoutBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var i = 5; - if (i == 5) - { - Console.WriteLine(""true""); - } - else if (i == 4) - Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var i = 5; - if (i == 5) - { - Console.WriteLine(""true""); - } - else if (i == 4) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, IfStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ImplementEqualsAndGetHashCodeTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ImplementEqualsAndGetHashCodeTests.cs new file mode 100644 index 0000000..8b11dc6 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/ImplementEqualsAndGetHashCodeTests.cs @@ -0,0 +1,1560 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.ImplementEqualsAndGetHashCode; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class ImplementEqualsAndGetHashCodeTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new ImplementEqualsAndGetHashCodeAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new ImplementEqualsAndGetHashCodeCodeFix(); + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasReadonlyField() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasGetOnlyProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string Foo { get; } = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string Foo { get; } = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return Foo == value.Foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return Foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasMultipleFieldsAndProperties() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + readonly string _bar = ""test""; + + string Foo { get; } = ""test""; + string Bar { get; } = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + readonly string _bar = ""test""; + + string Foo { get; } = ""test""; + string Bar { get; } = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar && + Foo == value.Foo && + Bar == value.Bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + _bar.GetHashCode() ^ + Foo.GetHashCode() ^ + Bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasSetOnlyProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + string _bar = ""test""; + + string Foo { get; } = ""test""; + string Bar + { + set { _bar = value; } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + string _bar = ""test""; + + string Foo { get; } = ""test""; + string Bar + { + set { _bar = value; } + } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar && + Foo == value.Foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + Foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_DoesNotHaveFieldOrProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassImplementsEquals_HasField() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string _foo = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_HasField() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Struct MyStruct does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_HasProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + string Foo { get; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + string Foo { get; } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return Foo == value.Foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return Foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Struct MyStruct does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_HasMultipleFieldsAndProperties() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + readonly string _bar; + + string Foo { get; } + string Bar { get; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + readonly string _bar; + + string Foo { get; } + string Bar { get; } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return _foo == value._foo && + _bar == value._bar && + Foo == value.Foo && + Bar == value.Bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + _bar.GetHashCode() ^ + Foo.GetHashCode() ^ + Bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Struct MyStruct does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_HasSetOnlyProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + string _bar; + + string Foo { get; } + string Bar + { + set { _bar = value; } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + readonly string _foo; + string _bar; + + string Foo { get; } + string Bar + { + set { _bar = value; } + } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return _foo == value._foo && + _bar == value._bar && + Foo == value.Foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + Foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Struct MyStruct does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_DoesNotHaveFieldOrProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructImplementsEquals_HasField() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + string _foo; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return _foo == value._foo; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasChainedFields() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test"", _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test"", _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + _bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasStaticFields() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasConstFields() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + const string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + const string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasBaseClassImplementingEquals() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return base.Equals(obj) && + _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasBaseClassImplementingEquals_HasInterface() + { + var original = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyBaseClass + { + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass, IClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyBaseClass + { + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass, IClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return base.Equals(obj) && + _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_StructDoesNotImplementEither_ImplementsInterface() + { + var original = @" +namespace ConsoleApplication1 +{ + interface IStruct { } + + struct MyStruct : IStruct + { + readonly string _foo; + static string _bar; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + interface IStruct { } + + struct MyStruct : IStruct + { + readonly string _foo; + static string _bar; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyStruct) != obj.GetType()) + { + return false; + } + + var value = (MyStruct)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Struct MyStruct does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasBaseClassImplementingEquals_BaseClassHasField() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public string foo; + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public string foo; + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return base.Equals(obj) && + _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasBaseBaseClassImplementingEquals_BaseBaseClassHasField() + { + var original = @" +namespace ConsoleApplication1 +{ + class A + { + public virtual bool M { get { return true; } } + public override bool Equals(object obj) => true; + } + + class B : A { } + + class C : B + { + public override bool M { get { return true; } } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class A + { + public virtual bool M { get { return true; } } + public override bool Equals(object obj) => true; + } + + class B : A { } + + class C : B + { + public override bool M { get { return true; } } + + public override bool Equals(object obj) + { + if (obj == null || typeof(C) != obj.GetType()) + { + return false; + } + + var value = (C)obj; + return base.Equals(obj) && + M.Equals(value.M); + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return base.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class C does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasBaseClassNotImplementingEquals() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public string foo; + public override int GetHashCode() => 1; // disable analyzer for this + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public string foo; + public override int GetHashCode() => 1; // disable analyzer for this + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesImplementsEquals_OverridesPropertyInBaseClass() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public virtual string Bar { get; } + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + public override string Bar { get; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyBaseClass + { + public virtual string Bar { get; } + public override bool Equals(object obj) => true; + } + + class MyClass : MyBaseClass + { + readonly string _foo = ""test""; + static string _bar = ""test""; + public override string Bar { get; } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return base.Equals(obj) && + _foo == value._foo && + Bar == value.Bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + Bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeUsesReadonly() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + string _bar = ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo = ""test""; + string _bar = ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeUsesValueTypes() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct { } + class MyClassN { } + + class MyClass + { + readonly int _foo = 0; + readonly MyStruct _bar; + readonly MyClassN _fizz; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct { } + class MyClassN { } + + class MyClass + { + readonly int _foo = 0; + readonly MyStruct _bar; + readonly MyClassN _fizz; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo.Equals(value._foo) && + _bar.Equals(value._bar) && + _fizz == value._fizz; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + _bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeUsesString() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + readonly string _foo; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeDoesNotUseInterface() + { + var original = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyClass + { + readonly string _foo; + readonly IClass _bar; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyClass + { + readonly string _foo; + readonly IClass _bar; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeReturnsBaseGetHashCodeWhenNoEligbleValues() + { + var original = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyClass + { + readonly IClass _bar; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + interface IClass { } + + class MyClass + { + readonly IClass _bar; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _bar == value._bar; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return base.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsComparesAll_GetHashCodeUsesBodylessGetOnlyProperties() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string _foo { get; set; } + string _bar { get; } + string _fizz { get { return ""test""; } } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string _foo { get; set; } + string _bar { get; } + string _fizz { get { return ""test""; } } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo && + _bar == value._bar && + _fizz == value._fizz; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_NeitherUsesDelegates() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string _foo { get; } + public delegate int PerformCalculation(int x, int y); + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string _foo { get; } + public delegate int PerformCalculation(int x, int y); + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo == value._foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_EqualsUsesEqualsOnValueTypes() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct { } + + class MyClass + { + readonly MyStruct _foo; + MyStruct _bar { get; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct { } + + class MyClass + { + readonly MyStruct _foo; + MyStruct _bar { get; } + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo.Equals(value._foo) && + _bar.Equals(value._bar); + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return _foo.GetHashCode() ^ + _bar.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_ClassDoesNotImplementEither_HasExpressionBodiedProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string Foo => ""test""; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + string Foo => ""test""; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return Foo == value.Foo; + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return base.GetHashCode(); + } + } +}"; + + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_PartialClassDoesNotImplementEither_ImplementsAllTypesInSplitClass() + { + var original = @" +namespace ConsoleApplication1 +{ + public partial class MyClass + { + private int _foo; + } + + public partial class MyClass + { + private int _bar; + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + public partial class MyClass + { + private int _foo; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _foo.Equals(value._foo) && + _bar.Equals(value._bar); + } + + public override int GetHashCode() + { + // Add any fields you're interested in, taking into account the guidelines described in + // https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx + return base.GetHashCode(); + } + } + + public partial class MyClass + { + private int _bar; + } +}"; + + // two diagnostics because it is reported in two places + VerifyDiagnostic(original, "Class MyClass does not implement Equals() and GetHashCode().", + "Class MyClass does not implement Equals() and GetHashCode()."); + VerifyFix(original, result, 0); + } + + [TestMethod] + public void ImplementEqualsAndGetHashCode_PartialClassImplementsEquals() + { + var firstTree = @" +namespace ConsoleApplication1 +{ + public partial class MyClass + { + private int _foo; + + public override bool Equals(object obj) + { + if (obj == null || typeof(MyClass) != obj.GetType()) + { + return false; + } + + var value = (MyClass)obj; + return _bar.Equals(value._bar) && + _foo.Equals(value._foo); + } + } +}"; + + var secondTree = @" +namespace ConsoleApplication1 +{ + public partial class MyClass + { + private int _bar; + } +}"; + + VerifyDiagnostic(new[] {firstTree, secondTree}); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopStatementWithoutBracesTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopStatementWithoutBracesTests.cs deleted file mode 100644 index 3730a98..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopStatementWithoutBracesTests.cs +++ /dev/null @@ -1,612 +0,0 @@ -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.General.LoopStatementWithoutBraces; - -namespace VSDiagnostics.Test.Tests.General -{ - [TestClass] - public class LoopStatementWithoutBracesTests : CSharpCodeFixVerifier - { - protected override DiagnosticAnalyzer DiagnosticAnalyzer => new LoopStatementWithoutBracesAnalyzer(); - - protected override CodeFixProvider CodeFixProvider => new LoopStatementWithoutBracesCodeFix(); - - [TestMethod] - public void LoopStatementWithoutBraces_For_WithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_For_WithoutBraces_OnSameLine_WithIntermittentComment() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) /* comments */ Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) /* comments */ - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_For_WithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) - Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_For_WithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - for (int i = 0; i < 10; i++) - { - Console.WriteLine(""true""); - } - } - } -}"; - VerifyDiagnostic(original); - } - - [TestMethod] - public void LoopStatementWithoutBraces_While_WithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_While_WithoutBraces_OnSameLine_WithIntermittentComment() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) /* comments */ Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) /* comments */ - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_While_WithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) - Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_While_WithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - while (true) - { - Console.WriteLine(""true""); - } - } - } -}"; - VerifyDiagnostic(original); - } - - [TestMethod] - public void LoopStatementWithoutBraces_Foreach_WithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_Foreach_WithoutBraces_OnSameLine_WithIntermittentComment() - { - var original = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) /* comments */ Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) /* comments */ - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_Foreach_WithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) - Console.WriteLine(""true""); - } - } -}"; - - var result = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) - { - Console.WriteLine(""true""); - } - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_Foreach_WithBraces() - { - var original = @" -using System; -using System.Text; -using System.Collections.Generic; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - var list = new List(); - foreach (var item in list) - { - Console.WriteLine(""true""); - } - } - } -}"; - VerifyDiagnostic(original); - } - - [TestMethod] - public void LoopStatementWithoutBraces_DoWhile_WithoutBraces_OnSameLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do Console.WriteLine(""true""); while (true); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do - { - Console.WriteLine(""true""); - } - while (true); - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_DoWhile_WithoutBraces_OnSameLine_WithIntermittentComment() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do /* comments */ - Console.WriteLine(""true""); - while (true); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do /* comments */ - { - Console.WriteLine(""true""); - } - while (true); - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_DoWhile_WithoutBraces_OnNextLine() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do - Console.WriteLine(""true""); - while (true); - } - } -}"; - - var result = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do - { - Console.WriteLine(""true""); - } - while (true); - } - } -}"; - - VerifyDiagnostic(original, LoopStatementWithoutBracesAnalyzer.Rule.MessageFormat.ToString()); - VerifyFix(original, result); - } - - [TestMethod] - public void LoopStatementWithoutBraces_DoWhile_WithBraces() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - do - { - Console.WriteLine(""true""); - } while (true); - } - } -}"; - VerifyDiagnostic(original); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopedRandomInstantiationTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopedRandomInstantiationTests.cs new file mode 100644 index 0000000..d9fc2b2 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/LoopedRandomInstantiationTests.cs @@ -0,0 +1,259 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.LoopedRandomInstantiation; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class LoopedRandomInstantiationTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new LoopedRandomInstantiationAnalyzer(); + + [TestMethod] + public void LoopedRandomInstantiation_WhileLoop() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + var rand = new Random(); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_DoWhileLoop() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do + { + var rand = new Random(); + } while (true); + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_ForLoop() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (var i = 0; i > 5; i++) + { + var rand = new Random(4); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_ForeachLoop() + { + var original = @" +using System; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) + { + var rand = new Random(); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_MultipleDeclaratorsInDeclaration() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + Random rand = new Random(), rind = new Random(2); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand"), + string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rind")); + } + + [TestMethod] + public void LoopedRandomInstantiation_MultipleLevelsOfNesting() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + if (true) + { + Random rand = new Random(); + } + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_RandomInstanceNotInLoop() + { + var original = @" +using System; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var rand = new Random(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void LoopedRandomInstantiation_RandomNotSystemRandom() + { + var original = @" +namespace ConsoleApplication1 +{ + class Random {} + + class MyClass + { + void Method() + { + var rand = new Random(); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void LoopedRandomInstantiation_TypeIsObject_DoesNotCrashAnalyzerBecauseContainingNamespaceIsNull() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object[] o = {}; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void LoopedRandomInstantiation_Struct() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + struct MyStruct + { + void Method() + { + while (true) + { + var rand = new Random(); + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(LoopedRandomInstantiationAnalyzer.Rule.MessageFormat.ToString(), "rand")); + } + + [TestMethod] + public void LoopedRandomInstantiation_NotInLoop_Struct() + { + var original = @" +using System; + +namespace ConsoleApplication1 +{ + struct MyStruct + { + void Method() + { + var rand = new Random(); + } + } +}"; + + VerifyDiagnostic(original); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/MissingBracesTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/MissingBracesTests.cs new file mode 100644 index 0000000..9a2fadd --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/MissingBracesTests.cs @@ -0,0 +1,1662 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.MissingBraces; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class MissingBracesTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new MissingBracesAnalyzer(); + + protected override CodeFixProvider CodeFixProvider => new MissingBracesCodeFix(); + + [TestMethod] + public void MissingBraces_IfWithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "An if statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_IfWithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) /* comments */ Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) /* comments */ + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "An if statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_IfWithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "An if statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_IfWithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_ElseStatementWithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else Console.WriteLine(""false""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else + { + Console.WriteLine(""false""); + } + } + } +}"; + + VerifyDiagnostic(original, "An else statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_ElseStatementWithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else + Console.WriteLine(""false""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else + { + Console.WriteLine(""false""); + } + } + } +}"; + + VerifyDiagnostic(original, "An else statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_ElseStatementWithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else + { + Console.WriteLine(""false""); + } + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_IfAndElseWithoutBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + Console.WriteLine(""true""); + else + Console.WriteLine(""false""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + if(true) + { + Console.WriteLine(""true""); + } + else + { + Console.WriteLine(""false""); + } + } + } +}"; + + VerifyDiagnostic(original, + "An if statement should be written with braces", + "An else statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_ElseIfWithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var i = 5; + if (i == 5) + { + Console.WriteLine(""true""); + } + else if (i == 4) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_ElseIfWithoutBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var i = 5; + if (i == 5) + { + Console.WriteLine(""true""); + } + else if (i == 4) + Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var i = 5; + if (i == 5) + { + Console.WriteLine(""true""); + } + else if (i == 4) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "An if statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_UsingWithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) Console.WriteLine(""true""); + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, "A using statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_UsingWithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) /* blah blah */ Console.WriteLine(""true""); + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) /* blah blah */ + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, "A using statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_UsingWithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + Console.WriteLine(""true""); + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, "A using statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_UsingWithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_WithOutBraces_ChildIsUsingStatement() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + using (var e = new Disposable()) + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_WithOutBraces_ChildIsUsingStatementWithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + using (var e = new Disposable()) + Console.WriteLine(""true""); + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + using (var d = new Disposable()) + using (var e = new Disposable()) + { + Console.WriteLine(""true""); + } + } + } + + class Disposable : IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } +}"; + + VerifyDiagnostic(original, "A using statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_For_WithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A for statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_For_WithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) /* comments */ Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) /* comments */ + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A for statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_For_WithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) + Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A for statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_For_WithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + for (int i = 0; i < 10; i++) + { + Console.WriteLine(""true""); + } + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_While_WithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A while statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_While_WithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) /* comments */ Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) /* comments */ + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A while statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_While_WithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A while statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_While_WithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + while (true) + { + Console.WriteLine(""true""); + } + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_Foreach_WithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A foreach statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_Foreach_WithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) /* comments */ Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) /* comments */ + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A foreach statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_Foreach_WithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) + Console.WriteLine(""true""); + } + } +}"; + + var result = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) + { + Console.WriteLine(""true""); + } + } + } +}"; + + VerifyDiagnostic(original, "A foreach statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_Foreach_WithBraces() + { + var original = @" +using System; +using System.Text; +using System.Collections.Generic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var list = new List(); + foreach (var item in list) + { + Console.WriteLine(""true""); + } + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_DoWhile_WithoutBraces_OnSameLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do Console.WriteLine(""true""); while (true); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do + { + Console.WriteLine(""true""); + } + while (true); + } + } +}"; + + VerifyDiagnostic(original, "A do statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_DoWhile_WithoutBraces_OnSameLine_WithIntermittentComment() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do /* comments */ + Console.WriteLine(""true""); + while (true); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do /* comments */ + { + Console.WriteLine(""true""); + } + while (true); + } + } +}"; + + VerifyDiagnostic(original, "A do statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_DoWhile_WithoutBraces_OnNextLine() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do + Console.WriteLine(""true""); + while (true); + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do + { + Console.WriteLine(""true""); + } + while (true); + } + } +}"; + + VerifyDiagnostic(original, "A do statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_DoWhile_WithBraces() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + do + { + Console.WriteLine(""true""); + } while (true); + } + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_Lock_WithoutBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + lock (str) + return; + } +}"; + + var result = @" +class Program +{ + static void Main() + { + var str = ""test""; + lock (str) + { + return; + } + } +}"; + + VerifyDiagnostic(original, "A lock statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_Lock_WithoutBraces_NestedInLock() + { + var original = @" +class Program +{ + static void Main() + { + var str1 = ""test""; + var str2 = ""test""; + lock (str1) + lock (str2) // VS thinks this should be indented one more level + return; + } +}"; + + var result = @" +class Program +{ + static void Main() + { + var str1 = ""test""; + var str2 = ""test""; + lock (str1) + lock (str2) // VS thinks this should be indented one more level + { + return; + } + } +}"; + + VerifyDiagnostic(original, "A lock statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_Lock_WithBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + lock (str) + { + return; + } + } +}"; + + VerifyDiagnostic(original); + } + + [Ignore] // need unsafe compilation option + [TestMethod] + public void MissingBraces_Fixed_WithoutBraces() + { + var original = @" +class Point +{ + public int x; + public int y; +} + +class Program +{ + unsafe static void TestMethod() + { + var pt = new Point(); + fixed (int* p = &pt.x) + *p = 1; + } +}"; + + var result = @" +class Point +{ + public int x; + public int y; +} + +class Program +{ + unsafe static void TestMethod() + { + var pt = new Point(); + fixed (int* p = &pt.x) + { + *p = 1; + } + } +}"; + + VerifyDiagnostic(original, "A fixed statement should be written with braces"); + VerifyFix(original, result); + } + + [Ignore] // need unsafe compilation option + [TestMethod] + public void MissingBraces_Fixed_WithoutBraces_NestedInFixed() + { + var original = @" +class Point +{ + public int x; + public int y; +} + +class Program +{ + unsafe static void TestMethod() + { + var pt = new Point(); + fixed (int* p = &pt.x) + fixed (int* q = &pt.y) + *p = 1; + } +}"; + + var result = @" +class Point +{ + public int x; + public int y; +} + +class Program +{ + unsafe static void TestMethod() + { + var pt = new Point(); + fixed (int* p = &pt.x) + fixed (int* q = &pt.y) + { + *p = 1; + } + } +}"; + + VerifyDiagnostic(original, "A fixed statement should be written with braces"); + VerifyFix(original, result); + } + + [Ignore] // need unsafe compilation option + [TestMethod] + public void MissingBraces_Fixed_WithBraces() + { + var original = @" +class Point +{ + public int x; + public int y; +} + +class Program +{ + unsafe static void TestMethod() + { + var pt = new Point(); + fixed (int* p = &pt.x) + { + *p = 1; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_CaseLabel_WithoutBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + case ""test"": + return; + } + } +}"; + + var result = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + case ""test"": + { + return; + } + } + } +}"; + + VerifyDiagnostic(original, "A switch section statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_CaseLabel_WithoutBraces_ManyChildStatements() + { + var original = @" +class Program +{ + static int Test() + { + var str = ""test""; + switch (str) + { + case ""test"": + var i = 0; + i = str.GetHashCode(); + return i; + } + return 0; + } +}"; + + var result = @" +class Program +{ + static int Test() + { + var str = ""test""; + switch (str) + { + case ""test"": + { + var i = 0; + i = str.GetHashCode(); + return i; + } + } + return 0; + } +}"; + + VerifyDiagnostic(original, "A switch section statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_DefaultLabel_WithoutBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + default: + return; + } + } +}"; + + var result = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + default: + { + return; + } + } + } +}"; + + VerifyDiagnostic(original, "A switch section statement should be written with braces"); + VerifyFix(original, result); + } + + [TestMethod] + public void MissingBraces_CaseLabel_WithBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + case ""test"": + { + return; + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void MissingBraces_DefaultLabel_WithBraces() + { + var original = @" +class Program +{ + static void Main() + { + var str = ""test""; + switch (str) + { + default: + { + return; + } + } + } +}"; + + VerifyDiagnostic(original); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NonEncapsulatedOrMutableFieldTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NonEncapsulatedOrMutableFieldTests.cs index 32bb487..0d41175 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NonEncapsulatedOrMutableFieldTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NonEncapsulatedOrMutableFieldTests.cs @@ -369,6 +369,57 @@ class MyClass VerifyFix(original, result); } + [TestMethod] + public void NonEncapsulatedOrMutableField_Param() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int myField; + + void DoSomething() + { + MyMethod(myField); + } + + void MyMethod(int x) + { + + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int myField { get; set; } + + void DoSomething() + { + MyMethod(myField); + } + + void MyMethod(int x) + { + + } + } +}"; + + VerifyDiagnostic(original, string.Format(NonEncapsulatedOrMutableFieldAnalyzer.Rule.MessageFormat.ToString(), "myField")); + VerifyFix(original, result); + } + [TestMethod] public void NonEncapsulatedOrMutableField_Ref() { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/RedudantPrivateSetterTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/RedudantPrivateSetterTests.cs new file mode 100644 index 0000000..6fa9a22 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/RedudantPrivateSetterTests.cs @@ -0,0 +1,358 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.RedundantPrivateSetter; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class RedundantPrivateSetterTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new RedundantPrivateSetterAnalyzer(); + + protected override CodeFixProvider CodeFixProvider => new RedundantPrivateSetterCodeFix(); + + [TestMethod] + public void RedundantPrivateSetter() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; private set; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; } + } +}"; + + VerifyDiagnostic(original, + string.Format(RedundantPrivateSetterAnalyzer.Rule.MessageFormat.ToString(), "MyProperty")); + VerifyFix(original, result); + } + + [TestMethod] + public void RedundantPrivateSetter_ConstructorUsage() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; private set; } + + public MyClass() + { + MyProperty = 42; + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; } + + public MyClass() + { + MyProperty = 42; + } + } +}"; + + VerifyDiagnostic(original, + string.Format(RedundantPrivateSetterAnalyzer.Rule.MessageFormat.ToString(), "MyProperty")); + VerifyFix(original, result); + } + + [TestMethod] + public void RedundantPrivateSetter_NonConstructorUsage() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; private set; } + + public void MyMethod() + { + MyProperty = 42; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_ConstructorAndNonConstructorUsage() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; private set; } + + public MyClass() + { + MyProperty = 42; + } + + public void Method() + { + MyProperty = 69; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_NonPrivateAccessibility() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; protected set; } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_NoAccessibility() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; set; } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_NoSetter() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_ExpressionBodiedProperty() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty => 5; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_NonConstructorReading() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; private set; } + + public void MyMethod() + { + if(MyProperty > 5) + { + + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public int MyProperty { get; } + + public void MyMethod() + { + if(MyProperty > 5) + { + + } + } + } +}"; + + VerifyDiagnostic(original, + string.Format(RedundantPrivateSetterAnalyzer.Rule.MessageFormat.ToString(), "MyProperty")); + VerifyFix(original, result); + } + + [TestMethod] + public void RedundantPrivateSetter_PartialClass() + { + var firstTree = @" +namespace ConsoleApplication1 +{ + partial class MyClass + { + public int MyProperty { get; private set; } + + public MyClass() + { + MyProperty = 42; + } + } +}"; + + var secondTree = @" +namespace ConsoleApplication1 +{ + partial class MyClass + { + public void MyMethod() + { + MyProperty = 42; + } + } +}"; + + VerifyDiagnostic(new[] {firstTree, secondTree}); + } + + [TestMethod] + public void RedundantPrivateSetter_PartialClass_IrrelevantIdentifier() + { + var firstTree = @" +namespace ConsoleApplication1 +{ + partial class MyClass + { + public int MyProperty { get; private set; } + + public MyClass() + { + MyProperty = 42; + } + } +}"; + + var secondTree = @" +namespace ConsoleApplication1 +{ + partial class MyClass + { + public void MyMethod() + { + var MyProperty = 42; + } + } +}"; + + VerifyDiagnostic(new[] { firstTree, secondTree }, string.Format(RedundantPrivateSetterAnalyzer.Rule.MessageFormat.ToString(), "MyProperty")); + } + + [TestMethod] + public void RedundantPrivateSetter_StaticNonConstructorUsage() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public static int MyProperty { get; private set; } + + public static void MyMethod() + { + MyProperty = 42; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_Indexer() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public string this[int i] + { + get + { + return string.Empty; + } + + private set { } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void RedundantPrivateSetter_Struct() + { + var original = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + public int MyProperty { get; private set; } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + struct MyStruct + { + public int MyProperty { get; } + } +}"; + + VerifyDiagnostic(original, + string.Format(RedundantPrivateSetterAnalyzer.Rule.MessageFormat.ToString(), "MyProperty")); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SimplifyExpressionBodiedMemberTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SimplifyExpressionBodiedMemberTests.cs index 32da872..aa3b22a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SimplifyExpressionBodiedMemberTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SimplifyExpressionBodiedMemberTests.cs @@ -907,6 +907,26 @@ void MyMethod() throw new NotImplementedException(); } } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void SimplifyExpressionBodiedMember_WithVoidMethod_EmptyReturn() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void MyMethod() + { + return; + } + } }"; VerifyDiagnostic(original); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchDoesNotHandleAllEnumOptionsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchDoesNotHandleAllEnumOptionsTests.cs new file mode 100644 index 0000000..4a39f9f --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchDoesNotHandleAllEnumOptionsTests.cs @@ -0,0 +1,1019 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.SwitchDoesNotHandleAllEnumOptions; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class SwitchDoesNotHandleAllEnumOptionsTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new SwitchDoesNotHandleAllEnumOptionsAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new SwitchDoesNotHandleAllEnumOptionsCodeFix(); + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new System.NotImplementedException(); + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_AllEnumStatements() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + case MyEnum.FizzBuzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_CaseStatementsNotEnum() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch (""test"") + { + case ""Fizz"": + case ""Buzz"": + case ""FizzBuzz"": + break; + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_CaseHasDefaultStatement_NewStatementsAreAddedAboveDefault() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + default: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new System.NotImplementedException(); + case MyEnum.Buzz: + throw new System.NotImplementedException(); + case MyEnum.Fizz: + throw new System.NotImplementedException(); + default: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement_MultipleSections() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + break; + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new System.NotImplementedException(); + case MyEnum.Fizz: + break; + case MyEnum.Buzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatements() + { + var original = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Asynchronous: + case DeleteOnClose: + break; + } + } + } +}"; + + var result = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Encrypted: + throw new System.NotImplementedException(); + case SequentialScan: + throw new System.NotImplementedException(); + case RandomAccess: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + case Asynchronous: + case DeleteOnClose: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement_AddsAllMissingStatements() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Buzz: + break; + default: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new System.NotImplementedException(); + case MyEnum.Fizz: + throw new System.NotImplementedException(); + case MyEnum.Buzz: + break; + default: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_NoEnumStatements() + { + var original = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + } + } + } +}"; + + var result = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Encrypted: + throw new System.NotImplementedException(); + case SequentialScan: + throw new System.NotImplementedException(); + case DeleteOnClose: + throw new System.NotImplementedException(); + case RandomAccess: + throw new System.NotImplementedException(); + case Asynchronous: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatement_MixedExpandedEnumStatements() + { + var original = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case FileOptions.Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + var result = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case DeleteOnClose: + throw new System.NotImplementedException(); + case Asynchronous: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + case Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result, allowedNewCompilerDiagnosticsId: "CS8019"); // unneeded using directive + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatement_AllExpandedEnumStatements() + { + var original = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case FileOptions.Encrypted: + break; + case FileOptions.SequentialScan: + break; + case FileOptions.RandomAccess: + break; + } + } + } +}"; + + var result = @" +using System.IO; +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case FileOptions.DeleteOnClose: + throw new System.NotImplementedException(); + case FileOptions.Asynchronous: + throw new System.NotImplementedException(); + case FileOptions.WriteThrough: + throw new System.NotImplementedException(); + case FileOptions.None: + throw new System.NotImplementedException(); + case FileOptions.Encrypted: + break; + case FileOptions.SequentialScan: + break; + case FileOptions.RandomAccess: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement_NoRedundantQualifierIfUsingSystemDirectiveExists() + { + var original = @" +using System; +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +using System; +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new NotImplementedException(); + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement_UsingAliasForSystem() + { + var original = @" +using Fizz = System; // seriously... +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +using Fizz = System; // seriously... +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new Fizz.NotImplementedException(); + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatement_SimplifiesAllStatementsWhenParentDirectiveNotIncluded() + { + var original = @" +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + var result = @" +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case DeleteOnClose: + throw new System.NotImplementedException(); + case Asynchronous: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + case Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatement_SimplifiesAllStatementsWhenUsingDirectiveIncludesWhitespace() + { + var original = @" +using static System . IO . FileOptions; // Happy maintaining + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + var result = @" +using static System . IO . FileOptions; // Happy maintaining + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case DeleteOnClose: + throw new System.NotImplementedException(); + case Asynchronous: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + case Encrypted: + break; + case SequentialScan: + break; + case RandomAccess: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatement_NestedEnum() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.FizzBuzz: + throw new System.NotImplementedException(); + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_MissingEnumStatements_CaseValueIsCastToEnumType() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case (MyEnum) 0: + break; + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_NoCaseStatements_NoUsings() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = System.IO.FileOptions.DeleteOnClose; + switch (e) + { + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = System.IO.FileOptions.DeleteOnClose; + switch (e) + { + case System.IO.FileOptions.Encrypted: + throw new System.NotImplementedException(); + case System.IO.FileOptions.SequentialScan: + throw new System.NotImplementedException(); + case System.IO.FileOptions.DeleteOnClose: + throw new System.NotImplementedException(); + case System.IO.FileOptions.RandomAccess: + throw new System.NotImplementedException(); + case System.IO.FileOptions.Asynchronous: + throw new System.NotImplementedException(); + case System.IO.FileOptions.WriteThrough: + throw new System.NotImplementedException(); + case System.IO.FileOptions.None: + throw new System.NotImplementedException(); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_NoCaseStatements_NormalUsing() + { + var original = @" +using System.IO; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = FileOptions.DeleteOnClose; + switch (e) + { + } + } + } +}"; + + var result = @" +using System.IO; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = FileOptions.DeleteOnClose; + switch (e) + { + case FileOptions.Encrypted: + throw new System.NotImplementedException(); + case FileOptions.SequentialScan: + throw new System.NotImplementedException(); + case FileOptions.DeleteOnClose: + throw new System.NotImplementedException(); + case FileOptions.RandomAccess: + throw new System.NotImplementedException(); + case FileOptions.Asynchronous: + throw new System.NotImplementedException(); + case FileOptions.WriteThrough: + throw new System.NotImplementedException(); + case FileOptions.None: + throw new System.NotImplementedException(); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchDoesNotHandleAllEnumOptions_NoCaseStatements_UsingStatic() + { + var original = @" +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + } + } + } +}"; + + var result = @" +using static System.IO.FileOptions; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = DeleteOnClose; + switch (e) + { + case Encrypted: + throw new System.NotImplementedException(); + case SequentialScan: + throw new System.NotImplementedException(); + case DeleteOnClose: + throw new System.NotImplementedException(); + case RandomAccess: + throw new System.NotImplementedException(); + case Asynchronous: + throw new System.NotImplementedException(); + case WriteThrough: + throw new System.NotImplementedException(); + case None: + throw new System.NotImplementedException(); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchIsMissingDefaultLabelTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchIsMissingDefaultLabelTests.cs new file mode 100644 index 0000000..5101334 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchIsMissingDefaultLabelTests.cs @@ -0,0 +1,357 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.SwitchIsMissingDefaultLabel; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class SwitchIsMissingDefaultLabelTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new SwitchIsMissingDefaultLabelAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new SwitchIsMissingDefaultLabelCodeFix(); + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnEnum() + { + var original = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + enum MyEnum + { + Fizz, Buzz, FizzBuzz + } + + class MyClass + { + void Method() + { + var e = MyEnum.Fizz; + switch (e) + { + case MyEnum.Fizz: + case MyEnum.Buzz: + break; + default: + throw new System.ArgumentException(nameof(e)); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnString() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = ""test""; + switch (e) + { + case ""test"": + case ""test1"": + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = ""test""; + switch (e) + { + case ""test"": + case ""test1"": + break; + default: + throw new System.ArgumentException(nameof(e)); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnStringLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch (""test"") + { + case ""test"": + case ""test1"": + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch (""test"") + { + case ""test"": + case ""test1"": + break; + default: + throw new System.ArgumentException(""test""); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnIntegerLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch (0) + { + case 0: + case 1: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch (0) + { + case 0: + case 1: + break; + default: + throw new System.ArgumentException(0.ToString()); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_HasDefaultStatement() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var e = ""test""; + switch (e) + { + case ""test"": + case ""test1"": + default: + break; + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_ParenthesizedStatement() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var x = 5; + switch ((x)) + { + case 5: + case 6: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var x = 5; + switch ((x)) + { + case 5: + case 6: + break; + default: + throw new System.ArgumentException(nameof(x)); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnParenthsizedStringLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch ((""test"")) + { + case ""test"": + case ""test1"": + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch ((""test"")) + { + case ""test"": + case ""test1"": + break; + default: + throw new System.ArgumentException((""test"").ToString()); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + + [TestMethod] + public void SwitchIsMissingDefaultLabel_MissingDefaultStatement_SwitchOnParenthsizedIntegerLiteral() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch ((0)) + { + case 0: + case 1: + break; + } + } + } +}"; + + var result = @" +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + switch ((0)) + { + case 0: + case 1: + break; + default: + throw new System.ArgumentException((0).ToString()); + } + } + } +}"; + + VerifyDiagnostic(original, SwitchIsMissingDefaultLabelAnalyzer.Rule.MessageFormat.ToString()); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TryCastUsingAsNotNullInsteadOfIsAsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TryCastUsingAsNotNullInsteadOfIsAsTests.cs index 74c70bc..a0a19b8 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TryCastUsingAsNotNullInsteadOfIsAsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TryCastUsingAsNotNullInsteadOfIsAsTests.cs @@ -456,52 +456,6 @@ void Method() VerifyDiagnostic(original); } - [TestMethod] - public void TryCastWithoutUsingAsNotNull_ChainedVariableDeclaration() - { - var original = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - object o = 5; - if (o is int) - { - int? oAsInt = o as int?, x = 10; - } - } - } -}"; - - var expected = @" -using System; -using System.Text; - -namespace ConsoleApplication1 -{ - class MyClass - { - void Method() - { - object o = 5; - var oAsInt32 = o as int?; - if (oAsInt32 != null) - { - int? x = 10; - } - } - } -}"; - - VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); - VerifyFix(original, expected); - } - [TestMethod] public void TryCastWithoutUsingAsNotNull_TryCastNullCheck() { @@ -570,7 +524,8 @@ void Method() } }"; - VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o"), + VerifyDiagnostic(original, + string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o"), string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); VerifyFix(original, expected); } @@ -1751,5 +1706,577 @@ void Method(object o) VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); VerifyFix(original, expected); } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_TernaryOperator() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var x = o is int ? 5 : 6; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_TernaryOperator_WithCasts() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var x = o is int ? ((int) o) : ((double) o); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_Return() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + object Method(object o) + { + return o is int ? 5 : 6; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_Return_WithCasts() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + object Method(object o) + { + return o is int ? ((int) o) : ((double) o); + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_Return_IsStatement() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + bool Method(object o) + { + return o is int; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_Local() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var isInt = o is int; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_IrrelevantIfParent() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (true) + { + var res = o is int ? ((int)o) : ((double)o); + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_IrrelevantIfParent_2() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (true) + { + if (true) + { + var res = o is int ? ((int)o) : ((double)o); + } + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_IrrelevantIfAncestor() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + if (true) + { + if (o is string) + { + string oAsString = o as string; + } + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + if (true) + { + var oAsString = o as string; + if (oAsString != null) + { + } + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_Trivia() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + if (o is string) + { + // Test + string oAsString = o as string; // Test + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + var oAsString = o as string; + if (oAsString != null) + { + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_MultipleDeclarators() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + if (o is string) + { + string oAsString = o as string, s = ""test""; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + object o = ""sample""; + var oAsString = o as string; + if (oAsString != null) + { + string s = ""test""; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_TernaryWithAppropriateIfCondition() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + int x, y; + + void Method(object o) + { + if (o is int) + { + var res = x > y ? (int) o : (double) o; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + int x, y; + + void Method(object o) + { + var oAsInt32 = o as int?; + if (oAsInt32 != null) + { + var res = x > y ? oAsInt32.Value : (double) o; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_TernaryWithAppropriateIfCondition_RepeatedCheck() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (o is int) + { + var res = o is int ? (int) o : (double) o; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var oAsInt32 = o as int?; + if (oAsInt32 != null) + { + var res = o is int ? oAsInt32.Value : (double) o; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_DirectCastAndTryCast() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (o is int) + { + var res = o is int ? (int) o : o as double?; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var oAsInt32 = o as int?; + if (oAsInt32 != null) + { + var res = o is int ? oAsInt32.Value : o as double?; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_RedundantCheck() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (o is int) + { + var x = (int) o; + var y = o is int; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var oAsInt32 = o as int?; + if (oAsInt32 != null) + { + var y = o is int; + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_While() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (true) + { + while(o is int) + { + var x = o as int?; + o = ""test""; + } + } + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TryCastWithoutUsingAsNotNull_NegativeCondition() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + if (!(o is int)) + { + var x = o as int?; + } + } + } +}"; + + var expected = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(object o) + { + var oAsInt32 = o as int?; + if (!(oAsInt32 != null)) + { + } + } + } +}"; + + VerifyDiagnostic(original, string.Format(TryCastWithoutUsingAsNotNullAnalyzer.Rule.MessageFormat.ToString(), "o")); + VerifyFix(original, expected); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TypeToVarTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TypeToVarTests.cs index 6d80e32..cfd134f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TypeToVarTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/TypeToVarTests.cs @@ -280,5 +280,46 @@ void Method() VerifyDiagnostic(original); } + + [TestMethod] + public void TypeToVar_WithLambdaExpression_NoType() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + Action x = () => { }; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TypeToVar_WithLocalDefinedWithDynamic() + { + var original = @" +using System.Dynamic; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + dynamic exceptionInfo = new ExpandoObject(); + } + } +}"; + + VerifyDiagnostic(original); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/UseAliasesInsteadOfConcreteTypeTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/UseAliasesInsteadOfConcreteTypeTests.cs index 4bbc275..ac632e4 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/UseAliasesInsteadOfConcreteTypeTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/UseAliasesInsteadOfConcreteTypeTests.cs @@ -940,7 +940,7 @@ namespace ConsoleApplication1 { class MyClass { - public static explicit operator int(MyClass c) + public static explicit operator int (MyClass c) { return 5; } @@ -1571,7 +1571,7 @@ static void Main() { } } [TestMethod] - public void UseAliasesInsteadOfConcreteType_DisplaysCorrectWarning() + public void UseAliasesInsteadOfConcreteType_DoesNotDisplayWarningWithAlias() { var original = @" using Single = System.String; @@ -1587,22 +1587,7 @@ static void Main() } }"; - var result = @" -using Single = System.String; - -namespace ConsoleApplication1 -{ - class Foo - { - static void Main() - { - string bar = ""test""; - } - } -}"; - - VerifyDiagnostic(original, string.Format(UseAliasesInsteadOfConcreteTypeAnalyzer.Rule.MessageFormat.ToString(), "string", "String")); - VerifyFix(original, result, allowNewCompilerDiagnostics: true); + VerifyDiagnostic(original); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructMutateSelfTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructMutateSelfTests.cs index 5f09140..30444be 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructMutateSelfTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructMutateSelfTests.cs @@ -1,7 +1,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; using RoslynTester.Helpers.CSharp; -using VSDiagnostics.Diagnostics.Structs; using VSDiagnostics.Diagnostics.Structs.StructShouldNotMutateSelf; namespace VSDiagnostics.Test.Tests.Structs diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructWithoutElementaryMethodsOverriddenTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructWithoutElementaryMethodsOverriddenTests.cs new file mode 100644 index 0000000..5b1ba0f --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Structs/StructWithoutElementaryMethodsOverriddenTests.cs @@ -0,0 +1,216 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Structs.StructWithoutElementaryMethodsOverridden; + +namespace VSDiagnostics.Test.Tests.Structs +{ + [TestClass] + public class StructWithoutElementaryMethodsOverriddenTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new StructWithoutElementaryMethodsOverriddenAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new StructWithoutElementaryMethodsOverriddenCodeFix(); + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_NoMethodsImplemented() + { + var original = @" +struct X +{ +}"; + + var result = @" +struct X +{ + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + + public override string ToString() + { + throw new System.NotImplementedException(); + } +}"; + + VerifyDiagnostic(original, string.Format(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.MessageFormat.ToString(), "X")); + VerifyFix(original, result); + } + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_EqualsImplemented() + { + var original = @" +struct X +{ + public override bool Equals(object obj) + { + return false; + } +}"; + + var result = @" +struct X +{ + public override bool Equals(object obj) + { + return false; + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } + + public override string ToString() + { + throw new System.NotImplementedException(); + } +}"; + + VerifyDiagnostic(original, string.Format(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.MessageFormat.ToString(), "X")); + VerifyFix(original, result); + } + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_GetHashCodeImplemented() + { + var original = @" +struct X +{ + public override int GetHashCode() + { + return 0; + } +}"; + + var result = @" +struct X +{ + public override int GetHashCode() + { + return 0; + } + + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override string ToString() + { + throw new System.NotImplementedException(); + } +}"; + + VerifyDiagnostic(original, string.Format(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.MessageFormat.ToString(), "X")); + VerifyFix(original, result); + } + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_ToStringImplemented() + { + var original = @" +struct X +{ + public override string ToString() + { + return string.Empty; + } +}"; + + var result = @" +struct X +{ + public override string ToString() + { + return string.Empty; + } + + public override bool Equals(object obj) + { + throw new System.NotImplementedException(); + } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } +}"; + + VerifyDiagnostic(original, string.Format(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.MessageFormat.ToString(), "X")); + VerifyFix(original, result); + } + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_EqualsAndGetHashCodeImplemented() + { + var original = @" +struct X +{ + public override bool Equals(object obj) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } +}"; + + var result = @" +struct X +{ + public override bool Equals(object obj) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public override string ToString() + { + throw new System.NotImplementedException(); + } +}"; + + VerifyDiagnostic(original, string.Format(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.MessageFormat.ToString(), "X")); + VerifyFix(original, result); + } + + [TestMethod] + public void StructWithoutElementaryMethodsOverridden_AllImplemented() + { + var original = @" +struct X +{ + public override bool Equals(object obj) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public override string ToString() + { + return string.Empty; + } +}"; + + VerifyDiagnostic(original); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Tests/TestMethodWithoutTestAttributeTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Tests/TestMethodWithoutTestAttributeTests.cs new file mode 100644 index 0000000..43d89ee --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Tests/TestMethodWithoutTestAttributeTests.cs @@ -0,0 +1,187 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.Tests.TestMethodWithoutTestAttribute; + +namespace VSDiagnostics.Test.Tests.Tests +{ + [TestClass] + public class TestMethodWithoutTestAttributeTests : CSharpDiagnosticVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new TestMethodWithoutTestAttributeAnalyzer(); + + [TestMethod] + public void TestMethodWithoutTestAttribute_MSTest() + { + var original = @" +namespace ConsoleApplication1 +{ + [TestClass] + class MyClass + { + public void MyMethod() + { + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_NUnit() + { + var original = @" +namespace ConsoleApplication1 +{ + [TestFixture] + class MyClass + { + public void MyMethod() + { + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_XUnit_NoOtherMethodsWithAttribute() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public void MyMethod() + { + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_XUnit_OtherMethodWithAttribute() + { + var original = @" +namespace ConsoleApplication1 +{ + class MyClass + { + public void MyMethod() + { + } + + [Fact] + public void MyOtherMethod() + { + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_struct() + { + var original = @" +namespace ConsoleApplication1 +{ + [TestClass] + struct MyStruct + { + public void MyMethod() + { + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_TaskReturn() + { + var original = @" +using System.Threading.Tasks; + +namespace ConsoleApplication1 +{ + [TestClass] + class MyClass + { + public async Task MyMethod() + { + await Task.Delay(0); + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_TaskTReturn() + { + var original = @" +using System.Threading.Tasks; + +namespace ConsoleApplication1 +{ + [TestClass] + class MyClass + { + public Task MyMethod() + { + return Task.FromResult(5); + } + } +}"; + + VerifyDiagnostic(original, "Method MyMethod might be missing a test attribute"); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_OtherReturnType() + { + var original = @" +namespace ConsoleApplication1 +{ + [TestClass] + class MyClass + { + public int MyMethod() + { + return 5; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void TestMethodWithoutTestAttribute_OtherAttribute() + { + var original = @" +namespace ConsoleApplication1 +{ + [TestClass] + class MyClass + { + [SomethingElse] + public int MyMethod() + { + return 5; + } + } +}"; + + VerifyDiagnostic(original); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/ExtensionsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/ExtensionsTests.cs index 6466588..d220df5 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/ExtensionsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/ExtensionsTests.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/NamingConventionsTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/NamingConventionsTests.cs index bc40e74..15407c1 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/NamingConventionsTests.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/Utilities/NamingConventionsTests.cs @@ -1,7 +1,6 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using VSDiagnostics.Diagnostics.General.NamingConventions; -using VSDiagnostics.Utilities; namespace VSDiagnostics.Test.Tests.Utilities { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj index 107bd62..c8e7970 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj @@ -10,7 +10,7 @@ Properties VSDiagnostics.Test VSDiagnostics.Test - v4.5 + v4.5.2 512 @@ -63,7 +63,7 @@ True - ..\..\packages\RoslynTester.1.6.2\lib\RoslynTester.dll + ..\..\packages\RoslynTester.1.6.3\lib\RoslynTester.dll True @@ -71,24 +71,24 @@ ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll True - - ..\..\packages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll True - - ..\..\packages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll True - - ..\..\packages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll True - - ..\..\packages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll True - - ..\..\packages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll True @@ -103,40 +103,47 @@ + + - - - - - - + + + + + + - - + + + + + - - + + + + @@ -144,9 +151,11 @@ + + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/app.config b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/app.config index 4d5b6d9..aec9512 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/app.config +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/app.config @@ -1,35 +1,50 @@  - - + - + - + - + - + - + + + + + + + + + + + + + + + + + - + - \ No newline at end of file + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config index ef1dae9..2a8bbab 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config @@ -1,15 +1,30 @@  - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/VSDiagnostics.Vsix.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/VSDiagnostics.Vsix.csproj index f4eea85..516cbed 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/VSDiagnostics.Vsix.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/VSDiagnostics.Vsix.csproj @@ -1,8 +1,14 @@  - 14.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + 14.0 @@ -15,7 +21,7 @@ Properties VSDiagnostics VSDiagnostics - v4.5 + v4.5.2 false false false @@ -47,7 +53,7 @@ /rootsuffix Roslyn - + Designer @@ -59,67 +65,7 @@ - - ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll - True - - - ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll - True - - - ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - - - ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - - - diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/app.config b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/app.config new file mode 100644 index 0000000..da592df --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/packages.config b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/packages.config deleted file mode 100644 index 5eebdf3..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/packages.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/source.extension.vsixmanifest b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/source.extension.vsixmanifest index cf222c6..45733fa 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/source.extension.vsixmanifest +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Vsix/source.extension.vsixmanifest @@ -2,25 +2,28 @@ - - - VSDiagnostics.Vsix - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - + + + VSDiagnostics.Vsix + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostic.nuspec b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostic.nuspec index 1328224..2e0d656 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostic.nuspec +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostic.nuspec @@ -2,7 +2,7 @@ VSDiagnostics - 1.9.3 + 1.9.4 VSDiagnostics Jeroen Vannevel Jeroen Vannevel @@ -10,7 +10,7 @@ https://github.com/Vannevelj/VSDiagnostics false A collection of code-quality analyzers based on the new Roslyn platform. This project aims to ensure code-quality as you type it in your editor rather than having to do this as a separate build-step. - For the latest releasenotes, see the Github release: https://github.com/Vannevelj/VSDiagnostics/releases/tag/v1.9.3 + For the latest releasenotes, see the Github release: https://github.com/Vannevelj/VSDiagnostics/releases/tag/v1.9.4 Jeroen Vannevel Visual Studio, Roslyn, Diagnostics, Analyzers @@ -18,6 +18,7 @@ + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Arithmetic/DivideIntegerByInteger/DivideIntegerByIntegerAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Arithmetic/DivideIntegerByInteger/DivideIntegerByIntegerAnalyzer.cs new file mode 100644 index 0000000..32215a4 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Arithmetic/DivideIntegerByInteger/DivideIntegerByIntegerAnalyzer.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Arithmetic.DivideIntegerByInteger +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class DivideIntegerByIntegerAnalyzer : DiagnosticAnalyzer + { + private static readonly SpecialType[] IntegerTypes = + { + SpecialType.System_Byte, SpecialType.System_Int16, SpecialType.System_Int32, SpecialType.System_Int64, + SpecialType.System_SByte, SpecialType.System_UInt16, SpecialType.System_UInt32, SpecialType.System_UInt64 + }; + + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + private static readonly string Category = VSDiagnosticsResources.ArithmeticCategory; + private static readonly string Message = VSDiagnosticsResources.DivideIntegerByIntegerAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.DivideIntegerByIntegerAnalyzerTitle; + + internal static DiagnosticDescriptor Rule => + new DiagnosticDescriptor(DiagnosticId.DivideIntegerByInteger, Title, Message, Category, Severity, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.DivideExpression); + + private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + { + var divideExpression = (BinaryExpressionSyntax) context.Node; + var leftType = context.SemanticModel.GetTypeInfo(divideExpression.Left).Type; + if (leftType == null) + { + return; + } + + if (IntegerTypes.Contains(leftType.SpecialType)) + { + var rightType = context.SemanticModel.GetTypeInfo(divideExpression.Right).Type; + if (rightType == null) + { + return; + } + + if (IntegerTypes.Contains(rightType.SpecialType)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, divideExpression.GetLocation(), divideExpression.ToString())); + } + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeAnalyzer.cs new file mode 100644 index 0000000..b04fc43 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeAnalyzer.cs @@ -0,0 +1,84 @@ +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.Async.AsyncMethodWithVoidReturnType +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class AsyncMethodWithVoidReturnTypeAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.AsyncCategory; + private static readonly string Message = VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeMessage; + private static readonly string Title = VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeTitle; + + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.AsyncMethodWithVoidReturnType, Title, Message, Category, Severity, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration); + + private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + { + var method = (MethodDeclarationSyntax) context.Node; + + // Method has to return void + var returnType = context.SemanticModel.GetTypeInfo(method.ReturnType); + if (returnType.Type == null || returnType.Type.SpecialType != SpecialType.System_Void) + { + return; + } + + var isAsync = false; + foreach (var modifier in method.Modifiers) + { + // Method has to be marked async + if (modifier.IsKind(SyntaxKind.AsyncKeyword)) + { + isAsync = true; + } + + // Partial methods can only have a void return type + if (modifier.IsKind(SyntaxKind.PartialKeyword)) + { + return; + } + } + + if (!isAsync) + { + return; + } + + // Event handlers can only have a void return type + if (method.ParameterList?.Parameters.Count == 2) + { + var parameters = method.ParameterList.Parameters; + var firstArgumentType = context.SemanticModel.GetTypeInfo(parameters[0].Type); + var isFirstArgumentObject = firstArgumentType.Type != null && + firstArgumentType.Type.SpecialType == SpecialType.System_Object; + + + var secondArgumentType = context.SemanticModel.GetTypeInfo(parameters[1].Type); + var isSecondArgumentEventArgs = secondArgumentType.Type != null && + secondArgumentType.Type.InheritsFrom(typeof(EventArgs)); + + if (isFirstArgumentObject && isSecondArgumentEventArgs) + { + return; + } + } + + context.ReportDiagnostic(Diagnostic.Create(Rule, method.GetLocation(), method.Identifier.ValueText)); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs new file mode 100644 index 0000000..3af654d --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithVoidReturnType/AsyncMethodWithVoidReturnTypeCodeFix.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Async.AsyncMethodWithVoidReturnType +{ + [ExportCodeFixProvider(DiagnosticId.AsyncMethodWithVoidReturnType + "CF", LanguageNames.CSharp), Shared] + public class AsyncMethodWithVoidReturnTypeCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(AsyncMethodWithVoidReturnTypeAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + var methodDeclaration = + root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeCodeFixTitle, + x => ChangeReturnTypeAsync(context.Document, methodDeclaration, root, x), + AsyncMethodWithVoidReturnTypeAnalyzer.Rule.Id), + diagnostic); + } + + private Task ChangeReturnTypeAsync(Document document, MethodDeclarationSyntax methodDeclaration, SyntaxNode root, CancellationToken cancellationToken) + { + var newMethod = methodDeclaration.WithReturnType(SyntaxFactory.ParseTypeName("Task").WithAdditionalAnnotations(Formatter.Annotation)); + var newRoot = root.ReplaceNode(methodDeclaration, newMethod); + var newDocument = document.WithSyntaxRoot(newRoot); + + return Task.FromResult(newDocument); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixAnalyzer.cs index 0b0a36a..77ec6de 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,18 +23,11 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration); private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { - var method = context.Node as MethodDeclarationSyntax; - if (method == null) - { - return; - } + var method = (MethodDeclarationSyntax) context.Node; if (method.Modifiers.Any(SyntaxKind.OverrideKeyword)) { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixCodeFix.cs index 8a0df97..9540336 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/AsyncMethodWithoutAsyncSuffix/AsyncMethodWithoutAsyncSuffixCodeFix.cs @@ -11,7 +11,7 @@ namespace VSDiagnostics.Diagnostics.Async.AsyncMethodWithoutAsyncSuffix { - [ExportCodeFixProvider(nameof(AsyncMethodWithoutAsyncSuffixCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.AsyncMethodWithoutAsyncSuffix + "CF", LanguageNames.CSharp), Shared] public class AsyncMethodWithoutAsyncSuffixCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -29,15 +29,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.AsyncMethodWithoutAsyncSuffixCodeFixTitle, - x => AddSuffixAsync(context.Document, methodDeclaration, root, context.CancellationToken), + x => AddSuffixAsync(context.Document, methodDeclaration, root, x), AsyncMethodWithoutAsyncSuffixAnalyzer.Rule.Id), diagnostic); } private async Task AddSuffixAsync(Document document, MethodDeclarationSyntax methodDeclaration, SyntaxNode root, - CancellationToken cancellationToken) - { - return await RenameHelper.RenameSymbolAsync(document, root, methodDeclaration.Identifier, methodDeclaration.Identifier.Text + "Async", cancellationToken); - } + CancellationToken cancellationToken) + => await RenameHelper.RenameSymbolAsync(document, root, methodDeclaration.Identifier, methodDeclaration.Identifier.Text + "Async", cancellationToken); } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixAnalyzer.cs index e7fdbad..79c1a98 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixAnalyzer.cs @@ -1,12 +1,11 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; -namespace VSDiagnostics.Diagnostics.Async.SyncMethodWithSyncSuffix +namespace VSDiagnostics.Diagnostics.Async.SyncMethodWithAsyncSuffix { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class SyncMethodWithAsyncSuffixAnalyzer : DiagnosticAnalyzer @@ -14,28 +13,19 @@ public class SyncMethodWithAsyncSuffixAnalyzer : DiagnosticAnalyzer private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private static readonly string Category = VSDiagnosticsResources.AsyncCategory; - private static readonly string Message = VSDiagnosticsResources.SyncMethodWithSyncSuffixAnalyzerMessage; - private static readonly string Title = VSDiagnosticsResources.SyncMethodWithSyncSuffixAnalyzerTitle; + private static readonly string Message = VSDiagnosticsResources.SyncMethodWithAsyncSuffixAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.SyncMethodWithAsyncSuffixAnalyzerTitle; internal static DiagnosticDescriptor Rule - => - new DiagnosticDescriptor(DiagnosticId.SyncMethodWithAsyncSuffix, Title, Message, Category, Severity, - isEnabledByDefault: true); + => new DiagnosticDescriptor(DiagnosticId.SyncMethodWithAsyncSuffix, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration); private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { - var method = context.Node as MethodDeclarationSyntax; - if (method == null) - { - return; - } + var method = (MethodDeclarationSyntax) context.Node; if (method.Modifiers.Any(SyntaxKind.OverrideKeyword)) { @@ -62,7 +52,7 @@ private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) if (!declaredSymbol.IsAsync() && method.Identifier.Text.EndsWith("Async")) { context.ReportDiagnostic(Diagnostic.Create(Rule, method.Identifier.GetLocation(), - method.Identifier.Text)); + method.Identifier.Text)); } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixCodeFix.cs index c5c5041..3970fca 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Async/SyncMethodWithAsyncSuffix/SyncMethodWithAsyncSuffixCodeFix.cs @@ -9,10 +9,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using VSDiagnostics.Utilities; -namespace VSDiagnostics.Diagnostics.Async.SyncMethodWithSyncSuffix +namespace VSDiagnostics.Diagnostics.Async.SyncMethodWithAsyncSuffix { - - [ExportCodeFixProvider(nameof(SyncMethodWithAsyncSuffixCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.SyncMethodWithAsyncSuffix + "CF", LanguageNames.CSharp), Shared] public class SyncMethodWithAsyncSuffixCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -27,14 +26,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.SyncMethodWithSyncSuffixAnalyzerCodeFixTitle, - x => RemoveSuffixAsync(context.Document, methodDeclaration, root, context.CancellationToken), + CodeAction.Create(VSDiagnosticsResources.SyncMethodWithAsyncSuffixAnalyzerCodeFixTitle, + x => RemoveSuffixAsync(context.Document, methodDeclaration, root, x), SyncMethodWithAsyncSuffixAnalyzer.Rule.Id), diagnostic); } private async Task RemoveSuffixAsync(Document document, MethodDeclarationSyntax methodDeclaration, SyntaxNode root, - CancellationToken cancellationToken) + CancellationToken cancellationToken) { var origMethodName = methodDeclaration.Identifier.Text; var newMethodName = origMethodName.Substring(0, origMethodName.Length - "Async".Length); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListAnalyzer.cs index 46bca99..7a1fd2f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListAnalyzer.cs @@ -1,15 +1,13 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; -using CSharpSyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; -using VisualBasicSyntaxKind = Microsoft.CodeAnalysis.VisualBasic.SyntaxKind; -using CSharpAttributeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.AttributeSyntax; -using VisualBasicAttributeSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.AttributeSyntax; namespace VSDiagnostics.Diagnostics.Attributes.AttributeWithEmptyArgumentList { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AttributeWithEmptyArgumentListAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; @@ -25,40 +23,23 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeCSharpSymbol, CSharpSyntaxKind.Attribute); - context.RegisterSyntaxNodeAction(AnalyzeVisualBasicSymbol, VisualBasicSyntaxKind.Attribute); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeCSharpSymbol, SyntaxKind.Attribute); private void AnalyzeCSharpSymbol(SyntaxNodeAnalysisContext context) { - var attributeExpression = context.Node as CSharpAttributeSyntax; + var attributeSyntax = (AttributeSyntax) context.Node; // attribute must have arguments // if there are no parenthesis, the ArgumentList is null // if there are empty parenthesis, the ArgumentList is empty - if (attributeExpression?.ArgumentList == null || attributeExpression.ArgumentList.Arguments.Any()) + if (attributeSyntax.ArgumentList == null || attributeSyntax.ArgumentList.Arguments.Any()) { return; } - context.ReportDiagnostic(Diagnostic.Create(Rule, attributeExpression.GetLocation())); - } - - private void AnalyzeVisualBasicSymbol(SyntaxNodeAnalysisContext context) - { - var attributeExpression = context.Node as VisualBasicAttributeSyntax; - - // attribute must have arguments - // if there are no parenthesis, the ArgumentList is null - // if there are empty parenthesis, the ArgumentList is empty - if (attributeExpression?.ArgumentList == null || attributeExpression.ArgumentList.Arguments.Any()) - { - return; - } + var attributeName = attributeSyntax.Name.ToString(); - context.ReportDiagnostic(Diagnostic.Create(Rule, attributeExpression.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(Rule, attributeSyntax.GetLocation(), attributeName)); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListCodeFix.cs index 3eaa71f..4c2224f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/AttributeWithEmptyArgumentList/AttributeWithEmptyArgumentListCodeFix.cs @@ -5,13 +5,12 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using CSharpAttributeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.AttributeSyntax; -using VisualBasicAttributeSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.AttributeSyntax; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Attributes.AttributeWithEmptyArgumentList { - [ExportCodeFixProvider(nameof(AttributeWithEmptyArgumentListCodeFix), LanguageNames.CSharp, - LanguageNames.VisualBasic), Shared] + [ExportCodeFixProvider(DiagnosticId.AttributeWithEmptyArgumentList + "CF", LanguageNames.CSharp), Shared] public class AttributeWithEmptyArgumentListCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -34,30 +33,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private Task RemoveEmptyArgumentListAsync(Document document, SyntaxNode root, SyntaxNode statement) { - SyntaxNode newRoot = null; - - if (statement is CSharpAttributeSyntax) - { - newRoot = RemoveEmptyArgumentListCSharp(root, (CSharpAttributeSyntax) statement); - } - else if (statement is VisualBasicAttributeSyntax) - { - newRoot = RemoveEmptyArgumentListVisualBasic(root, (VisualBasicAttributeSyntax) statement); - } - + var newRoot = root.RemoveNode(((AttributeSyntax) statement).ArgumentList, SyntaxRemoveOptions.KeepNoTrivia); var newDocument = document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument.Project.Solution); } - - private SyntaxNode RemoveEmptyArgumentListCSharp(SyntaxNode root, CSharpAttributeSyntax attributeExpression) - { - return root.RemoveNode(attributeExpression.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia); - } - - private SyntaxNode RemoveEmptyArgumentListVisualBasic(SyntaxNode root, - VisualBasicAttributeSyntax attributeExpression) - { - return root.RemoveNode(attributeExpression.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia); - } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeAnalyzer.cs index 5529940..2d7c208 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeAnalyzer.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; -using CSharpSyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; -using VisualBasicSyntaxKind = Microsoft.CodeAnalysis.VisualBasic.SyntaxKind; namespace VSDiagnostics.Diagnostics.Attributes.EnumCanHaveFlagsAttribute { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + [DiagnosticAnalyzer(LanguageNames.CSharp)] public class EnumCanHaveFlagsAttributeAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Hidden; @@ -25,47 +22,22 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeCSharpSymbol, CSharpSyntaxKind.EnumDeclaration); - context.RegisterSyntaxNodeAction(AnalyzeVisualBasicSymbol, VisualBasicSyntaxKind.EnumStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.EnumDeclaration); - private void AnalyzeCSharpSymbol(SyntaxNodeAnalysisContext context) + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var enumDeclaration = (EnumDeclarationSyntax) context.Node; - // enum must not already have flags attribute - if (enumDeclaration.AttributeLists.Any( - a => a.Attributes.Any( - t => - { - var symbol = context.SemanticModel.GetSymbolInfo(t).Symbol; - - return symbol == null || symbol.ContainingType.MetadataName == typeof (FlagsAttribute).Name; - }))) + foreach (var list in enumDeclaration.AttributeLists) { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, enumDeclaration.GetLocation())); - } - - private void AnalyzeVisualBasicSymbol(SyntaxNodeAnalysisContext context) - { - var enumDeclaration = (EnumStatementSyntax) context.Node; - - // enum must not already have flags attribute - if (enumDeclaration.AttributeLists.Any( - a => a.Attributes.Any( - t => + foreach (var attribute in list.Attributes) + { + var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol; + if (symbol == null || symbol.ContainingType.MetadataName == typeof (FlagsAttribute).Name) { - var symbol = context.SemanticModel.GetSymbolInfo(t).Symbol; - - return symbol == null || symbol.ContainingType.MetadataName == typeof (FlagsAttribute).Name; - }))) - { - return; + return; + } + } } context.ReportDiagnostic(Diagnostic.Create(Rule, enumDeclaration.GetLocation())); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeCodeFix.cs index f4c1c2e..48b4d30 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/EnumCanHaveFlagsAttribute/EnumCanHaveFlagsAttributeCodeFix.cs @@ -1,24 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; -using CSharpCompilationUnitSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax; -using VisualBasicCompilationUnitSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.CompilationUnitSyntax; -using CSharpSyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using VisualBasicSyntaxFactory = Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Attributes.EnumCanHaveFlagsAttribute { - [ExportCodeFixProvider(nameof(EnumCanHaveFlagsAttributeCodeFix), LanguageNames.CSharp, LanguageNames.VisualBasic), + [ExportCodeFixProvider(DiagnosticId.EnumCanHaveFlagsAttribute + "CF", LanguageNames.CSharp), Shared] public class EnumCanHaveFlagsAttributeCodeFix : CodeFixProvider { @@ -41,34 +35,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } private Task AddFlagAttributeAsync(Document document, SyntaxNode root, SyntaxNode statement) - { - SyntaxNode newRoot = null; - - if (statement is EnumDeclarationSyntax) - { - newRoot = AddFlagAttributeCSharp(document, root, (EnumDeclarationSyntax) statement); - } - else if (statement is EnumStatementSyntax) - { - newRoot = AddFlagAttributeVisualBasic(document, root, (EnumStatementSyntax) statement); - } - - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); - } - - private SyntaxNode AddFlagAttributeCSharp(Document document, SyntaxNode root, SyntaxNode statement) { var generator = SyntaxGenerator.GetGenerator(document); - var flagsAttribute = CSharpSyntaxFactory.Attribute(CSharpSyntaxFactory.ParseName("Flags")); + var flagsAttribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName("Flags")); var newStatement = generator.AddAttributes(statement, flagsAttribute); var newRoot = root.ReplaceNode(statement, newStatement); - var compilationUnit = (CSharpCompilationUnitSyntax) newRoot; + var compilationUnit = (CompilationUnitSyntax) newRoot; - var usingSystemDirective = CSharpSyntaxFactory.UsingDirective(CSharpSyntaxFactory.ParseName("System")); + var usingSystemDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")); var usingDirectives = compilationUnit.Usings.Select(u => u.Name.GetText().ToString()); if (usingDirectives.All(u => u != usingSystemDirective.Name.GetText().ToString())) @@ -76,39 +53,8 @@ private SyntaxNode AddFlagAttributeCSharp(Document document, SyntaxNode root, Sy newRoot = generator.AddNamespaceImports(compilationUnit, usingSystemDirective); } - return newRoot; - } - - private SyntaxNode AddFlagAttributeVisualBasic(Document document, SyntaxNode root, SyntaxNode statement) - { - var generator = SyntaxGenerator.GetGenerator(document); - - var flagsAttribute = VisualBasicSyntaxFactory.Attribute(VisualBasicSyntaxFactory.ParseName("Flags")); - var newStatement = generator.AddAttributes(statement, flagsAttribute); - - var newRoot = root.ReplaceNode(statement, newStatement).WithAdditionalAnnotations(Formatter.Annotation); - - var compilationUnit = (VisualBasicCompilationUnitSyntax) newRoot; - - var importSystemClause = - VisualBasicSyntaxFactory.SimpleImportsClause( - VisualBasicSyntaxFactory.ParseName("System")) - .WithTrailingTrivia( - VisualBasicSyntaxFactory.SyntaxTrivia( - Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.EndOfLineTrivia, Environment.NewLine)); - var importsList = VisualBasicSyntaxFactory.SeparatedList(new List {importSystemClause}); - var importStatement = VisualBasicSyntaxFactory.ImportsStatement(importsList); - - var imports = - compilationUnit.Imports.SelectMany( - c => c.ImportsClauses.OfType().Select(i => i.Name.GetText().ToString())); - - if (imports.All(u => u != importSystemClause.Name.GetText().ToString())) - { - newRoot = generator.AddNamespaceImports(compilationUnit, importStatement); - } - - return newRoot; + var newDocument = document.WithSyntaxRoot(newRoot); + return Task.FromResult(newDocument.Project.Solution); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoAnalyzer.cs index 191c029..a075c26 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoAnalyzer.cs @@ -46,22 +46,31 @@ internal static DiagnosticDescriptor ValuesDontFitRule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DefaultRule, ValuesDontFitRule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.EnumDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.EnumDeclaration); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var declarationExpression = (EnumDeclarationSyntax) context.Node; - var flagsAttribute = declarationExpression.AttributeLists.FirstOrDefault( - a => a.Attributes.FirstOrDefault( - t => + + AttributeListSyntax flagsAttribute = null; + + foreach (var list in declarationExpression.AttributeLists) + { + foreach (var attribute in list.Attributes) + { + var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol; + if (symbol == null || symbol.ContainingType.MetadataName == typeof (FlagsAttribute).Name) { - var symbol = context.SemanticModel.GetSymbolInfo(t).Symbol; - return symbol == null || symbol.ContainingType.MetadataName == typeof(FlagsAttribute).Name; - }) != null); + flagsAttribute = list; + break; + } + } + if (flagsAttribute != null) + { + break; + } + } if (flagsAttribute == null) { @@ -70,7 +79,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) var enumName = context.SemanticModel.GetDeclaredSymbol(declarationExpression).Name; var enumMemberDeclarations = - declarationExpression.ChildNodes().OfType().ToList(); + declarationExpression.ChildNodes().OfType(SyntaxKind.EnumMemberDeclaration).ToArray(); foreach (var member in enumMemberDeclarations) { @@ -79,11 +88,26 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) continue; } - var descendantNodes = member.EqualsValue.Value.DescendantNodesAndSelf().ToList(); - if (descendantNodes.OfType().Any() && - descendantNodes.OfType().Any()) + var descendantNodes = member.EqualsValue.Value.DescendantNodesAndSelf(); + + var containsLiteralExpression = false; + var containsIdentifierName = false; + foreach (var node in descendantNodes) { - return; + if (node is LiteralExpressionSyntax) + { + containsLiteralExpression = true; + } + + if (node.IsKind(SyntaxKind.IdentifierName)) + { + containsIdentifierName = true; + } + + if (containsIdentifierName && containsLiteralExpression) + { + return; + } } } @@ -109,7 +133,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // We have to make sure that by moving to powers of two, we won't exceed the type's maximum value // For example: 255 is the last possible value for a byte enum - if (IsOutsideOfRange(keyword, enumMemberDeclarations.Count)) + if (IsOutsideOfRange(keyword, enumMemberDeclarations.Length)) { context.ReportDiagnostic(Diagnostic.Create(ValuesDontFitRule, declarationExpression.Identifier.GetLocation(), @@ -129,9 +153,19 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) if (member.EqualsValue.Value is BinaryExpressionSyntax) { - var descendantNodes = member.EqualsValue.Value.DescendantNodesAndSelf().ToList(); - if (descendantNodes.Any() && - descendantNodes.All(n => n is IdentifierNameSyntax || n is BinaryExpressionSyntax)) + var descendantNodes = new List(member.EqualsValue.Value.DescendantNodesAndSelf()); + + var all = true; + foreach (var node in descendantNodes) + { + if (!node.IsKind(SyntaxKind.IdentifierName) && !(node is BinaryExpressionSyntax)) + { + all = false; + break; + } + } + + if (descendantNodes.Any() && all) { continue; } @@ -140,7 +174,10 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) var symbol = context.SemanticModel.GetDeclaredSymbol(member); var value = symbol.ConstantValue; - if (value == null) { return; } + if (value == null) + { + return; + } /* `value` is an `object`. Casting it to `dynamic` * will allow us to avoid a huge `switch` statement diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoCodeFix.cs index abdee77..80b0306 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/FlagsEnumValuesAreNotPowersOfTwo/FlagsEnumValuesAreNotPowersOfTwoCodeFix.cs @@ -9,10 +9,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Attributes.FlagsEnumValuesAreNotPowersOfTwo { - [ExportCodeFixProvider(nameof(FlagsEnumValuesAreNotPowersOfTwoCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.FlagsEnumValuesAreNotPowersOfTwo + "CF", LanguageNames.CSharp), Shared] public class FlagsEnumValuesAreNotPowersOfTwoCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -30,11 +31,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.FlagsEnumValuesAreNotPowersOfTwoCodeFixTitle, - x => AdjustEnumValues(context.Document, root, statement), + x => AdjustEnumValuesAsync(context.Document, root, statement), FlagsEnumValuesAreNotPowersOfTwoAnalyzer.DefaultRule.Id), diagnostic); } - private async Task AdjustEnumValues(Document document, SyntaxNode root, SyntaxNode statement) + private async Task AdjustEnumValuesAsync(Document document, SyntaxNode root, SyntaxNode statement) { var semanticModel = await document.GetSemanticModelAsync(); @@ -112,7 +113,7 @@ private async Task AdjustEnumValues(Document document, SyntaxNode root var newStatement = declarationExpression.WithMembers(SyntaxFactory.SeparatedList(enumMemberDeclarations)) - .WithAdditionalAnnotations(Formatter.Annotation); + .WithAdditionalAnnotations(Formatter.Annotation); var newRoot = root.ReplaceNode(statement, newStatement); return document.WithSyntaxRoot(newRoot).Project.Solution; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/ObsoleteAttributeWithoutReason/ObsoleteAttributeWithoutReasonAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/ObsoleteAttributeWithoutReason/ObsoleteAttributeWithoutReasonAnalyzer.cs index c970de9..52ea837 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/ObsoleteAttributeWithoutReason/ObsoleteAttributeWithoutReasonAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/ObsoleteAttributeWithoutReason/ObsoleteAttributeWithoutReasonAnalyzer.cs @@ -1,16 +1,14 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; -using CSharpSyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; -using VisualBasicSyntaxKind = Microsoft.CodeAnalysis.VisualBasic.SyntaxKind; -using CSharpAttributeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.AttributeSyntax; -using VisualBasicAttributeSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.AttributeSyntax; namespace VSDiagnostics.Diagnostics.Attributes.ObsoleteAttributeWithoutReason { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ObsoleteAttributeWithoutReasonAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; @@ -24,49 +22,15 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeCSharpSymbol, CSharpSyntaxKind.Attribute); - context.RegisterSyntaxNodeAction(AnalyzeVisualBasicSymbol, VisualBasicSyntaxKind.Attribute); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeCSharpSymbol, SyntaxKind.Attribute); private void AnalyzeCSharpSymbol(SyntaxNodeAnalysisContext context) { - var attributeExpression = context.Node as CSharpAttributeSyntax; - if (attributeExpression == null) - { - return; - } - - // attribute type must be of type ObsoleteAttribute - var type = context.SemanticModel.GetSymbolInfo(attributeExpression).Symbol; - if (type == null || type.ContainingType.MetadataName != typeof (ObsoleteAttribute).Name) - { - return; - } - - // attribute must have arguments - // if there are no parenthesis, the ArgumentList is null - // if there are empty parenthesis, the ArgumentList is empty - if (attributeExpression.ArgumentList != null && attributeExpression.ArgumentList.Arguments.Any()) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, attributeExpression.GetLocation())); - } - - private void AnalyzeVisualBasicSymbol(SyntaxNodeAnalysisContext context) - { - var attributeExpression = context.Node as VisualBasicAttributeSyntax; - if (attributeExpression == null) - { - return; - } + var attributeExpression = (AttributeSyntax) context.Node; // attribute type must be of type ObsoleteAttribute var type = context.SemanticModel.GetSymbolInfo(attributeExpression).Symbol; - if (type == null || type.ContainingType.MetadataName != typeof (ObsoleteAttribute).Name) + if (type == null || type.ContainingType.MetadataName != typeof(ObsoleteAttribute).Name) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameAnalyzer.cs index 9c667d6..a0586f5 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameAnalyzer.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using System.ComponentModel; -using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -28,19 +27,21 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.MethodDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.MethodDeclaration); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var methodDeclaration = (MethodDeclarationSyntax) context.Node; - var parentClass = methodDeclaration.Ancestors().OfType().FirstOrDefault(); + var methodDeclaration = (MethodDeclarationSyntax)context.Node; + var parentNode = methodDeclaration.GetEnclosingTypeNode(); + if (!parentNode.IsKind(SyntaxKind.StructDeclaration) && !parentNode.IsKind(SyntaxKind.ClassDeclaration)) + { + return; + } + var typeSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(parentNode); // class must implement INotifyPropertyChanged - if (!parentClass.ImplementsInterface(context.SemanticModel, typeof (INotifyPropertyChanged))) + if (!typeSymbol.ImplementsInterfaceOrBaseClass(typeof(INotifyPropertyChanged))) { return; } @@ -68,13 +69,17 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) } // parameter must not have CallerMemberNameAttribute - if (param.AttributeLists.Any(a => a.Attributes.Any() && a.Attributes.Any(t => - { - var symbol = context.SemanticModel.GetSymbolInfo(t).Symbol; - return symbol != null && symbol.ContainingSymbol.MetadataName == typeof (CallerMemberNameAttribute).Name; - }))) + foreach (var list in param.AttributeLists) { - return; + foreach (var attribute in list.Attributes) + { + var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol; + if (symbol != null && + symbol.ContainingSymbol.MetadataName == typeof (CallerMemberNameAttribute).Name) + { + return; + } + } } context.ReportDiagnostic(Diagnostic.Create(Rule, methodDeclaration.GetLocation())); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameCodeFix.cs index 004b4bd..dfe434a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Attributes/OnPropertyChangedWithoutCallerMemberName/OnPropertyChangedWithoutCallerMemberNameCodeFix.cs @@ -9,10 +9,11 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Attributes.OnPropertyChangedWithoutCallerMemberName { - [ExportCodeFixProvider(nameof(OnPropertyChangedWithoutCallerMemberNameCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.OnPropertyChangedWithoutCallerMemberName + "CF", LanguageNames.CSharp), Shared] public class OnPropertyChangedWithoutCallerMemberNameCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -29,11 +30,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var statement = root.FindNode(diagnosticSpan); context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.OnPropertyChangedWithoutCallerMemberNameCodeFixTitle, - x => AddCallerMemberNameAttribute(context.Document, statement), + x => AddCallerMemberNameAttributeAsync(context.Document, statement), OnPropertyChangedWithoutCallerMemberNameAnalyzer.Rule.Id), diagnostic); } - private async Task AddCallerMemberNameAttribute(Document document, SyntaxNode statement) + private async Task AddCallerMemberNameAttributeAsync(Document document, SyntaxNode statement) { var editor = await DocumentEditor.CreateAsync(document); @@ -45,20 +46,20 @@ private async Task AddCallerMemberNameAttribute(Document document, Syn var newParam = param.Default == null ? param.WithAttributeLists(param.AttributeLists.Add(attributeList)) - .WithDefault(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("\"\""))) + .WithDefault(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("\"\""))) : param.WithAttributeLists(param.AttributeLists.Add(attributeList)); editor.ReplaceNode(param, newParam); - var parentClass = methodDeclaration.Ancestors().OfType().FirstOrDefault(); + var parentNode = methodDeclaration.GetEnclosingTypeNode(); var methodInvocations = - parentClass.DescendantNodes() - .OfType().Where(i => - { - var identifierExpression = i.Expression as IdentifierNameSyntax; - return identifierExpression != null && - identifierExpression.Identifier.ValueText == "OnPropertyChanged"; - }); + parentNode.DescendantNodes() + .OfType().Where(i => + { + var identifierExpression = i.Expression as IdentifierNameSyntax; + return identifierExpression != null && + identifierExpression.Identifier.ValueText == "OnPropertyChanged"; + }); foreach (var methodInvocation in methodInvocations) { @@ -78,10 +79,10 @@ private async Task AddCallerMemberNameAttribute(Document document, Syn { var usings = compilationUnit.Usings.Add(usingSystemRuntimeCompilerServicesDirective) - .OrderBy(u => u.Name.GetText().ToString()); + .OrderBy(u => u.Name.GetText().ToString()); newRoot = newRoot.ReplaceNode(compilationUnit, compilationUnit.WithUsings(SyntaxFactory.List(usings)) - .WithAdditionalAnnotations(Formatter.Annotation)); + .WithAdditionalAnnotations(Formatter.Annotation)); } var newDocument = document.WithSyntaxRoot(newRoot); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorAnalyzer.cs index 766cc6c..19bc724 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorAnalyzer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -27,51 +28,83 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.ObjectCreationExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.ObjectCreationExpression); private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { - var objectCreationExpression = context.Node as ObjectCreationExpressionSyntax; - if (objectCreationExpression?.ArgumentList == null || !objectCreationExpression.ArgumentList.Arguments.Any()) + var objectCreationExpression = (ObjectCreationExpressionSyntax) context.Node; + if (objectCreationExpression.ArgumentList == null || !objectCreationExpression.ArgumentList.Arguments.Any()) { return; } var exceptionType = objectCreationExpression.Type; var symbolInformation = context.SemanticModel.GetSymbolInfo(exceptionType); - if (symbolInformation.Symbol.InheritsFrom(typeof (ArgumentException))) + if (symbolInformation.Symbol.InheritsFrom(typeof(ArgumentException))) { - var arguments = - objectCreationExpression.ArgumentList.Arguments.Select(x => x.Expression) - .OfType(); + var arguments = new List(); + + foreach (var argument in objectCreationExpression.ArgumentList.Arguments) + { + if (argument.Expression is LiteralExpressionSyntax) + { + arguments.Add((LiteralExpressionSyntax)argument.Expression); + } + } + var methodParameters = objectCreationExpression.Ancestors() - .OfType() - .FirstOrDefault()? - .ParameterList.Parameters; + .OfType(SyntaxKind.MethodDeclaration) + .FirstOrDefault()? + .ParameterList.Parameters; + + // Exception is declared inside a method + if (methodParameters != null) + { + foreach (var argument in arguments) + { + var argumentName = argument.Token.ValueText; + ParameterSyntax correspondingParameter = null; + + foreach (var parameter in methodParameters) + { + if (string.Equals((string)parameter.Identifier.Value, argumentName, + StringComparison.OrdinalIgnoreCase)) + { + correspondingParameter = parameter; + break; + } + } + + if (correspondingParameter != null) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, argument.GetLocation(), + correspondingParameter.Identifier.Value)); + return; + } + } + } + + // We also account for the situation where an exception is thrown from a property setter and refers to the contextual keyword "value" + var propertyDeclaration = objectCreationExpression.Ancestors() + .OfType(SyntaxKind.PropertyDeclaration) + .FirstOrDefault(); - // Exception is declared outside a method - if (methodParameters == null) + if (propertyDeclaration == null) { return; } - foreach (var argument in arguments) + foreach (var argument in objectCreationExpression.ArgumentList.Arguments) { - var argumentName = argument.Token.ValueText; - var correspondingParameter = - methodParameters.Value.FirstOrDefault( - x => - string.Equals((string) x.Identifier.Value, (string) argumentName, - StringComparison.OrdinalIgnoreCase)); - if (correspondingParameter != null) + if (argument.Expression.IsKind(SyntaxKind.StringLiteralExpression)) { - context.ReportDiagnostic(Diagnostic.Create(Rule, argument.GetLocation(), - correspondingParameter.Identifier.Value)); - return; + var stringLiteral = (LiteralExpressionSyntax) argument.Expression; + if (stringLiteral.Token.ValueText == "value") + { + context.ReportDiagnostic(Diagnostic.Create(Rule, argument.GetLocation(), "value")); + return; + } } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorCodeFix.cs index 96681b8..030afbf 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ArgumentExceptionWithoutNameofOperator/ArgumentExceptionWithoutNameofOperatorCodeFix.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -8,10 +9,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Exceptions.ArgumentExceptionWithoutNameofOperator { - [ExportCodeFixProvider(nameof(ArgumentExceptionWithoutNameofOperatorCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.ArgumentExceptionWithoutNameofOperator + "CF", LanguageNames.CSharp), Shared] public class ArgumentExceptionWithoutNameofOperatorCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -36,32 +38,61 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) ArgumentExceptionWithoutNameofOperatorAnalyzer.Rule.Id), diagnostic); } - private Task UseNameofAsync(Document document, SyntaxNode root, - ObjectCreationExpressionSyntax objectCreationExpression) + private Task UseNameofAsync(Document document, SyntaxNode root, ObjectCreationExpressionSyntax objectCreationExpression) { - var method = objectCreationExpression.Ancestors().OfType().First(); - var methodParameters = method.ParameterList.Parameters; + var method = objectCreationExpression.Ancestors().OfType(SyntaxKind.MethodDeclaration).FirstOrDefault(); + PropertyDeclarationSyntax property = default(PropertyDeclarationSyntax); + if (method == null) + { + // Fired from a property setter + property = objectCreationExpression.Ancestors() + .OfType(SyntaxKind.PropertyDeclaration) + .First(); + } + var expressionArguments = objectCreationExpression.ArgumentList.Arguments.Select(x => x.Expression) - .OfType(); + .OfType(); foreach (var expressionArgument in expressionArguments) { - foreach (var methodParameter in methodParameters) + if (property != default(PropertyDeclarationSyntax)) { - if (string.Equals((string) methodParameter.Identifier.Value, (string) expressionArgument.Token.Value, - StringComparison.OrdinalIgnoreCase)) + if (string.Equals(expressionArgument.Token.ValueText, "value", StringComparison.OrdinalIgnoreCase)) { - var newExpression = SyntaxFactory.ParseExpression($"nameof({methodParameter.Identifier})"); - var newParent = objectCreationExpression.ReplaceNode(expressionArgument, newExpression); - var newRoot = root.ReplaceNode(objectCreationExpression, newParent); - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); + return CreateNewExpressionAsync(root, "value", objectCreationExpression, expressionArgument, document); } } + else + { + Debug.Assert(method != null); + var methodParameters = method.ParameterList.Parameters; + foreach (var methodParameter in methodParameters) + { + if (string.Equals(methodParameter.Identifier.ValueText, expressionArgument.Token.ValueText, StringComparison.OrdinalIgnoreCase)) + { + return CreateNewExpressionAsync(root, methodParameter.Identifier.ValueText, objectCreationExpression, expressionArgument, document); + } + } + } + } - return null; + throw new InvalidOperationException("No corresponding parameter could be found"); + } + + private Task CreateNewExpressionAsync( + SyntaxNode root, + string newIdentifier, + ObjectCreationExpressionSyntax objectCreationExpression, + ExpressionSyntax argumentExpression, + Document document) + { + var newExpression = SyntaxFactory.ParseExpression($"nameof({newIdentifier})"); + var newParent = objectCreationExpression.ReplaceNode(argumentExpression, newExpression); + var newRoot = root.ReplaceNode(objectCreationExpression, newParent); + var newDocument = document.WithSyntaxRoot(newRoot); + return Task.FromResult(newDocument.Project.Solution); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/CatchNullReferenceException/CatchingNullReferenceExceptionAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/CatchNullReferenceException/CatchingNullReferenceExceptionAnalyzer.cs index 1eb8aab..a3a9382 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/CatchNullReferenceException/CatchingNullReferenceExceptionAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/CatchNullReferenceException/CatchingNullReferenceExceptionAnalyzer.cs @@ -22,16 +22,13 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.CatchDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.CatchDeclaration); private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { - var catchDeclaration = context.Node as CatchDeclarationSyntax; + var catchDeclaration = (CatchDeclarationSyntax) context.Node; - var catchType = catchDeclaration?.Type; + var catchType = catchDeclaration.Type; if (catchType == null) { return; @@ -40,7 +37,7 @@ private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) var catchSymbol = context.SemanticModel.GetSymbolInfo(catchType).Symbol; if (catchSymbol != null) { - if (catchSymbol.MetadataName == typeof (NullReferenceException).Name) + if (catchSymbol.MetadataName == typeof(NullReferenceException).Name) { context.ReportDiagnostic(Diagnostic.Create(Rule, catchDeclaration.GetLocation())); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyArgumentException/EmptyArgumentExceptionAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyArgumentException/EmptyArgumentExceptionAnalyzer.cs index 2f64023..529ca56 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyArgumentException/EmptyArgumentExceptionAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyArgumentException/EmptyArgumentExceptionAnalyzer.cs @@ -23,23 +23,20 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.ThrowStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.ThrowStatement); private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { - var throwStatement = context.Node as ThrowStatementSyntax; + var throwStatement = (ThrowStatementSyntax) context.Node; - var expression = throwStatement?.Expression as ObjectCreationExpressionSyntax; + var expression = throwStatement.Expression as ObjectCreationExpressionSyntax; if (expression == null) { return; } var symbolInformation = context.SemanticModel.GetSymbolInfo(expression.Type); - if (!symbolInformation.Symbol.InheritsFrom(typeof (ArgumentException))) + if (!symbolInformation.Symbol.InheritsFrom(typeof(ArgumentException))) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyCatchClause/EmptyCatchClauseAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyCatchClause/EmptyCatchClauseAnalyzer.cs index 70770f1..a721f58 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyCatchClause/EmptyCatchClauseAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/EmptyCatchClause/EmptyCatchClauseAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,15 +21,12 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.CatchClause); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.CatchClause); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var catchClause = context.Node as CatchClauseSyntax; - if (catchClause?.Block == null) + var catchClause = (CatchClauseSyntax) context.Node; + if (catchClause.Block == null) { return; } @@ -40,9 +36,12 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) return; } - if (catchClause.Block.CloseBraceToken.LeadingTrivia.Any(x => x.IsCommentTrivia())) + foreach (var trivia in catchClause.Block.CloseBraceToken.LeadingTrivia) { - return; + if (trivia.IsCommentTrivia()) + { + return; + } } context.ReportDiagnostic(Diagnostic.Create(Rule, catchClause.CatchKeyword.GetLocation())); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ExceptionThrownFromProhibitedContext/ExceptionThrownFromProhibitedContextAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ExceptionThrownFromProhibitedContext/ExceptionThrownFromProhibitedContextAnalyzer.cs new file mode 100644 index 0000000..874a1d5 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/ExceptionThrownFromProhibitedContext/ExceptionThrownFromProhibitedContextAnalyzer.cs @@ -0,0 +1,180 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Exceptions.ExceptionThrownFromProhibitedContext +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ExceptionThrownFromProhibitedContextAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.ExceptionsCategory; + + private static DiagnosticDescriptor ImplicitOperatorRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromImplicitOperator, + VSDiagnosticsResources.ExceptionThrownFromImplicitOperatorAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromImplicitOperatorAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor PropertyGetterRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromPropertyGetter, + VSDiagnosticsResources.ExceptionThrownFromPropertyGetterAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromPropertyGetterAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor StaticConstructorRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromStaticConstructor, + VSDiagnosticsResources.ExceptionThrownFromStaticConstructorAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromStaticConstructorAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor FinallyBlockRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromFinallyBlock, + VSDiagnosticsResources.ExceptionThrownFromFinallyBlockAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromFinallyBlockAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor EqualityOperatorRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromEqualityOperator, + VSDiagnosticsResources.ExceptionThrownFromEqualityOperatorAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromEqualityOperatorAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor DisposeRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromDispose, + VSDiagnosticsResources.ExceptionThrownFromDisposeAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromDisposeAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor FinalizerRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromFinalizer, + VSDiagnosticsResources.ExceptionThrownFromFinalizerAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromFinalizerAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor GetHashCodeRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromGetHashCode, + VSDiagnosticsResources.ExceptionThrownFromGetHashCodeAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromGetHashCodeAnalyzerMessage, Category, Severity, true); + + private static DiagnosticDescriptor EqualsRule + => new DiagnosticDescriptor(DiagnosticId.ExceptionThrownFromEquals, + VSDiagnosticsResources.ExceptionThrownFromEqualsAnalyzerTitle, + VSDiagnosticsResources.ExceptionThrownFromEqualsAnalyzerMessage, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create( + ImplicitOperatorRule, + PropertyGetterRule, + StaticConstructorRule, + FinallyBlockRule, + EqualityOperatorRule, + DisposeRule, + FinalizerRule, + GetHashCodeRule, + EqualsRule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ThrowStatement); + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + // Since the current node is a throw statement there is no symbol to be found. + // Therefore we look at whatever member is holding the statement (constructor, method, property, etc) and see what encloses that + var containingType = context.SemanticModel.GetEnclosingSymbol(context.Node.SpanStart).ContainingType; + var warningLocation = context.Node.GetLocation(); + + foreach (var ancestor in context.Node.Ancestors()) + { + if (ancestor.IsKind(SyntaxKind.MethodDeclaration)) + { + var method = (MethodDeclarationSyntax) ancestor; + var methodName = method.Identifier.ValueText; + + if (methodName == "Dispose") + { + var arity = method.ParameterList.Parameters.Count; + context.ReportDiagnostic(Diagnostic.Create(DisposeRule, warningLocation, arity == 0 ? "Dispose()" : "Dispose(bool)", containingType.Name)); + return; + } + + if (methodName == "GetHashCode") + { + // Make sure we're dealing with the actual members defined in 'Object' in case they're hidden in a subclass + var currentMethodSymbol = context.SemanticModel.GetDeclaredSymbol(method); + + var objectSymbol = context.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Object); + var objectGetHashCodeSymbol = objectSymbol.GetMembers("GetHashCode").Single(); + + while (currentMethodSymbol.IsOverride) + { + currentMethodSymbol = currentMethodSymbol.OverriddenMethod; + } + + if (currentMethodSymbol.Equals(objectGetHashCodeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create(GetHashCodeRule, warningLocation, containingType.Name)); + return; + } + } + + // We don't verify we're dealing with the 'Object' overridden method of 'Equals' because that would exclude Equals(T) in IEquatable + // Furthermore we can expect to have multiple overloads of 'Equals' on argument type to provide a better equality comparison experience + // This is not the case for GetHashCode() where we only expect one implementation + if (methodName == "Equals" && method.ParameterList.Parameters.Count == 1) + { + context.ReportDiagnostic(Diagnostic.Create(EqualsRule, warningLocation, method.ParameterList.Parameters[0].Type.ToString(), containingType.Name)); + return; + } + } + else if (ancestor.IsKind(SyntaxKind.GetAccessorDeclaration)) + { + var property = ancestor.Ancestors().OfType(SyntaxKind.PropertyDeclaration).FirstOrDefault(); + if (property == null) + { + return; + } + context.ReportDiagnostic(Diagnostic.Create(PropertyGetterRule, warningLocation, property.Identifier.ValueText)); + return; + } + else if (ancestor.IsKind(SyntaxKind.FinallyClause)) + { + context.ReportDiagnostic(Diagnostic.Create(FinallyBlockRule, warningLocation)); + return; + } + else if (ancestor.IsKind(SyntaxKind.OperatorDeclaration)) + { + var operatorDeclaration = (OperatorDeclarationSyntax) ancestor; + if (operatorDeclaration.OperatorToken.IsKind(SyntaxKind.EqualsEqualsToken) || operatorDeclaration.OperatorToken.IsKind(SyntaxKind.ExclamationEqualsToken)) + { + var operatorToken = operatorDeclaration.OperatorToken.ValueText; + var firstType = operatorDeclaration.ParameterList.Parameters[0].Type.ToString(); + var secondType = operatorDeclaration.ParameterList.Parameters[1].Type.ToString(); + context.ReportDiagnostic(Diagnostic.Create(EqualityOperatorRule, warningLocation, operatorToken, firstType, secondType, containingType.Name)); + return; + } + } + else if (ancestor.IsKind(SyntaxKind.ConversionOperatorDeclaration)) + { + var conversionOperatorDeclaration = (ConversionOperatorDeclarationSyntax) ancestor; + if (conversionOperatorDeclaration.ImplicitOrExplicitKeyword.IsKind(SyntaxKind.ImplicitKeyword)) + { + context.ReportDiagnostic(Diagnostic.Create(ImplicitOperatorRule, warningLocation, conversionOperatorDeclaration.Type.ToString(), containingType.Name)); + return; + } + } + else if (ancestor.IsKind(SyntaxKind.ConstructorDeclaration)) + { + var constructor = (ConstructorDeclarationSyntax) ancestor; + if (constructor.Modifiers.Any(SyntaxKind.StaticKeyword)) + { + context.ReportDiagnostic(Diagnostic.Create(StaticConstructorRule, warningLocation, containingType.Name)); + return; + } + } + else if (ancestor.IsKind(SyntaxKind.DestructorDeclaration)) + { + context.ReportDiagnostic(Diagnostic.Create(FinalizerRule, warningLocation, containingType.Name)); + return; + } + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceAnalyzer.cs index f2e8cd7..5705acc 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceAnalyzer.cs @@ -13,36 +13,27 @@ namespace VSDiagnostics.Diagnostics.Exceptions.RethrowExceptionWithoutLosingStac public class RethrowExceptionWithoutLosingStacktraceAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - private static readonly string Category = VSDiagnosticsResources.ExceptionsCategory; + private static readonly string Message = VSDiagnosticsResources.RethrowExceptionWithoutLosingStacktraceAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.RethrowExceptionWithoutLosingStacktraceAnalyzerTitle; - private static readonly string Message = - VSDiagnosticsResources.RethrowExceptionWithoutLosingStacktraceAnalyzerMessage; - - private static readonly string Title = - VSDiagnosticsResources.RethrowExceptionWithoutLosingStacktraceAnalyzerTitle; - - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.RethrowExceptionWithoutLosingStacktrace, Title, Message, Category, Severity, true); + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.RethrowExceptionWithoutLosingStacktrace, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ThrowStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ThrowStatement); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var throwStatement = context.Node as ThrowStatementSyntax; + var throwStatement = (ThrowStatementSyntax) context.Node; - var throwIdentifierSyntax = throwStatement?.Expression as IdentifierNameSyntax; + var throwIdentifierSyntax = throwStatement.Expression as IdentifierNameSyntax; if (throwIdentifierSyntax == null) { return; } - var catchClause = throwStatement.Ancestors().OfType().FirstOrDefault(); + var catchClause = throwStatement.Ancestors().OfType(SyntaxKind.CatchClause).FirstOrDefault(); // Code is in an incomplete state (user is typing the catch clause but hasn't typed the identifier yet) var exceptionIdentifier = catchClause?.Declaration?.Identifier; @@ -51,10 +42,10 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) return; } - var catchClauseIdentifier = exceptionIdentifier.Value.ToString(); - var thrownIdentifier = throwIdentifierSyntax.Identifier.Value.ToString(); + var catchClauseIdentifier = exceptionIdentifier.Value.ValueText; + var thrownIdentifier = throwIdentifierSyntax.Identifier.ValueText; - if (string.Equals(catchClauseIdentifier, thrownIdentifier, StringComparison.Ordinal)) + if (catchClauseIdentifier == thrownIdentifier) { context.ReportDiagnostic(Diagnostic.Create(Rule, throwStatement.GetLocation())); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceCodeFix.cs index 156c00c..f51d0f1 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/RethrowExceptionWithoutLosingStacktrace/RethrowExceptionWithoutLosingStacktraceCodeFix.cs @@ -7,10 +7,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Exceptions.RethrowExceptionWithoutLosingStacktrace { - [ExportCodeFixProvider(nameof(RethrowExceptionWithoutLosingStacktraceCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.RethrowExceptionWithoutLosingStacktrace + "CF", LanguageNames.CSharp), Shared] public class RethrowExceptionWithoutLosingStacktraceCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -32,7 +33,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } private Task RemoveRethrowAsync(Document document, SyntaxNode root, - ThrowStatementSyntax throwStatement) + ThrowStatementSyntax throwStatement) { var newStatement = SyntaxFactory.ThrowStatement(); var newRoot = root.ReplaceNode(throwStatement, newStatement); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/SingleGeneralException/SingleGeneralExceptionAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/SingleGeneralException/SingleGeneralExceptionAnalyzer.cs index b38eb80..f5d877c 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/SingleGeneralException/SingleGeneralExceptionAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Exceptions/SingleGeneralException/SingleGeneralExceptionAnalyzer.cs @@ -18,19 +18,16 @@ public class SingleGeneralExceptionAnalyzer : DiagnosticAnalyzer private static readonly string Title = VSDiagnosticsResources.SingleGeneralExceptionAnalyzerTitle; internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.SingleGeneralException, Title, Message, Category, Severity, true); + => new DiagnosticDescriptor(DiagnosticId.SingleGeneralException, Title, Message, Category, Severity, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.TryStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.TryStatement); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var tryStatement = context.Node as TryStatementSyntax; - if (tryStatement?.Catches.Count != 1) + var tryStatement = (TryStatementSyntax) context.Node; + if (tryStatement.Catches.Count != 1) { return; } @@ -45,7 +42,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) var symbol = context.SemanticModel.GetSymbolInfo(declaredException).Symbol; if (symbol != null) { - if (symbol.MetadataName == typeof (Exception).Name) + if (symbol.MetadataName == typeof(Exception).Name) { context.ReportDiagnostic(Diagnostic.Create(Rule, declaredException.GetLocation())); } 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/Diagnostics/General/AsToCast/AsToCastAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastAnalyzer.cs index 18898e5..d5ece8e 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastAnalyzer.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; @@ -16,25 +15,12 @@ public class AsToCastAnalyzer : DiagnosticAnalyzer private static readonly string Message = VSDiagnosticsResources.AsToCastAnalyzerMessage; private static readonly string Title = VSDiagnosticsResources.AsToCastAnalyzerTitle; - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.AsToCast, Title, Message, Category, Severity, true); + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.AsToCast, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.AsExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.AsExpression); - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) - { - var binaryExpression = context.Node as BinaryExpressionSyntax; - if (binaryExpression == null) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, binaryExpression.GetLocation())); - } + private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) => context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation())); } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastCodeFix.cs index b6e8a3d..0a7098e 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/AsToCast/AsToCastCodeFix.cs @@ -8,10 +8,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.AsToCast { - [ExportCodeFixProvider(nameof(AsToCastCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.AsToCast + "CF", LanguageNames.CSharp), Shared] public class AsToCastCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(AsToCastAnalyzer.Rule.Id); @@ -24,24 +25,20 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; - var statement = root.FindNode(diagnosticSpan); + var statement = root.FindNode(diagnosticSpan).DescendantNodesAndSelf().OfType().First(); context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.AsToCastCodeFixTitle, x => AsToCastAsync(context.Document, root, statement), AsToCastAnalyzer.Rule.Id), diagnostic); } - private Task AsToCastAsync(Document document, SyntaxNode root, SyntaxNode statement) + private Task AsToCastAsync(Document document, SyntaxNode root, SyntaxNode statement) { var binaryExpression = (BinaryExpressionSyntax) statement; - var typeSyntax = SyntaxFactory.ParseTypeName(binaryExpression.Right.GetText().ToString()); - var newExpression = - SyntaxFactory.CastExpression(typeSyntax, binaryExpression.Left) - .WithAdditionalAnnotations(Formatter.Annotation); + var typeSyntax = (TypeSyntax) binaryExpression.Right; - var newRoot = root.ReplaceNode(binaryExpression, newExpression); - - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); + var newExpression = SyntaxFactory.CastExpression(typeSyntax, binaryExpression.Left).WithAdditionalAnnotations(Formatter.Annotation); + root = root.ReplaceNode(binaryExpression, newExpression); + return Task.FromResult(document.WithSyntaxRoot(root)); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsAnalyzer.cs index 1278988..dd37038 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsAnalyzer.cs @@ -21,26 +21,24 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.CastExpression); + + /// + /// We don't handle situations like + /// int x = (int) o; + /// Turning this into a soft cast (o as int?) would cause issues when trying to apply this to a generic definition + /// This is only needed for non-nullable valuetypes because they need to become nullable for this kind of cast + /// + private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.CastExpression); - } - - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) - { - var castExpression = context.Node as CastExpressionSyntax; - if (castExpression == null) - { - return; - } - - var castedTypeInfo = context.SemanticModel.GetTypeInfo(castExpression.Expression); - if (castedTypeInfo.ConvertedType != null && castedTypeInfo.ConvertedType.IsValueType) + var castExpression = (CastExpressionSyntax) context.Node; + var type = context.SemanticModel.GetTypeInfo(castExpression.Type).Type; + if (type.IsValueType && type.OriginalDefinition.SpecialType != SpecialType.System_Nullable_T) { return; } - context.ReportDiagnostic(Diagnostic.Create(Rule, castExpression.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation())); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsCodeFix.cs index 9421121..2921fb4 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CastToAs/CastToAsCodeFix.cs @@ -8,10 +8,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.CastToAs { - [ExportCodeFixProvider(nameof(CastToAsCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.CastToAs + "CF", LanguageNames.CSharp), Shared] public class CastToAsCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CastToAsAnalyzer.Rule.Id); @@ -24,7 +25,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; - var statement = root.FindNode(diagnosticSpan); + var statement = root.FindNode(diagnosticSpan).DescendantNodesAndSelf().OfType().First(); context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.CastToAsCodeFixTitle, x => CastToAsAsync(context.Document, root, statement), CastToAsAnalyzer.Rule.Id), diagnostic); @@ -33,14 +34,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private Task CastToAsAsync(Document document, SyntaxNode root, SyntaxNode statement) { var castExpression = (CastExpressionSyntax) statement; - var newExpression = - SyntaxFactory.BinaryExpression(SyntaxKind.AsExpression, castExpression.Expression, castExpression.Type) - .WithAdditionalAnnotations(Formatter.Annotation); + var newExpression = SyntaxFactory.BinaryExpression(SyntaxKind.AsExpression, castExpression.Expression, castExpression.Type) + .WithAdditionalAnnotations(Formatter.Annotation); - var newRoot = root.ReplaceNode(castExpression, newExpression); - - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); + root = root.ReplaceNode(castExpression, newExpression); + return Task.FromResult(document.WithSyntaxRoot(root).Project.Solution); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralAnalyzer.cs index 18b3ac5..ac0b5c9 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralAnalyzer.cs @@ -21,18 +21,11 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.FalseLiteralExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.FalseLiteralExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var literalExpression = context.Node as LiteralExpressionSyntax; - if (literalExpression == null) - { - return; - } + var literalExpression = (LiteralExpressionSyntax) context.Node; if (!(literalExpression.Token.IsKind(SyntaxKind.FalseKeyword) && literalExpression.Token.Value is bool)) { @@ -54,7 +47,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - if (rightSymbol.Type.IsNullable()) + if (rightSymbol.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { return; } @@ -68,7 +61,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - if (leftSymbol.Type.IsNullable()) + if (leftSymbol.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralCodeFix.cs index 2678ed2..b9e973d 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToFalseLiteral/CompareBooleanToFalseLiteralCodeFix.cs @@ -9,12 +9,24 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.CompareBooleanToFalseLiteral { - [ExportCodeFixProvider(nameof(CompareBooleanToFalseLiteralCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.CompareBooleanToFalseLiteral + "CF", LanguageNames.CSharp), Shared] public class CompareBooleanToFalseLiteralCodeFix : CodeFixProvider { + private static readonly Dictionary MapOperatorToReverseOperator = + new Dictionary + { + { SyntaxKind.EqualsEqualsToken, SyntaxKind.ExclamationEqualsToken }, + { SyntaxKind.ExclamationEqualsToken, SyntaxKind.EqualsEqualsToken }, + { SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken }, + { SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken }, + { SyntaxKind.LessThanEqualsToken, SyntaxKind.GreaterThanToken }, + { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken } + }; + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CompareBooleanToFalseLiteralAnalyzer.Rule.Id); @@ -54,7 +66,7 @@ private Task SimplifyExpressionAsync(Document document, SyntaxNode roo var newOperator = binaryExpression.OperatorToken.IsKind(SyntaxKind.EqualsEqualsToken) ? MapOperatorToReverseOperator.First(kvp => kvp.Key == internalBinaryExpression.OperatorToken.Kind()) - .Value + .Value : internalBinaryExpression.OperatorToken.Kind(); newExpression = internalBinaryExpression.WithOperatorToken(SyntaxFactory.Token(newOperator)); @@ -73,21 +85,10 @@ private Task SimplifyExpressionAsync(Document document, SyntaxNode roo } var newRoot = - root.ReplaceNode(binaryExpression, newExpression).WithAdditionalAnnotations(Formatter.Annotation); + root.ReplaceNode(binaryExpression, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); var newDocument = document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument.Project.Solution); } - - private static readonly Dictionary MapOperatorToReverseOperator = - new Dictionary - { - {SyntaxKind.EqualsEqualsToken, SyntaxKind.ExclamationEqualsToken}, - {SyntaxKind.ExclamationEqualsToken, SyntaxKind.EqualsEqualsToken}, - {SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken}, - {SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken}, - {SyntaxKind.LessThanEqualsToken, SyntaxKind.GreaterThanToken}, - {SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken}, - }; } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralAnalyzer.cs index d8900d6..76dc3d7 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralAnalyzer.cs @@ -21,18 +21,11 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.TrueLiteralExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.TrueLiteralExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var literalExpression = context.Node as LiteralExpressionSyntax; - if (literalExpression == null) - { - return; - } + var literalExpression = (LiteralExpressionSyntax) context.Node; if (!(literalExpression.Token.IsKind(SyntaxKind.TrueKeyword) && literalExpression.Token.Value is bool)) { @@ -54,7 +47,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - if (rightSymbol.Type.IsNullable()) + if (rightSymbol.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { return; } @@ -68,7 +61,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - if (leftSymbol.Type.IsNullable()) + if (leftSymbol.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralCodeFix.cs index 3143c8f..06c79b9 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/CompareBooleanToTrueLiteral/CompareBooleanToTrueLiteralCodeFix.cs @@ -9,12 +9,24 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.CompareBooleanToTrueLiteral { - [ExportCodeFixProvider(nameof(CompareBooleanToTrueLiteralCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.CompareBooleanToTrueLiteral + "CF", LanguageNames.CSharp), Shared] public class CompareBooleanToTrueLiteralCodeFix : CodeFixProvider { + private static readonly Dictionary MapOperatorToReverseOperator = + new Dictionary + { + { SyntaxKind.EqualsEqualsToken, SyntaxKind.ExclamationEqualsToken }, + { SyntaxKind.ExclamationEqualsToken, SyntaxKind.EqualsEqualsToken }, + { SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken }, + { SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken }, + { SyntaxKind.LessThanEqualsToken, SyntaxKind.GreaterThanToken }, + { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken } + }; + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CompareBooleanToTrueLiteralAnalyzer.Rule.Id); @@ -55,7 +67,7 @@ private Task SimplifyExpressionAsync(Document document, SyntaxNode roo var newOperator = binaryExpression.OperatorToken.IsKind(SyntaxKind.EqualsEqualsToken) ? internalBinaryExpression.OperatorToken.Kind() : MapOperatorToReverseOperator.First(kvp => kvp.Key == internalBinaryExpression.OperatorToken.Kind()) - .Value; + .Value; newExpression = internalBinaryExpression.WithOperatorToken(SyntaxFactory.Token(newOperator)); } @@ -73,21 +85,10 @@ private Task SimplifyExpressionAsync(Document document, SyntaxNode roo } var newRoot = - root.ReplaceNode(binaryExpression, newExpression).WithAdditionalAnnotations(Formatter.Annotation); + root.ReplaceNode(binaryExpression, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); var newDocument = document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument.Project.Solution); } - - private static readonly Dictionary MapOperatorToReverseOperator = - new Dictionary - { - {SyntaxKind.EqualsEqualsToken, SyntaxKind.ExclamationEqualsToken}, - {SyntaxKind.ExclamationEqualsToken, SyntaxKind.EqualsEqualsToken}, - {SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken}, - {SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken}, - {SyntaxKind.LessThanEqualsToken, SyntaxKind.GreaterThanToken}, - {SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken}, - }; } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseAnalyzer.cs deleted file mode 100644 index 0d02976..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseAnalyzer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using VSDiagnostics.Utilities; - -namespace VSDiagnostics.Diagnostics.General.ConditionIsAlwaysFalse -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class ConditionIsAlwaysFalseAnalyzer : DiagnosticAnalyzer - { - private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; - private static readonly string Message = VSDiagnosticsResources.ConditionIsAlwaysFalseAnalyzerMessage; - private static readonly string Title = VSDiagnosticsResources.ConditionIsAlwaysFalseAnalyzerTitle; - - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.ConditionIsAlwaysFalse, Title, Message, Category, Severity, true); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IfStatement); - } - - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) - { - var ifStatement = (IfStatementSyntax) context.Node; - - if (ifStatement.Condition.IsKind(SyntaxKind.FalseLiteralExpression)) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, ifStatement.Condition.GetLocation())); - } - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseCodeFix.cs deleted file mode 100644 index 8e9433f..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysFalse/ConditionIsAlwaysFalseCodeFix.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; - -namespace VSDiagnostics.Diagnostics.General.ConditionIsAlwaysFalse -{ - [ExportCodeFixProvider(nameof(ConditionIsAlwaysFalseCodeFix), LanguageNames.CSharp), Shared] - public class ConditionIsAlwaysFalseCodeFix : CodeFixProvider - { - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(ConditionIsAlwaysFalseAnalyzer.Rule.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var statement = root.FindNode(diagnosticSpan); - context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.ConditionIsAlwaysTrueCodeFixTitle, - x => RemoveConditionAsync(context.Document, root, statement), ConditionIsAlwaysFalseAnalyzer.Rule.Id), - diagnostic); - } - - private Task RemoveConditionAsync(Document document, SyntaxNode root, SyntaxNode statement) - { - var ifStatement = statement.Ancestors().OfType().First(); - var blockStatement = ifStatement.Else?.Statement as BlockSyntax; - - // no else - var newRoot = - root.RemoveNode(ifStatement, SyntaxRemoveOptions.KeepNoTrivia) - .WithAdditionalAnnotations(Formatter.Annotation); - - // else with braces - if (blockStatement != null) - { - newRoot = - root.ReplaceNode(ifStatement, blockStatement.Statements) - .WithAdditionalAnnotations(Formatter.Annotation); - } - - // else without braces - if (ifStatement.Else != null && blockStatement == null) - { - newRoot = - root.ReplaceNode(ifStatement, ifStatement.Else.Statement) - .WithAdditionalAnnotations(Formatter.Annotation); - } - - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueCodeFix.cs deleted file mode 100644 index f6daddb..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueCodeFix.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; - -namespace VSDiagnostics.Diagnostics.General.ConditionIsAlwaysTrue -{ - [ExportCodeFixProvider(nameof(ConditionIsAlwaysTrueCodeFix), LanguageNames.CSharp), Shared] - public class ConditionIsAlwaysTrueCodeFix : CodeFixProvider - { - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(ConditionIsAlwaysTrueAnalyzer.Rule.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var statement = root.FindNode(diagnosticSpan); - context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.ConditionIsAlwaysTrueCodeFixTitle, - x => RemoveConditionAsync(context.Document, root, statement), ConditionIsAlwaysTrueAnalyzer.Rule.Id), - diagnostic); - } - - private Task RemoveConditionAsync(Document document, SyntaxNode root, SyntaxNode statement) - { - var ifStatement = statement.Ancestors().OfType().First(); - - var blockStatement = ifStatement.Statement as BlockSyntax; - - var newRoot = blockStatement == null - ? root.ReplaceNode(ifStatement, ifStatement.Statement).WithAdditionalAnnotations(Formatter.Annotation) - : root.ReplaceNode(ifStatement, blockStatement.Statements).WithAdditionalAnnotations(Formatter.Annotation); - - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantAnalyzer.cs new file mode 100644 index 0000000..f727b4a --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantAnalyzer.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.ConditionIsConstant +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ConditionIsConstantAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.ConditionIsConstantAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.ConditionIsConstantAnalyzerTitle; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.ConditionIsConstant, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IfStatement); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var ifStatement = (IfStatementSyntax) context.Node; + + var constantValue = context.SemanticModel.GetConstantValue(ifStatement.Condition); + + if (!constantValue.HasValue) + { + return; + } + + if ((bool) constantValue.Value) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + ifStatement.Condition.GetLocation(), + ImmutableDictionary.CreateRange(new[] { new KeyValuePair("IsConditionTrue", "true") }), + "true")); + } + + if (!(bool) constantValue.Value) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + ifStatement.Condition.GetLocation(), + ImmutableDictionary.CreateRange(new[] { new KeyValuePair("IsConditionTrue", "false") }), + "false")); + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantCodeFix.cs new file mode 100644 index 0000000..8fbb81d --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsConstant/ConditionIsConstantCodeFix.cs @@ -0,0 +1,127 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.ConditionIsConstant +{ + [ExportCodeFixProvider(DiagnosticId.ConditionIsConstant + "CF", LanguageNames.CSharp), Shared] + public class ConditionIsConstantCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(ConditionIsConstantAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var statement = root.FindNode(diagnosticSpan); + + if (bool.Parse(diagnostic.Properties["IsConditionTrue"])) + { + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.ConditionIsConstantCodeFixTitle, + x => RemoveConstantTrueConditionAsync(context.Document, root, statement), + ConditionIsConstantAnalyzer.Rule.Id), + diagnostic); + } + else + { + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.ConditionIsConstantCodeFixTitle, + x => RemoveConstantFalseConditionAsync(context.Document, root, statement), + ConditionIsConstantAnalyzer.Rule.Id), + diagnostic); + } + } + + private Task RemoveConstantTrueConditionAsync(Document document, SyntaxNode root, SyntaxNode statement) + { + var ifStatement = statement.Ancestors().OfType().First(); + + var blockStatement = ifStatement.Statement as BlockSyntax; + + SyntaxNode newRoot; + + /* this condition will be true when the `if` does not have braces: + + if (condition) statement; + */ + if (blockStatement == null) + { + newRoot = root.ReplaceNode(ifStatement, ifStatement.Statement).WithAdditionalAnnotations(Formatter.Annotation); + } + else + { + /* if the if statement's parent is `SyntaxKind.ElseClause`, + the `else` does not have braces and needs the entire `if` block, braces and all: + + else if (condition) { statement; } + + otherwise, the block is already there and we need to replace `if` with just the block statements + also covers the general `if`-with-braces condition + + else { if (condition) { statements; } } + if (condition) { statements; } + */ + newRoot = ifStatement.Parent.IsKind(SyntaxKind.ElseClause) + ? root.ReplaceNode(ifStatement, blockStatement).WithAdditionalAnnotations(Formatter.Annotation) + : root.ReplaceNode(ifStatement, blockStatement.Statements).WithAdditionalAnnotations(Formatter.Annotation); + } + + var newDocument = document.WithSyntaxRoot(newRoot); + return Task.FromResult(newDocument.Project.Solution); + } + + private Task RemoveConstantFalseConditionAsync(Document document, SyntaxNode root, SyntaxNode statement) + { + var ifStatement = statement.Ancestors().OfType().First(); + var blockStatement = ifStatement.Else?.Statement as BlockSyntax; + + SyntaxNode newRoot; + + // all `if` conditions without being directly in a parent `else` + if (!ifStatement.Parent.IsKind(SyntaxKind.ElseClause)) + { + // no else + newRoot = + root.RemoveNode(ifStatement, SyntaxRemoveOptions.KeepNoTrivia) + .WithAdditionalAnnotations(Formatter.Annotation); + + // else with braces + if (blockStatement != null) + { + newRoot = + root.ReplaceNode(ifStatement, blockStatement.Statements) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + // else without braces + if (ifStatement.Else != null && blockStatement == null) + { + newRoot = + root.ReplaceNode(ifStatement, ifStatement.Else.Statement) + .WithAdditionalAnnotations(Formatter.Annotation); + } + } + else + { + newRoot = root.RemoveNode(ifStatement.Parent, SyntaxRemoveOptions.KeepLeadingTrivia & SyntaxRemoveOptions.KeepTrailingTrivia); + } + + var newDocument = document.WithSyntaxRoot(newRoot); + return Task.FromResult(newDocument.Project.Solution); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsAnalyzer.cs index 002779d..f9c90b9 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsAnalyzer.cs @@ -25,16 +25,13 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConditionalExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConditionalExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var conditionalExpression = context.Node as ConditionalExpressionSyntax; + var conditionalExpression = (ConditionalExpressionSyntax) context.Node; - var trueExpression = conditionalExpression?.WhenTrue as LiteralExpressionSyntax; + var trueExpression = conditionalExpression.WhenTrue as LiteralExpressionSyntax; if (trueExpression == null) { return; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsCodeFix.cs index cbea9f5..31cff54 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsDefaultOptions/ConditionalOperatorReturnsDefaultOptionsCodeFix.cs @@ -7,10 +7,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.ConditionalOperatorReturnsDefaultOptions { - [ExportCodeFixProvider(nameof(ConditionalOperatorReturnsDefaultOptionsCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.ConditionalOperatorReturnsDefaultOptions + "CF", LanguageNames.CSharp), Shared] public class ConditionalOperatorReturnsDefaultOptionsCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -36,8 +37,7 @@ private Task RemoveConditionalAsync(Document document, SyntaxNode root var conditionalExpression = (ConditionalExpressionSyntax) statement; var newRoot = - root.ReplaceNode(conditionalExpression, conditionalExpression.Condition) - .WithAdditionalAnnotations(Formatter.Annotation); + root.ReplaceNode(conditionalExpression, conditionalExpression.Condition.WithAdditionalAnnotations(Formatter.Annotation)); var newDocument = document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument.Project.Solution); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsAnalyzer.cs index ae08fb7..a54320a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsAnalyzer.cs @@ -25,16 +25,13 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConditionalExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConditionalExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var conditionalExpression = context.Node as ConditionalExpressionSyntax; + var conditionalExpression = (ConditionalExpressionSyntax) context.Node; - var trueExpression = conditionalExpression?.WhenTrue as LiteralExpressionSyntax; + var trueExpression = conditionalExpression.WhenTrue as LiteralExpressionSyntax; if (trueExpression == null) { return; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix.cs index a2e2e9f..472b580 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionalOperatorReturnsInvertedDefaultOptions/ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix.cs @@ -8,10 +8,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.ConditionalOperatorReturnsInvertedDefaultOptions { - [ExportCodeFixProvider(nameof(ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix), LanguageNames.CSharp), + [ExportCodeFixProvider(DiagnosticId.ConditionalOperatorReturnsInvertedDefaultOptions + "CF", LanguageNames.CSharp), Shared] public class ConditionalOperatorReturnsInvertedDefaultOptionsCodeFix : CodeFixProvider { @@ -47,7 +48,7 @@ private Task RemoveConditionalAsync(Document document, SyntaxNode root } var newRoot = - root.ReplaceNode(conditionalExpression, newExpression).WithAdditionalAnnotations(Formatter.Annotation); + root.ReplaceNode(conditionalExpression, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); var newDocument = document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument.Project.Solution); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ElementaryMethodsOfTypeInCollectionNotOverridden/ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ElementaryMethodsOfTypeInCollectionNotOverridden/ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.cs new file mode 100644 index 0000000..8c1cc4f --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ElementaryMethodsOfTypeInCollectionNotOverridden/ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer.cs @@ -0,0 +1,86 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.ElementaryMethodsOfTypeInCollectionNotOverridden +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.ElementaryMethodsOfTypeInCollectionNotOverriddenAnalyzerTitle; + + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.ElementaryMethodsOfTypeInCollectionNotOverridden, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ObjectCreationExpression); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var objectTypeInfo = context.SemanticModel.GetTypeInfo(context.Node).Type as INamedTypeSymbol; + + if (objectTypeInfo == null) + { + return; + } + + var ienumerableIsImplemented = objectTypeInfo.ImplementsInterface(typeof(IEnumerable)) || + objectTypeInfo.ImplementsInterface(typeof(IEnumerable<>)); + + if (!ienumerableIsImplemented) + { + return; + } + + var objectType = ((ObjectCreationExpressionSyntax) context.Node).Type as GenericNameSyntax; + if (objectType == null) + { + return; + } + + foreach (var genericType in objectType.TypeArgumentList.Arguments) + { + if (genericType == null) + { + return; + } + + var genericTypeInfo = context.SemanticModel.GetTypeInfo(genericType).Type; + if (genericTypeInfo == null || + genericTypeInfo.TypeKind == TypeKind.Interface || + genericTypeInfo.TypeKind == TypeKind.TypeParameter) + { + return; + } + + var implementsEquals = false; + var implementsGetHashCode = false; + foreach (var member in genericTypeInfo.GetMembers()) + { + if (member.Name == nameof(Equals)) + { + implementsEquals = true; + } + + if (member.Name == nameof(GetHashCode)) + { + implementsGetHashCode = true; + } + } + + if (!implementsEquals || !implementsGetHashCode) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, genericType.GetLocation())); + } + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.cs new file mode 100644 index 0000000..6491c21 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.EqualsAndGetHashcodeNotImplementedTogether +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class EqualsAndGetHashcodeNotImplementedTogetherAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.EqualsAndGetHashcodeNotImplementedTogetherAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.EqualsAndGetHashcodeNotImplementedTogetherAnalyzerTitle; + + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.EqualsAndGetHashcodeNotImplementedTogether, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterCompilationStartAction((compilationContext) => + { + var objectSymbol = compilationContext.Compilation.GetSpecialType(SpecialType.System_Object); + IMethodSymbol objectEquals = null; + IMethodSymbol objectGetHashCode = null; + + foreach (var symbol in objectSymbol.GetMembers()) + { + if (!(symbol is IMethodSymbol)) + { + continue; + } + + var method = (IMethodSymbol)symbol; + if (method.MetadataName == nameof(Equals) && method.Parameters.Length == 1) + { + objectEquals = method; + } + + if (method.MetadataName == nameof(GetHashCode) && !method.Parameters.Any()) + { + objectGetHashCode = method; + } + } + + compilationContext.RegisterSyntaxNodeAction((syntaxNodeContext) => + { + var classDeclaration = (ClassDeclarationSyntax) syntaxNodeContext.Node; + + var equalsImplemented = false; + var getHashcodeImplemented = false; + + foreach (var node in classDeclaration.Members) + { + if (!node.IsKind(SyntaxKind.MethodDeclaration)) + { + continue; + } + + var methodDeclaration = (MethodDeclarationSyntax)node; + if (!methodDeclaration.Modifiers.Contains(SyntaxKind.OverrideKeyword)) + { + continue; + } + + var methodSymbol = syntaxNodeContext.SemanticModel.GetDeclaredSymbol(methodDeclaration).OverriddenMethod; + + // this will happen if the base class is deleted and there is still a derived class + if (methodSymbol == null) + { + return; + } + + while (methodSymbol.IsOverride) + { + methodSymbol = methodSymbol.OverriddenMethod; + } + + if (methodSymbol == objectEquals) + { + equalsImplemented = true; + } + + if (methodSymbol == objectGetHashCode) + { + getHashcodeImplemented = true; + } + } + + if (equalsImplemented ^ getHashcodeImplemented) + { + syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(Rule, classDeclaration.Identifier.GetLocation(), + ImmutableDictionary.CreateRange(new[] { new KeyValuePair("IsEqualsImplemented", equalsImplemented.ToString()) }))); + } + }, SyntaxKind.ClassDeclaration); + }); + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherCodeFix.cs new file mode 100644 index 0000000..dfffd83 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/EqualsAndGetHashcodeNotImplementedTogether/EqualsAndGetHashcodeNotImplementedTogetherCodeFix.cs @@ -0,0 +1,96 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.EqualsAndGetHashcodeNotImplementedTogether +{ + [ExportCodeFixProvider(DiagnosticId.EqualsAndGetHashcodeNotImplementedTogether + "CF", LanguageNames.CSharp), Shared] + public class EqualsAndGetHashcodeNotImplementedTogetherCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var statement = root.FindNode(diagnosticSpan); + + if (bool.Parse(diagnostic.Properties["IsEqualsImplemented"])) + { + context.RegisterCodeFix( + CodeAction.Create(string.Format(VSDiagnosticsResources.EqualsAndGetHashcodeNotImplementedTogetherCodeFixTitle, "GetHashCode()"), + x => ImplementGetHashCodeAsync(context.Document, root, statement), + EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.Id), + diagnostic); + } + else + { + context.RegisterCodeFix( + CodeAction.Create(string.Format(VSDiagnosticsResources.EqualsAndGetHashcodeNotImplementedTogetherCodeFixTitle, "Equals(object obj)"), + x => ImplementEqualsAsync(context.Document, root, statement), + EqualsAndGetHashcodeNotImplementedTogetherAnalyzer.Rule.Id), + diagnostic); + } + } + + private async Task ImplementEqualsAsync(Document document, SyntaxNode root, SyntaxNode statement) + { + var classDeclaration = (ClassDeclarationSyntax) statement; + + var newRoot = root.ReplaceNode(classDeclaration, classDeclaration.AddMembers(GetEqualsMethod())); + var newDocument = await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot)); + return newDocument.Project.Solution; + } + + private async Task ImplementGetHashCodeAsync(Document document, SyntaxNode root, SyntaxNode statement) + { + var classDeclaration = (ClassDeclarationSyntax)statement; + + var newRoot = root.ReplaceNode(classDeclaration, classDeclaration.AddMembers(GetGetHashCodeMethod())); + var newDocument = await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot)); + return newDocument.Project.Solution; + } + + private MethodDeclarationSyntax GetEqualsMethod() + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + var bodyStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();") + .WithAdditionalAnnotations(Simplifier.Annotation); + var parameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("obj")) + .WithType(SyntaxFactory.ParseTypeName("object")); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("bool"), "Equals") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(bodyStatement) + .AddParameterListParameters(parameter) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private MethodDeclarationSyntax GetGetHashCodeMethod() + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + var bodyStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();") + .WithAdditionalAnnotations(Simplifier.Annotation); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("int"), "GetHashCode") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(bodyStatement) + .WithAdditionalAnnotations(Formatter.Annotation); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersAnalyzer.cs index d95ab4e..5ab6700 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -12,203 +11,194 @@ namespace VSDiagnostics.Diagnostics.General.ExplicitAccessModifiers internal class ExplicitAccessModifiersAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Hidden; - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; private static readonly string Message = VSDiagnosticsResources.ExplicitAccessModifiersAnalyzerMessage; private static readonly string Title = VSDiagnosticsResources.ExplicitAccessModifiersAnalyzerTitle; - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.ExplicitAccessModifiers, Title, Message, Category, Severity, true); + private readonly SyntaxKind[] _accessModifierKinds = + { + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.InternalKeyword, + SyntaxKind.PrivateKeyword + }; + + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.ExplicitAccessModifiers, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, - SyntaxKind.ClassDeclaration, - SyntaxKind.ConstructorDeclaration, - SyntaxKind.DelegateDeclaration, - SyntaxKind.EnumDeclaration, - SyntaxKind.EventDeclaration, - SyntaxKind.EventFieldDeclaration, - SyntaxKind.FieldDeclaration, - SyntaxKind.IndexerDeclaration, - SyntaxKind.InterfaceDeclaration, - SyntaxKind.MethodDeclaration, - SyntaxKind.PropertyDeclaration, - SyntaxKind.StructDeclaration); + context.RegisterSyntaxNodeAction(HandleClass, SyntaxKind.ClassDeclaration); + context.RegisterSyntaxNodeAction(HandleConstructor, SyntaxKind.ConstructorDeclaration); + context.RegisterSyntaxNodeAction(HandleDelegate, SyntaxKind.DelegateDeclaration); + context.RegisterSyntaxNodeAction(HandleEnum, SyntaxKind.EnumDeclaration); + context.RegisterSyntaxNodeAction(HandleEvent, SyntaxKind.EventDeclaration); + context.RegisterSyntaxNodeAction(HandleEventField, SyntaxKind.EventFieldDeclaration); + context.RegisterSyntaxNodeAction(HandleField, SyntaxKind.FieldDeclaration); + context.RegisterSyntaxNodeAction(HandleIndexer, SyntaxKind.IndexerDeclaration); + context.RegisterSyntaxNodeAction(HandleInterface, SyntaxKind.InterfaceDeclaration); + context.RegisterSyntaxNodeAction(HandleMethod, SyntaxKind.MethodDeclaration); + context.RegisterSyntaxNodeAction(HandleProperty, SyntaxKind.PropertyDeclaration); + context.RegisterSyntaxNodeAction(HandleStruct, SyntaxKind.StructDeclaration); } - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + private void HandleClass(SyntaxNodeAnalysisContext context) { - if (context.Node.Parent is InterfaceDeclarationSyntax) + var declarationExpression = (ClassDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - return; + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is ClassDeclarationSyntax) + private void HandleStruct(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (StructDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - var declarationExpression = (ClassDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is StructDeclarationSyntax) + private void HandleEnum(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (EnumDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - var declarationExpression = (StructDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is EnumDeclarationSyntax) + private void HandleDelegate(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (DelegateDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - var declarationExpression = (EnumDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is DelegateDeclarationSyntax) + private void HandleInterface(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (InterfaceDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - var declarationExpression = (DelegateDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is InterfaceDeclarationSyntax) + private void HandleField(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (FieldDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) { - var declarationExpression = (InterfaceDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), "private")); } + } - if (context.Node is FieldDeclarationSyntax) + private void HandleProperty(SyntaxNodeAnalysisContext context) + { + if (context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) { - var declarationExpression = (FieldDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - "private")); - } + return; } - if (context.Node is PropertyDeclarationSyntax) + var declarationExpression = (PropertyDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds) && + declarationExpression.ExplicitInterfaceSpecifier == null) { - var declarationExpression = (PropertyDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())) && - declarationExpression.ExplicitInterfaceSpecifier == null) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is MethodDeclarationSyntax) + private void HandleMethod(SyntaxNodeAnalysisContext context) + { + if (context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) { - var declarationExpression = (MethodDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())) && - declarationExpression.Modifiers.All(m => m.Kind() != SyntaxKind.PartialKeyword) && - declarationExpression.ExplicitInterfaceSpecifier == null) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + return; } - if (context.Node is ConstructorDeclarationSyntax) + var declarationExpression = (MethodDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds) && + !declarationExpression.Modifiers.Contains(SyntaxKind.PartialKeyword) && + declarationExpression.ExplicitInterfaceSpecifier == null) { - var declarationExpression = (ConstructorDeclarationSyntax) context.Node; - if ( - !declarationExpression.Modifiers.Any( - m => _accessModifierKinds.Contains(m.Kind()) || m.Kind() == SyntaxKind.StaticKeyword)) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; - - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is EventFieldDeclarationSyntax) + private void HandleConstructor(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (ConstructorDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds) && + !declarationExpression.Modifiers.Contains(SyntaxKind.StaticKeyword)) { - var declarationExpression = (EventFieldDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - "private")); - } + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } + } - if (context.Node is EventDeclarationSyntax) + private void HandleEventField(SyntaxNodeAnalysisContext context) + { + if (context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) { - var declarationExpression = (EventDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()))) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + return; + } - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var declarationExpression = (EventFieldDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), "private")); } + } - if (context.Node is IndexerDeclarationSyntax) + private void HandleEvent(SyntaxNodeAnalysisContext context) + { + if (context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) { - var declarationExpression = (IndexerDeclarationSyntax) context.Node; - if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())) && - declarationExpression.ExplicitInterfaceSpecifier == null) - { - var accessibility = - context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + return; + } - context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), - accessibility.ToString().ToLowerInvariant())); - } + var declarationExpression = (EventDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds)) + { + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); } } - private readonly SyntaxKind[] _accessModifierKinds = + private void HandleIndexer(SyntaxNodeAnalysisContext context) { - SyntaxKind.PublicKeyword, - SyntaxKind.ProtectedKeyword, - SyntaxKind.InternalKeyword, - SyntaxKind.PrivateKeyword - }; + if (context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) + { + return; + } + + var declarationExpression = (IndexerDeclarationSyntax) context.Node; + if (!declarationExpression.Modifiers.ContainsAny(_accessModifierKinds) && + declarationExpression.ExplicitInterfaceSpecifier == null) + { + var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility; + context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(), + accessibility.ToString().ToLowerInvariant())); + } + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersCodeFix.cs index 0cc597a..3bb0b30 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ExplicitAccessModifiers/ExplicitAccessModifiersCodeFix.cs @@ -6,10 +6,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.ExplicitAccessModifiers { - [ExportCodeFixProvider(nameof(ExplicitAccessModifiersCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.ExplicitAccessModifiers + "CF", LanguageNames.CSharp), Shared] public class ExplicitAccessModifiersCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -31,12 +32,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.ExplicitAccessModifiersCodeFixTitle, - x => AddModifier(context.Document, root, statement, accessibility), + x => AddModifierAsync(context.Document, root, statement, accessibility), ExplicitAccessModifiersAnalyzer.Rule.Id), diagnostic); } - private Task AddModifier(Document document, SyntaxNode root, SyntaxNode statement, - Accessibility accessibility) + private Task AddModifierAsync(Document document, SyntaxNode root, SyntaxNode statement, + Accessibility accessibility) { var generator = SyntaxGenerator.GetGenerator(document); var newStatement = generator.WithAccessibility(statement, accessibility); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GetHashCodeRefersToMutableMember/GetHashCodeRefersToMutableMemberAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GetHashCodeRefersToMutableMember/GetHashCodeRefersToMutableMemberAnalyzer.cs new file mode 100644 index 0000000..2c70075 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GetHashCodeRefersToMutableMember/GetHashCodeRefersToMutableMemberAnalyzer.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace VSDiagnostics.Diagnostics.General.GetHashCodeRefersToMutableMember +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GetHashCodeRefersToMutableMemberAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.GetHashCodeRefersToMutableFieldAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.GetHashCodeRefersToMutableFieldAnalyzerTitle; + + internal static DiagnosticDescriptor Rule => + new DiagnosticDescriptor(DiagnosticId.GetHashCodeRefersToMutableField, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + + private void AnalyzeSymbol(SymbolAnalysisContext context) + { + var namedType = (INamedTypeSymbol)context.Symbol; + var semanticModel = context.Compilation.GetSemanticModel(context.Symbol.Locations[0].SourceTree); + + var getHashCode = GetHashCodeSymbol(namedType); + if (getHashCode == null) { return; } + + var getHashCodeLocation = getHashCode.Locations[0]; + var root = getHashCodeLocation?.SourceTree.GetRoot(context.CancellationToken); + if (root == null) + { + return; + } + + var getHashCodeNode = (MethodDeclarationSyntax)root.FindNode(getHashCodeLocation.SourceSpan); + var nodes = getHashCodeNode.DescendantNodes(descendIntoChildren: target => true); + + var identifierNameNodes = nodes.OfType(SyntaxKind.IdentifierName); + foreach (var node in identifierNameNodes) + { + var symbol = semanticModel.GetSymbolInfo(node).Symbol; + if (symbol == null) + { + continue; + } + + if (symbol.Kind == SymbolKind.Field) + { + var fieldIsMutableOrStatic = FieldIsMutableOrStatic((IFieldSymbol) symbol); + if (fieldIsMutableOrStatic.Item1) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, getHashCode.Locations[0], + fieldIsMutableOrStatic.Item2)); + } + } + else if (symbol.Kind == SymbolKind.Property) + { + var propertyIsMutable = PropertyIsMutable((IPropertySymbol) symbol, root); + if (propertyIsMutable.Item1) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, getHashCode.Locations[0], + propertyIsMutable.Item2)); + } + } + } + } + + private IMethodSymbol GetHashCodeSymbol(INamedTypeSymbol symbol) + { + foreach (var member in symbol.GetMembers()) + { + if (!(member is IMethodSymbol)) + { + continue; + } + + var method = (IMethodSymbol)member; + if (method.MetadataName == nameof(GetHashCode) && method.Parameters.Length == 0) + { + return method; + } + } + + return null; + } + + private Tuple FieldIsMutableOrStatic(IFieldSymbol field) + { + var description = string.Empty; + var returnResult = false; + + if (field.IsConst) + { + description += "const "; + returnResult = true; + } + + // constant fields are marked static + if (field.IsStatic && !field.IsConst) + { + description += "static "; + returnResult = true; + } + + // constant fields are marked non-readonly + if (!field.IsReadOnly && !field.IsConst) + { + description += "non-readonly "; + returnResult = true; + } + + if (!field.Type.IsValueType && field.Type.SpecialType != SpecialType.System_String) + { + description += "non-value type, non-string "; + returnResult = true; + } + + return Tuple.Create(returnResult, description + "field " + field.Name); + } + + private Tuple PropertyIsMutable(IPropertySymbol property, SyntaxNode root) + { + var description = string.Empty; + var returnResult = false; + + if (property.IsStatic) + { + description += "static "; + returnResult = true; + } + + if (!property.Type.IsValueType && property.Type.SpecialType != SpecialType.System_String) + { + description += "non-value type, non-string "; + returnResult = true; + } + + if (property.SetMethod != null) + { + description += "settable "; + returnResult = true; + } + + var propertyLocation = property.Locations[0]; + var propertyNode = (PropertyDeclarationSyntax) root.FindNode(propertyLocation.SourceSpan); + + // ensure getter does not have body + // the property has to have at least one of {get, set}, and it doesn't have a set (see above) + // this will not have an NRE in First() + // the accessor list might be null if it uses the arrow operator `=>` + if (propertyNode.AccessorList == null || + propertyNode.AccessorList.Accessors[0].Body != null) + { + description += "property with bodied getter "; + returnResult = true; + } + else + { + description += "property "; + } + + return Tuple.Create(returnResult, description + property.Name); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GotoDetection/GotoDetectionAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GotoDetection/GotoDetectionAnalyzer.cs index ad5e9c8..f921cd0 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GotoDetection/GotoDetectionAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/GotoDetection/GotoDetectionAnalyzer.cs @@ -8,7 +8,7 @@ namespace VSDiagnostics.Diagnostics.General.GotoDetection { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class GotoDetectionAnalyzer : DiagnosticAnalyzer + public class GotoDetectionAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; @@ -21,19 +21,12 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.GotoStatement, SyntaxKind.GotoCaseStatement, - SyntaxKind.GotoDefaultStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.GotoStatement, SyntaxKind.GotoCaseStatement, + SyntaxKind.GotoDefaultStatement); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var literalExpression = context.Node as GotoStatementSyntax; - if (literalExpression == null) - { - return; - } + var literalExpression = (GotoStatementSyntax) context.Node; context.ReportDiagnostic(Diagnostic.Create(Rule, literalExpression.GetLocation())); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesAnalyzer.cs deleted file mode 100644 index 69edf21..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesAnalyzer.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using VSDiagnostics.Utilities; - -namespace VSDiagnostics.Diagnostics.General.IfStatementWithoutBraces -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class IfStatementWithoutBracesAnalyzer : DiagnosticAnalyzer - { - private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; - private static readonly string Message = VSDiagnosticsResources.IfStatementWithoutBracesAnalyzerMessage; - private static readonly string Title = VSDiagnosticsResources.IfStatementWithoutBracesAnalyzerTitle; - - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.IfStatementWithoutBraces, Title, Message, Category, Severity, true); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IfStatement, SyntaxKind.ElseClause); - } - - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) - { - var ifStatement = context.Node as IfStatementSyntax; - if (ifStatement != null) - { - HandleIf(context, ifStatement); - } - - var elseClause = context.Node as ElseClauseSyntax; - if (elseClause != null) - { - HandleElse(context, elseClause); - } - } - - private void HandleIf(SyntaxNodeAnalysisContext context, IfStatementSyntax ifStatement) - { - if (ifStatement.Statement is BlockSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, ifStatement.IfKeyword.GetLocation())); - } - - private void HandleElse(SyntaxNodeAnalysisContext context, ElseClauseSyntax elseClause) - { - if (elseClause.Statement is BlockSyntax) - { - return; - } - - if (elseClause.Statement is IfStatementSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, elseClause.ElseKeyword.GetLocation())); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesCodeFix.cs deleted file mode 100644 index 15c51bb..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/IfStatementWithoutBraces/IfStatementWithoutBracesCodeFix.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace VSDiagnostics.Diagnostics.General.IfStatementWithoutBraces -{ - [ExportCodeFixProvider(nameof(IfStatementWithoutBracesCodeFix), LanguageNames.CSharp), Shared] - public class IfStatementWithoutBracesCodeFix : CodeFixProvider - { - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(IfStatementWithoutBracesAnalyzer.Rule.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var statement = root.FindNode(diagnosticSpan); - context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.IfStatementWithoutBracesCodeFixTitle, - x => UseBracesNotationAsync(context.Document, root, statement), - IfStatementWithoutBracesAnalyzer.Rule.Id), diagnostic); - } - - private Task UseBracesNotationAsync(Document document, SyntaxNode root, SyntaxNode statement) - { - SyntaxNode newBlock = null; - - var ifSyntax = statement as IfStatementSyntax; - if (ifSyntax != null) - { - newBlock = GetNewBlock(statement, ifSyntax.Statement); - } - - var elseSyntax = statement as ElseClauseSyntax; - if (elseSyntax != null) - { - newBlock = GetNewBlock(statement, elseSyntax.Statement); - } - - var newRoot = root.ReplaceNode(statement, newBlock); - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); - } - - private SyntaxNode GetNewBlock(SyntaxNode statement, StatementSyntax statementBody) - { - var body = SyntaxFactory.Block(statementBody); - return statement.ReplaceNode(statementBody, body); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeAnalyzer.cs new file mode 100644 index 0000000..36feb51 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeAnalyzer.cs @@ -0,0 +1,100 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.ImplementEqualsAndGetHashCode +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ImplementEqualsAndGetHashCodeAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Hidden; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.ImplementEqualsAndGetHashCodeAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.ImplementEqualsAndGetHashCodeAnalyzerTitle; + + internal static DiagnosticDescriptor Rule => + new DiagnosticDescriptor(DiagnosticId.ImplementEqualsAndGetHashCode, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + + private void AnalyzeSymbol(SymbolAnalysisContext symbol) + { + IMethodSymbol objectEquals; + IMethodSymbol objectGetHashCode; + + var namedType = (INamedTypeSymbol)symbol.Symbol; + GetEqualsAndGetHashCodeSymbols(namedType, out objectEquals, out objectGetHashCode); + + if (objectEquals != null || objectGetHashCode != null) { return; } + + if (MembersContainNonStaticFieldOrProperty(namedType.GetMembers())) + { + for (var i = 0; i < namedType.Locations.Count(); i++) + { + symbol.ReportDiagnostic(Diagnostic.Create(Rule, namedType.Locations[i], + namedType.TypeKind == TypeKind.Class ? "Class" : "Struct", namedType.Name)); + } + } + } + + private void GetEqualsAndGetHashCodeSymbols(INamedTypeSymbol symbol, out IMethodSymbol equalsSymbol, out IMethodSymbol getHashCodeSymbol) + { + equalsSymbol = null; + getHashCodeSymbol = null; + + foreach (var member in symbol.GetMembers()) + { + if (!(member is IMethodSymbol)) + { + continue; + } + + var method = (IMethodSymbol)member; + if (method.MetadataName == nameof(Equals) && method.Parameters.Count() == 1) + { + equalsSymbol = method; + } + + if (method.MetadataName == nameof(GetHashCode) && !method.Parameters.Any()) + { + getHashCodeSymbol = method; + } + } + } + + private bool MembersContainNonStaticFieldOrProperty(ImmutableArray members) + { + foreach (var member in members) + { + if (member.Kind != SymbolKind.Field && member.Kind != SymbolKind.Property) + { + continue; + } + + if (member.IsStatic) + { + continue; + } + + if (member.Kind == SymbolKind.Field) + { + return true; + } + + var property = (IPropertySymbol) member; + if (property.GetMethod != null) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeCodeFix.cs new file mode 100644 index 0000000..8d9e9dc --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ImplementEqualsAndGetHashCode/ImplementEqualsAndGetHashCodeCodeFix.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; +using System.Collections.Generic; + +namespace VSDiagnostics.Diagnostics.General.ImplementEqualsAndGetHashCode +{ + [ExportCodeFixProvider(DiagnosticId.ImplementEqualsAndGetHashCode + "CF", LanguageNames.CSharp), Shared] + public class ImplementEqualsAndGetHashCodeCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(ImplementEqualsAndGetHashCodeAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var statement = root.FindNode(diagnosticSpan); + + context.RegisterCodeFix( + CodeAction.Create(string.Format(VSDiagnosticsResources.ImplementEqualsAndGetHashCodeCodeFixTitle), + x => ImplementEqualsAndGetHashCodeAsync(context.Document, root, statement), + ImplementEqualsAndGetHashCodeAnalyzer.Rule.Id), + diagnostic); + } + + private async Task ImplementEqualsAndGetHashCodeAsync(Document document, SyntaxNode root, SyntaxNode declaration) + { + var model = await document.GetSemanticModelAsync(); + + var objectSymbol = model.Compilation.GetSpecialType(SpecialType.System_Object); + var objectEquals = objectSymbol.GetMembers().OfType() + .Single(method => method.MetadataName == nameof(Equals) && method.Parameters.Count() == 1); + + var typeDeclaration = (TypeDeclarationSyntax) declaration; + var typeSymbol = model.GetDeclaredSymbol(typeDeclaration); + + var equalsMethod = GetEqualsMethod(typeDeclaration.Identifier, typeSymbol.GetMembers(), typeSymbol, objectEquals); + var getHashCodeMethod = GetGetHashCodeMethod(typeSymbol.GetMembers()); + + var newNode = declaration.IsKind(SyntaxKind.ClassDeclaration) + ? (TypeDeclarationSyntax)((ClassDeclarationSyntax)typeDeclaration).AddMembers(equalsMethod, getHashCodeMethod) + : (TypeDeclarationSyntax)((StructDeclarationSyntax)typeDeclaration).AddMembers(equalsMethod, getHashCodeMethod); + + return document.WithSyntaxRoot(root.ReplaceNode(typeDeclaration, newNode)); + } + + private MethodDeclarationSyntax GetEqualsMethod(SyntaxToken identifier, ImmutableArray members, INamedTypeSymbol typeSymbol, IMethodSymbol objectEquals) + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + + var parameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("obj")) + .WithType(SyntaxFactory.ParseTypeName("object")); + + var ifStatement = SyntaxFactory.IfStatement(SyntaxFactory.ParseExpression($"obj == null || typeof({identifier}) != obj.GetType()"), + SyntaxFactory.Block(SyntaxFactory.ParseStatement("return false;"))); + + // the default formatting is a single space--we want a new line + var castStatement = SyntaxFactory.ParseStatement($"var value = ({identifier}) obj;{Environment.NewLine}"); + + var fieldAndPropertyEqualityStatements = new List(); + foreach (var member in members) + { + var fieldEqualityStatement = GetFieldComparisonStatement(member); + if (!string.IsNullOrEmpty(fieldEqualityStatement)) + { + fieldAndPropertyEqualityStatements.Add(fieldEqualityStatement); + } + + var propertyEqualityStatement = GetPropertyComparisonStatement(member); + if (!string.IsNullOrEmpty(propertyEqualityStatement)) + { + fieldAndPropertyEqualityStatements.Add(propertyEqualityStatement); + } + } + + var symbolHasBaseTypeOverridingEquals = BaseClassImplementsEquals(objectEquals, typeSymbol); + + var returnStatement = SyntaxFactory.ParseStatement( + $"return {(symbolHasBaseTypeOverridingEquals ? $"base.Equals(obj) &&{Environment.NewLine} " : string.Empty)}" + + string.Join($" &&{Environment.NewLine} ", fieldAndPropertyEqualityStatements) + ";"); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("bool"), "Equals") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(ifStatement, castStatement, returnStatement) + .AddParameterListParameters(parameter) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private string GetFieldComparisonStatement(ISymbol member) + { + if (member.Kind != SymbolKind.Field) + { + return string.Empty; + } + + var field = (IFieldSymbol)member; + if (field.IsStatic || field.IsConst) + { + return string.Empty; + } + + if (!field.DeclaringSyntaxReferences.Any()) + { + return string.Empty; + } + + return field.Type.IsValueType + ? $"{member.Name}.Equals(value.{member.Name})" + : $"{member.Name} == value.{member.Name}"; + } + + private string GetPropertyComparisonStatement(ISymbol member) + { + if (member.Kind != SymbolKind.Property) + { + return string.Empty; + } + + var property = (IPropertySymbol)member; + if (property.IsStatic) + { + return string.Empty; + } + + if (property.IsWriteOnly) + { + return string.Empty; + } + + return property.Type.IsValueType + ? $"{property.Name}.Equals(value.{property.Name})" + : $"{property.Name} == value.{property.Name}"; + } + + private MethodDeclarationSyntax GetGetHashCodeMethod(ImmutableArray members) + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + + var fieldAndPropertyGetHashCodeStatements = new List(); + foreach (var member in members) + { + var fieldGetHashCodeStatement = GetFieldHashCodeStatements(member); + if (!string.IsNullOrEmpty(fieldGetHashCodeStatement)) + { + fieldAndPropertyGetHashCodeStatements.Add(fieldGetHashCodeStatement); + } + + var propertyGetHashCodeStatement = GetPropertyHashCodeStatement(member); + if (!string.IsNullOrEmpty(propertyGetHashCodeStatement)) + { + fieldAndPropertyGetHashCodeStatements.Add(propertyGetHashCodeStatement); + } + } + + if (!fieldAndPropertyGetHashCodeStatements.Any()) + { + fieldAndPropertyGetHashCodeStatements.Add("base.GetHashCode()"); + } + + var returnStatement = SyntaxFactory.ParseStatement("return " + + string.Join($" ^{Environment.NewLine} ", + fieldAndPropertyGetHashCodeStatements) + ";") + .WithLeadingTrivia( + SyntaxFactory.ParseLeadingTrivia( +@"// Add any fields you're interested in, taking into account the guidelines described in +// https://msdn.microsoft.com/en-us/library/system.object.gethashcode%28v=vs.110%29.aspx +")); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("int"), "GetHashCode") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(returnStatement) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private string GetFieldHashCodeStatements(ISymbol member) + { + if (member.Kind != SymbolKind.Field) + { + return string.Empty; + } + + var field = (IFieldSymbol) member; + if (field.IsStatic || field.IsConst) + { + return string.Empty; + } + + if (!field.DeclaringSyntaxReferences.Any()) + { + return string.Empty; + } + + var symbol = field.Type; + if (!symbol.IsValueType && symbol.SpecialType != SpecialType.System_String) + { + return string.Empty; + } + + if (field.IsReadOnly) + { + return $"{field.Name}.GetHashCode()"; + } + + return string.Empty; + } + + private string GetPropertyHashCodeStatement(ISymbol member) + { + if (member.Kind != SymbolKind.Property) + { + return string.Empty; + } + + var property = (IPropertySymbol)member; + if (property.IsStatic) + { + return string.Empty; + } + + var symbol = property.Type; + if (!symbol.IsValueType && symbol.SpecialType != SpecialType.System_String) + { + return string.Empty; + } + + if (!property.IsReadOnly) + { + return string.Empty; + } + + var propertyNode = (PropertyDeclarationSyntax)property.DeclaringSyntaxReferences.First().GetSyntax(); + if (propertyNode.AccessorList != null && + propertyNode.AccessorList.Accessors.First(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)).Body == null) + { + return $"{property.Name}.GetHashCode()"; + } + + return string.Empty; + } + + private bool BaseClassImplementsEquals(IMethodSymbol objectEquals, INamedTypeSymbol symbol) + { + if (symbol.TypeKind != TypeKind.Class) + { + return false; + } + + var baseType = symbol.BaseType; + if (baseType == null) + { + return false; + } + + foreach (var member in baseType.GetMembers()) + { + var method = member as IMethodSymbol; + if (method == null || !method.IsOverride) + { + continue; + } + + while (method.IsOverride) + { + method = method.OverriddenMethod; + } + + if (method == objectEquals) + { + return true; + } + } + + return BaseClassImplementsEquals(objectEquals, baseType); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesAnalyzer.cs deleted file mode 100644 index 9ce75ba..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesAnalyzer.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using VSDiagnostics.Utilities; - -namespace VSDiagnostics.Diagnostics.General.LoopStatementWithoutBraces -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class LoopStatementWithoutBracesAnalyzer : DiagnosticAnalyzer - { - private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; - private static readonly string Message = VSDiagnosticsResources.LoopStatementWithoutBracesAnalyzerMessage; - private static readonly string Title = VSDiagnosticsResources.LoopStatementWithoutBracesAnalyzerTitle; - - internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.LoopStatementWithoutBraces, Title, Message, Category, Severity, true); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ForStatement, SyntaxKind.ForEachStatement, - SyntaxKind.WhileStatement, SyntaxKind.DoStatement); - } - - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) - { - var forLoop = context.Node as ForStatementSyntax; - if (forLoop != null) - { - HandleFor(context, forLoop); - return; - } - - var whileLoop = context.Node as WhileStatementSyntax; - if (whileLoop != null) - { - HandleWhile(context, whileLoop); - return; - } - - var foreachLoop = context.Node as ForEachStatementSyntax; - if (foreachLoop != null) - { - HandleForeach(context, foreachLoop); - return; - } - - var doLoop = context.Node as DoStatementSyntax; - if (doLoop != null) - { - HandleDo(context, doLoop); - } - } - - private void HandleFor(SyntaxNodeAnalysisContext context, ForStatementSyntax loop) - { - if (loop.Statement is BlockSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, loop.ForKeyword.GetLocation())); - } - - private void HandleWhile(SyntaxNodeAnalysisContext context, WhileStatementSyntax loop) - { - if (loop.Statement is BlockSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, loop.WhileKeyword.GetLocation())); - } - - private void HandleForeach(SyntaxNodeAnalysisContext context, ForEachStatementSyntax loop) - { - if (loop.Statement is BlockSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, loop.ForEachKeyword.GetLocation())); - } - - private void HandleDo(SyntaxNodeAnalysisContext context, DoStatementSyntax loop) - { - if (loop.Statement is BlockSyntax) - { - return; - } - - context.ReportDiagnostic(Diagnostic.Create(Rule, loop.DoKeyword.GetLocation())); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesCodeFix.cs deleted file mode 100644 index 977134d..0000000 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopStatementWithoutBraces/LoopStatementWithoutBracesCodeFix.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace VSDiagnostics.Diagnostics.General.LoopStatementWithoutBraces -{ - [ExportCodeFixProvider("LoopWithoutBraces", LanguageNames.CSharp), Shared] - public class LoopStatementWithoutBracesCodeFix : CodeFixProvider - { - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(LoopStatementWithoutBracesAnalyzer.Rule.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var statement = root.FindNode(diagnosticSpan); - context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.LoopStatementWithoutBracesCodeFixTitle, - x => UseBracesNotationAsync(context.Document, root, statement), - LoopStatementWithoutBracesAnalyzer.Rule.Id), diagnostic); - } - - private Task UseBracesNotationAsync(Document document, SyntaxNode root, SyntaxNode statement) - { - SyntaxNode newBlock = null; - - var forSyntax = statement as ForStatementSyntax; - if (forSyntax != null) - { - newBlock = GetNewBlock(statement, forSyntax.Statement); - } - - var whileSyntax = statement as WhileStatementSyntax; - if (whileSyntax != null) - { - newBlock = GetNewBlock(statement, whileSyntax.Statement); - } - - var foreachSyntax = statement as ForEachStatementSyntax; - if (foreachSyntax != null) - { - newBlock = GetNewBlock(statement, foreachSyntax.Statement); - } - - var doSyntax = statement as DoStatementSyntax; - if (doSyntax != null) - { - newBlock = GetNewBlock(statement, doSyntax.Statement); - } - - var newRoot = root.ReplaceNode(statement, newBlock); - var newDocument = document.WithSyntaxRoot(newRoot); - return Task.FromResult(newDocument.Project.Solution); - } - - private SyntaxNode GetNewBlock(SyntaxNode statement, StatementSyntax statementBody) - { - var body = SyntaxFactory.Block(statementBody); - return statement.ReplaceNode(statementBody, body); - } - } -} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopedRandomInstantiation/LoopedRandomInstantiationAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopedRandomInstantiation/LoopedRandomInstantiationAnalyzer.cs new file mode 100644 index 0000000..2ffdec6 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/LoopedRandomInstantiation/LoopedRandomInstantiationAnalyzer.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.LoopedRandomInstantiation +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class LoopedRandomInstantiationAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.LoopedRandomInstantiationAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.LoopedRandomInstantiationAnalyzerTitle; + + private readonly SyntaxKind[] _loopTypes = { SyntaxKind.ForEachStatement, SyntaxKind.ForStatement, SyntaxKind.WhileStatement, SyntaxKind.DoStatement }; + + internal static DiagnosticDescriptor Rule => + new DiagnosticDescriptor(DiagnosticId.LoopedRandomInstantiation, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.VariableDeclaration); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var variableDeclaration = (VariableDeclarationSyntax) context.Node; + + var type = variableDeclaration.Type; + if (type == null) + { + return; + } + + var typeInfo = context.SemanticModel.GetTypeInfo(type).Type; + + if (typeInfo?.OriginalDefinition.ContainingNamespace == null || + typeInfo.OriginalDefinition.ContainingNamespace.Name != nameof(System) || + typeInfo.Name != nameof(Random)) + { + return; + } + + SyntaxNode currentNode = variableDeclaration; + while (!currentNode.IsAnyKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration)) + { + if (_loopTypes.Contains(currentNode.Kind())) + { + foreach (var declarator in variableDeclaration.Variables) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, declarator.GetLocation(), declarator.Identifier.Text)); + } + return; + } + + currentNode = currentNode.Parent; + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesAnalyzer.cs new file mode 100644 index 0000000..8177165 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesAnalyzer.cs @@ -0,0 +1,142 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.MissingBraces +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class MissingBracesAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.MissingBracesAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.MissingBracesAnalyzerTitle; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.MissingBraces, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeIfSymbol, SyntaxKind.IfStatement); + context.RegisterSyntaxNodeAction(AnalyzeElseSymbol, SyntaxKind.ElseClause); + context.RegisterSyntaxNodeAction(AnalyzeForSymbol, SyntaxKind.ForStatement); + context.RegisterSyntaxNodeAction(AnalyzeForEachSymbol, SyntaxKind.ForEachStatement); + context.RegisterSyntaxNodeAction(AnalyzeWhileSymbol, SyntaxKind.WhileStatement); + context.RegisterSyntaxNodeAction(AnalyzeDoSymbol, SyntaxKind.DoStatement); + context.RegisterSyntaxNodeAction(AnalyzeUsingSymbol, SyntaxKind.UsingStatement); + context.RegisterSyntaxNodeAction(AnalyzeLockSymbol, SyntaxKind.LockStatement); + context.RegisterSyntaxNodeAction(AnalyzeFixedSymbol, SyntaxKind.FixedStatement); + context.RegisterSyntaxNodeAction(AnalyzeSwitchSection, SyntaxKind.SwitchSection); + } + + private void AnalyzeIfSymbol(SyntaxNodeAnalysisContext context) + { + var ifStatement = (IfStatementSyntax)context.Node; + if (!ifStatement.Statement.IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + ifStatement.IfKeyword.GetLocation(), "An", SyntaxFacts.GetText(SyntaxKind.IfKeyword))); + } + } + + private void AnalyzeElseSymbol(SyntaxNodeAnalysisContext context) + { + var elseClause = (ElseClauseSyntax)context.Node; + if (!elseClause.Statement.IsKind(SyntaxKind.Block) && + !elseClause.Statement.IsKind(SyntaxKind.IfStatement)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + elseClause.ElseKeyword.GetLocation(), "An", SyntaxFacts.GetText(SyntaxKind.ElseKeyword))); + } + } + + private void AnalyzeForSymbol(SyntaxNodeAnalysisContext context) + { + var forStatement = (ForStatementSyntax)context.Node; + if (!forStatement.Statement.IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + forStatement.ForKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.ForKeyword))); + } + } + + private void AnalyzeForEachSymbol(SyntaxNodeAnalysisContext context) + { + var forEachStatement = (ForEachStatementSyntax)context.Node; + if (!forEachStatement.Statement.IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + forEachStatement.ForEachKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.ForEachKeyword))); + } + } + + private void AnalyzeWhileSymbol(SyntaxNodeAnalysisContext context) + { + var whileStatement = (WhileStatementSyntax)context.Node; + if (!whileStatement.Statement.IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + whileStatement.WhileKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.WhileKeyword))); + } + } + + private void AnalyzeDoSymbol(SyntaxNodeAnalysisContext context) + { + var doStatement = (DoStatementSyntax)context.Node; + if (!doStatement.Statement.IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + doStatement.DoKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.DoKeyword))); + } + } + + private void AnalyzeUsingSymbol(SyntaxNodeAnalysisContext context) + { + var usingStatement = (UsingStatementSyntax)context.Node; + if (!usingStatement.Statement.IsKind(SyntaxKind.Block) && + !usingStatement.Statement.IsKind(SyntaxKind.UsingStatement)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + usingStatement.UsingKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.UsingKeyword))); + } + } + + private void AnalyzeLockSymbol(SyntaxNodeAnalysisContext context) + { + var lockStatement = (LockStatementSyntax)context.Node; + if (!lockStatement.Statement.IsKind(SyntaxKind.Block) && + !lockStatement.Statement.IsKind(SyntaxKind.LockStatement)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + lockStatement.LockKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.LockKeyword))); + } + } + + private void AnalyzeFixedSymbol(SyntaxNodeAnalysisContext context) + { + var fixedStatement = (FixedStatementSyntax)context.Node; + if (!fixedStatement.Statement.IsKind(SyntaxKind.Block) && + !fixedStatement.Statement.IsKind(SyntaxKind.FixedStatement)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + fixedStatement.FixedKeyword.GetLocation(), "A", SyntaxFacts.GetText(SyntaxKind.FixedKeyword))); + } + } + + private void AnalyzeSwitchSection(SyntaxNodeAnalysisContext context) + { + var switchSection = (SwitchSectionSyntax)context.Node; + if (switchSection.Statements.Count != 1 || !switchSection.Statements[0].IsKind(SyntaxKind.Block)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, + switchSection.GetLocation(), "A", "switch section")); + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesCodeFix.cs new file mode 100644 index 0000000..b67d409 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/MissingBraces/MissingBracesCodeFix.cs @@ -0,0 +1,102 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.MissingBraces +{ + [ExportCodeFixProvider(DiagnosticId.MissingBraces + "CF", LanguageNames.CSharp), Shared] + public class MissingBracesCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(MissingBracesAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.MissingBracesCodeFixTitle, + x => AddBracesAsync(context, x), + MissingBracesAnalyzer.Rule.Id), diagnostic); + } + + private async Task AddBracesAsync(CodeFixContext context, CancellationToken cancellationToken) + { + var root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + var statement = root.FindNode(diagnosticSpan); + + var newRoot = root.ReplaceNode(statement, GetReplacementNode(statement)); + return context.Document.WithSyntaxRoot(newRoot); + } + + private SyntaxNode GetReplacementNode(SyntaxNode statement) + { + switch (statement.Kind()) + { + case SyntaxKind.IfStatement: + var ifSyntax = (IfStatementSyntax)statement; + return GetNewBlock(statement, ifSyntax.Statement); + + case SyntaxKind.ElseClause: + var elseClause = (ElseClauseSyntax)statement; + return GetNewBlock(statement, elseClause.Statement); + + case SyntaxKind.ForStatement: + var forSyntax = (ForStatementSyntax)statement; + return GetNewBlock(statement, forSyntax.Statement); + + case SyntaxKind.ForEachStatement: + var forEachSyntax = (ForEachStatementSyntax)statement; + return GetNewBlock(statement, forEachSyntax.Statement); + + case SyntaxKind.WhileStatement: + var whileSyntax = (WhileStatementSyntax)statement; + return GetNewBlock(statement, whileSyntax.Statement); + + case SyntaxKind.DoStatement: + var doSyntax = (DoStatementSyntax)statement; + return GetNewBlock(statement, doSyntax.Statement); + + case SyntaxKind.UsingStatement: + var usingSyntax = (UsingStatementSyntax)statement; + return GetNewBlock(statement, usingSyntax.Statement); + + case SyntaxKind.LockStatement: + var lockSyntax = (LockStatementSyntax)statement; + return GetNewBlock(statement, lockSyntax.Statement); + + case SyntaxKind.FixedStatement: + var fixedSyntax = (FixedStatementSyntax)statement; + return GetNewBlock(statement, fixedSyntax.Statement); + + case SyntaxKind.SwitchSection: + var switchSectionSyntax = (SwitchSectionSyntax)statement; + return GetNewBlock(statement, switchSectionSyntax); + } + + return default(SyntaxNode); + } + + private SyntaxNode GetNewBlock(SyntaxNode statement, StatementSyntax statementBody) => + statement.ReplaceNode(statementBody, SyntaxFactory.Block(statementBody).WithAdditionalAnnotations(Formatter.Annotation)); + + private SyntaxNode GetNewBlock(SyntaxNode statement, SwitchSectionSyntax switchSection) => + statement.ReplaceNode(switchSection, + SyntaxFactory.SwitchSection(switchSection.Labels, + SyntaxFactory.List(new[] + {SyntaxFactory.Block(switchSection.Statements).WithAdditionalAnnotations(Formatter.Annotation)}))); + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs index 18cae52..771c3f7 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs @@ -23,26 +23,23 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, - SyntaxKind.FieldDeclaration, - SyntaxKind.PropertyDeclaration, - SyntaxKind.MethodDeclaration, - SyntaxKind.ClassDeclaration, - SyntaxKind.InterfaceDeclaration, - SyntaxKind.LocalDeclarationStatement, - SyntaxKind.Parameter, - SyntaxKind.StructDeclaration, - SyntaxKind.EnumDeclaration, - SyntaxKind.EnumMemberDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, + SyntaxKind.FieldDeclaration, + SyntaxKind.PropertyDeclaration, + SyntaxKind.MethodDeclaration, + SyntaxKind.ClassDeclaration, + SyntaxKind.InterfaceDeclaration, + SyntaxKind.LocalDeclarationStatement, + SyntaxKind.Parameter, + SyntaxKind.StructDeclaration, + SyntaxKind.EnumDeclaration, + SyntaxKind.EnumMemberDeclaration); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var nodeAsField = context.Node as FieldDeclarationSyntax; - if (nodeAsField != null) + if (context.Node.IsKind(SyntaxKind.FieldDeclaration)) { + var nodeAsField = (FieldDeclarationSyntax) context.Node; if (nodeAsField.Declaration == null) { return; @@ -79,38 +76,38 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - var nodeAsProperty = context.Node as PropertyDeclarationSyntax; - if (nodeAsProperty != null) + if (context.Node.IsKind(SyntaxKind.PropertyDeclaration)) { + var nodeAsProperty = (PropertyDeclarationSyntax) context.Node; CheckNaming(nodeAsProperty.Identifier, "property", NamingConvention.UpperCamelCase, context); return; } - var nodeAsMethod = context.Node as MethodDeclarationSyntax; - if (nodeAsMethod != null) + if (context.Node.IsKind(SyntaxKind.MethodDeclaration)) { + var nodeAsMethod = (MethodDeclarationSyntax) context.Node; CheckNaming(nodeAsMethod.Identifier, "method", NamingConvention.UpperCamelCase, context); return; } - var nodeAsClass = context.Node as ClassDeclarationSyntax; - if (nodeAsClass != null) + if (context.Node.IsKind(SyntaxKind.ClassDeclaration)) { + var nodeAsClass = (ClassDeclarationSyntax) context.Node; CheckNaming(nodeAsClass.Identifier, "class", NamingConvention.UpperCamelCase, context); return; } - var nodeAsInterface = context.Node as InterfaceDeclarationSyntax; - if (nodeAsInterface != null) + if (context.Node.IsKind(SyntaxKind.InterfaceDeclaration)) { + var nodeAsInterface = (InterfaceDeclarationSyntax) context.Node; CheckNaming(nodeAsInterface.Identifier, "interface", NamingConvention.InterfacePrefixUpperCamelCase, context); return; } - var nodeAsLocal = context.Node as LocalDeclarationStatementSyntax; - if (nodeAsLocal != null) + if (context.Node.IsKind(SyntaxKind.LocalDeclarationStatement)) { + var nodeAsLocal = (LocalDeclarationStatementSyntax) context.Node; if (nodeAsLocal.Declaration == null) { return; @@ -124,45 +121,44 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - var nodeAsParameter = context.Node as ParameterSyntax; - if (nodeAsParameter != null) + if (context.Node.IsKind(SyntaxKind.Parameter)) { + var nodeAsParameter = (ParameterSyntax) context.Node; CheckNaming(nodeAsParameter.Identifier, "parameter", NamingConvention.LowerCamelCase, context); return; } - var nodeAsStruct = context.Node as StructDeclarationSyntax; - if (nodeAsStruct != null) + if (context.Node.IsKind(SyntaxKind.StructDeclaration)) { + var nodeAsStruct = (StructDeclarationSyntax) context.Node; CheckNaming(nodeAsStruct.Identifier, "struct", NamingConvention.UpperCamelCase, context); return; } - var nodeAsEnum = context.Node as EnumDeclarationSyntax; - if (nodeAsEnum != null) + if (context.Node.IsKind(SyntaxKind.EnumDeclaration)) { + var nodeAsEnum = (EnumDeclarationSyntax) context.Node; CheckNaming(nodeAsEnum.Identifier, "enum", NamingConvention.UpperCamelCase, context); return; } - var nodeAsEnumMember = context.Node as EnumMemberDeclarationSyntax; - if (nodeAsEnumMember != null) + if (context.Node.IsKind(SyntaxKind.EnumMemberDeclaration)) { + var nodeAsEnumMember = (EnumMemberDeclarationSyntax) context.Node; CheckNaming(nodeAsEnumMember.Identifier, "enum member", NamingConvention.UpperCamelCase, context); - return; } } private static void CheckNaming(SyntaxToken currentIdentifier, string memberType, NamingConvention convention, - SyntaxNodeAnalysisContext context) + SyntaxNodeAnalysisContext context) { var conventionedIdentifier = currentIdentifier.WithConvention(convention); if (conventionedIdentifier.Text != currentIdentifier.Text) { context.ReportDiagnostic( - Diagnostic.Create(Rule, currentIdentifier.GetLocation(), - ImmutableDictionary.CreateRange( new[] { new KeyValuePair("convention", convention.ToString()) }), - memberType, currentIdentifier.Text, conventionedIdentifier.Text)); + Diagnostic.Create(Rule, currentIdentifier.GetLocation(), + ImmutableDictionary.CreateRange(new[] { new KeyValuePair("convention", convention.ToString()) }), + memberType, currentIdentifier.Text, conventionedIdentifier.Text)); } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs index ed469b3..363634c 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs @@ -7,13 +7,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.NamingConventions { - [ExportCodeFixProvider(nameof(NamingConventionsCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.NamingConventions + "CF", LanguageNames.CSharp), Shared] public class NamingConventionsCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -30,7 +28,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var identifier = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.NamingConventionsCodeFixTitle, - x => RenameAsync(context.Document, identifier, root, diagnostic, context.CancellationToken), NamingConventionsAnalyzer.Rule.Id), diagnostic); + x => RenameAsync(context.Document, identifier, root, diagnostic, x), NamingConventionsAnalyzer.Rule.Id), diagnostic); } private async Task RenameAsync(Document document, SyntaxToken identifier, SyntaxNode root, Diagnostic diagnostic, CancellationToken cancellationToken) diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldAnalyzer.cs index 83a7b4a..609f7b6 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldAnalyzer.cs @@ -1,11 +1,9 @@ -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.FindSymbols; using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.NonEncapsulatedOrMutableField @@ -24,29 +22,20 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.FieldDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.FieldDeclaration); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var fieldDeclaration = context.Node as FieldDeclarationSyntax; - if (fieldDeclaration == null) - { - return; - } + var fieldDeclaration = (FieldDeclarationSyntax) context.Node; // Don't handle (semi-)immutable fields - if (fieldDeclaration.Modifiers.Any( - x => x.IsKind(SyntaxKind.ConstKeyword) || x.IsKind(SyntaxKind.ReadOnlyKeyword))) + if (fieldDeclaration.Modifiers.ContainsAny(SyntaxKind.ConstKeyword, SyntaxKind.ReadOnlyKeyword)) { return; } // Only handle public, internal and protected internal fields - if (!fieldDeclaration.Modifiers.Any( - x => x.IsKind(SyntaxKind.PublicKeyword) || x.IsKind(SyntaxKind.InternalKeyword))) + if (!fieldDeclaration.Modifiers.ContainsAny(SyntaxKind.PublicKeyword, SyntaxKind.InternalKeyword)) { return; } @@ -80,7 +69,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // In this scenario our analyzer is triggered on the field in class X and won't find any references inside that syntax tree (assuming separate files) // However it won't find any ref/out usages there so it will create the diagnostic -- which obviously isn't supposed to happen because the other syntax tree DOES contain that // However until this ability is added, we'll just have to live with it - var outerClass = context.Node.Ancestors().OfType().LastOrDefault(); + var outerClass = context.Node.Ancestors().OfType(SyntaxKind.ClassDeclaration).LastOrDefault(); if (outerClass != null) { var semanticModel = context.SemanticModel; @@ -89,15 +78,15 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var fieldSymbol = semanticModel.GetDeclaredSymbol(variable); - foreach (var descendant in outerClass.DescendantNodes().OfType()) + foreach (var descendant in outerClass.DescendantNodes().OfType(SyntaxKind.IdentifierName)) { var descendentSymbol = semanticModel.GetSymbolInfo(descendant).Symbol; if (descendentSymbol != null && descendentSymbol.Equals(fieldSymbol)) { // The field is being referenced // Next we check whether it is referenced as an argument and passed by ref/out - var argument = descendant.AncestorsAndSelf().OfType().FirstOrDefault(); - if (argument != null && !argument.RefOrOutKeyword.IsMissing) + var argument = descendant.AncestorsAndSelf().OfType(SyntaxKind.Argument).FirstOrDefault(); + if (argument != null && !argument.RefOrOutKeyword.IsKind(SyntaxKind.None)) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldCodeFix.cs index 96284ee..bcc3731 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NonEncapsulatedOrMutableField/NonEncapsulatedOrMutableFieldCodeFix.cs @@ -9,12 +9,11 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using VSDiagnostics.Diagnostics.General.NamingConventions; using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.NonEncapsulatedOrMutableField { - [ExportCodeFixProvider(nameof(NonEncapsulatedOrMutableFieldCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.NonEncapsulatedOrMutableField + "CF", LanguageNames.CSharp), Shared] public class NonEncapsulatedOrMutableFieldCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -47,24 +46,24 @@ private async Task UsePropertyAsync(Document document, SyntaxNode stat var variableDeclaration = variableDeclarator.AncestorsAndSelf().OfType().First(); var newProperty = SyntaxFactory.PropertyDeclaration(variableDeclaration.Type, variableDeclarator.Identifier) - .WithAttributeLists(fieldStatement.AttributeLists) - .WithModifiers(fieldStatement.Modifiers) - .WithAdditionalAnnotations(Formatter.Annotation) - .WithAccessorList( - SyntaxFactory.AccessorList( - SyntaxFactory.List(new[] - { - SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), - SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) - }))); + .WithAttributeLists(fieldStatement.AttributeLists) + .WithModifiers(fieldStatement.Modifiers) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithAccessorList( + SyntaxFactory.AccessorList( + SyntaxFactory.List(new[] + { + SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), + SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + }))); if (variableDeclarator.Initializer != null) { newProperty = newProperty.WithInitializer(variableDeclarator.Initializer) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } var editor = await DocumentEditor.CreateAsync(document); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandAnalyzer.cs index 4784158..35bace3 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandAnalyzer.cs @@ -22,15 +22,12 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.GenericName); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.GenericName); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var argumentList = (GenericNameSyntax) context.Node; - if (argumentList.TypeArgumentList.Arguments.OfType().Any()) + if (argumentList.TypeArgumentList.Arguments.OfType(SyntaxKind.OmittedTypeArgument).Any()) { return; } @@ -67,31 +64,40 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // First we look through the different nodes that can't be nested // If nothing is found, we check if it's perhaps a standalone expression (such as an object creation without assigning it to an identifier) - var parentNode = - context.Node.AncestorsAndSelf().FirstOrDefault(x => variableAncestorNodes.Contains(x.Kind())) ?? - context.Node.AncestorsAndSelf().OfType().FirstOrDefault(); + SyntaxNode parentNode = null; + foreach (var node in context.Node.AncestorsAndSelf()) + { + if (variableAncestorNodes.Contains(node.Kind())) + { + parentNode = node; + } + } if (parentNode == null) { - return; + parentNode = context.Node.AncestorsAndSelf().OfType(SyntaxKind.ExpressionStatement).FirstOrDefault(); + + if (parentNode == null) + { + return; + } } if (parentNode.Kind() == SyntaxKind.LocalDeclarationStatement) { identifier = ((LocalDeclarationStatementSyntax) parentNode).Declaration? - .Variables - .FirstOrDefault()? - .Identifier - .Text; + .Variables + .FirstOrDefault()? + .Identifier + .Text; } else if (parentNode.Kind() == SyntaxKind.FieldDeclaration) { - identifier = - ((FieldDeclarationSyntax) parentNode).Declaration? - .Variables - .FirstOrDefault()? - .Identifier - .Text; + identifier = ((FieldDeclarationSyntax) parentNode).Declaration? + .Variables + .FirstOrDefault()? + .Identifier + .Text; } else if (parentNode.Kind() == SyntaxKind.Parameter) { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandCodeFix.cs index 0dcfce4..79dde7f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NullableToShorthand/NullableToShorthandCodeFix.cs @@ -8,10 +8,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.NullableToShorthand { - [ExportCodeFixProvider(nameof(NullableToShorthandCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.NullableToShorthand + "CF", LanguageNames.CSharp), Shared] public class NullableToShorthandCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -32,8 +33,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) NullableToShorthandAnalyzer.Rule.Id), diagnostic); } - private static async Task UseShorthandNotationAsync(Document document, SyntaxNode root, - SyntaxToken declaration) + private static async Task UseShorthandNotationAsync(Document document, SyntaxNode root, SyntaxToken declaration) { var node = root.FindNode(declaration.Span); var typeNode = (GenericNameSyntax) node; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorAnalyzer.cs index bc1cc09..21f4c97 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorAnalyzer.cs @@ -15,32 +15,21 @@ namespace VSDiagnostics.Diagnostics.General.OnPropertyChangedWithoutNameOfOperat public class OnPropertyChangedWithoutNameOfOperatorAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorAnalyzerTitle; - private static readonly string Message = - VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorAnalyzerMessage; - - private static readonly string Title = - VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorAnalyzerTitle; - - internal static DiagnosticDescriptor Rule - => - new DiagnosticDescriptor(DiagnosticId.OnPropertyChangedWithoutNameofOperator, Title, Message, Category, - Severity, true); + internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.OnPropertyChangedWithoutNameofOperator, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.InvocationExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.InvocationExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var invocation = context.Node as InvocationExpressionSyntax; + var invocation = (InvocationExpressionSyntax) context.Node; - var identifierExpression = invocation?.Expression as IdentifierNameSyntax; + var identifierExpression = invocation.Expression as IdentifierNameSyntax; if (identifierExpression == null) { return; @@ -64,9 +53,12 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) } // We use the descendent nodes in case it's wrapped in another level. For example: OnPropertyChanged(((nameof(MyProperty)))) - if (invokedProperty.DescendantNodesAndSelf().OfType().Any(x => x.IsNameofInvocation())) + foreach (var expression in invokedProperty.DescendantNodesAndSelf().OfType(SyntaxKind.InvocationExpression)) { - return; + if (expression.IsNameofInvocation()) + { + return; + } } var invocationArgument = context.SemanticModel.GetConstantValue(invokedProperty.Expression); @@ -77,7 +69,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // Get all the properties defined in this type // We can't just get all the descendents of the classdeclaration because that would pass by some of a partial class' properties - var classDeclaration = invocation.Ancestors().OfType().FirstOrDefault(); + var classDeclaration = invocation.Ancestors().OfType(SyntaxKind.ClassDeclaration).FirstOrDefault(); if (classDeclaration == null) { return; @@ -93,11 +85,13 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { if (string.Equals(property.Name, (string) invocationArgument.Value, StringComparison.OrdinalIgnoreCase)) { - var location = invokedProperty.Expression.DescendantNodesAndSelf().Last().GetLocation(); + // The original Linq was `Last()`. I used `LastOrDefault()` just because I didn't feel the need to implement a + // version to throw an `InvalidOperationException()` rather than a `NullReferenceException()` in this case. + var location = invokedProperty.Expression.DescendantNodesAndSelf().LastOrDefault().GetLocation(); var data = ImmutableDictionary.CreateRange(new[] { new KeyValuePair("parameterName", property.Name), - new KeyValuePair("startLocation", location.SourceSpan.Start.ToString(CultureInfo.InvariantCulture)), + new KeyValuePair("startLocation", location.SourceSpan.Start.ToString(CultureInfo.InvariantCulture)) }); context.ReportDiagnostic(Diagnostic.Create(Rule, location, data, property.Name)); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorCodeFix.cs index e330543..ee31b89 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/OnPropertyChangedWithoutNameOfOperator/OnPropertyChangedWithoutNameOfOperatorCodeFix.cs @@ -6,10 +6,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.OnPropertyChangedWithoutNameOfOperator { - [ExportCodeFixProvider(nameof(OnPropertyChangedWithoutNameOfOperatorCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.OnPropertyChangedWithoutNameofOperator + "CF", LanguageNames.CSharp), Shared] public class OnPropertyChangedWithoutNameOfOperatorCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -23,7 +24,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var diagnostic = context.Diagnostics.First(); context.RegisterCodeFix( - CodeAction.Create(VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorCodeFixTitle, + CodeAction.Create(VSDiagnosticsResources.OnPropertyChangedWithoutNameOfOperatorCodeFixTitle, x => UseNameOfAsync(context.Document, root, diagnostic), OnPropertyChangedWithoutNameOfOperatorAnalyzer.Rule.Id), diagnostic); } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterAnalyzer.cs new file mode 100644 index 0000000..5a89c9d --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterAnalyzer.cs @@ -0,0 +1,97 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.RedundantPrivateSetter +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class RedundantPrivateSetterAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.RedundantPrivateSetterAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.RedundantPrivateSetterAnalyzerTitle; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.RedundantPrivateSetter, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.SetAccessorDeclaration); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var setAccessor = (AccessorDeclarationSyntax) context.Node; + + // Since there are no modifiers that can work with 'private', we can just assume that there should be one modifier: private + var hasOneKeyword = setAccessor.Modifiers.Count == 1; + if (!hasOneKeyword) + { + return; + } + + var hasPrivateKeyword = setAccessor.Modifiers[0].IsKind(SyntaxKind.PrivateKeyword); + if (!hasPrivateKeyword) + { + return; + } + + var property = context.Node.Ancestors().OfType(SyntaxKind.PropertyDeclaration).FirstOrDefault(); + if (property == null) + { + return; + } + + // We have to check whether or not the value is being written to + // Since SymbolFinder does not work in an analyzer, we have to simulate finding the symbol ourselves + // We can do this by getting the inner-most class declaration and then looking at all of its descendants + // This is a fairly intensive operation but I'm not aware of any alternative + var enclosingTypeNode = setAccessor.GetEnclosingTypeNode(); + if (!enclosingTypeNode.IsKind(SyntaxKind.StructDeclaration) && !enclosingTypeNode.IsKind(SyntaxKind.ClassDeclaration)) + { + return; + } + + var classSymbol = context.SemanticModel.GetDeclaredSymbol(enclosingTypeNode); + var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property); + + foreach (var partialDeclaration in classSymbol.DeclaringSyntaxReferences) + { + foreach (var descendant in partialDeclaration.GetSyntax().DescendantNodes()) + { + if (descendant.IsKind(SyntaxKind.SimpleAssignmentExpression)) + { + var assignment = (AssignmentExpressionSyntax) descendant; + ISymbol assignedSymbol; + if (descendant.SyntaxTree.Equals(property.SyntaxTree)) + { + assignedSymbol = context.SemanticModel.GetSymbolInfo(assignment.Left).Symbol; + } + else + { + var newModel = context.SemanticModel.Compilation.GetSemanticModel(partialDeclaration.SyntaxTree); + assignedSymbol = newModel.GetSymbolInfo(assignment.Left).Symbol; + } + + if (assignedSymbol != null && assignedSymbol.Equals(propertySymbol)) + { + var hasConstructorAncestor = assignment.Ancestors().Any(SyntaxKind.ConstructorDeclaration); + if (!hasConstructorAncestor) + { + return; + } + } + } + } + } + + context.ReportDiagnostic(Diagnostic.Create(Rule, setAccessor.GetLocation(), property.Identifier.ValueText)); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterCodeFix.cs new file mode 100644 index 0000000..1967a44 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/RedundantPrivateSetter/RedundantPrivateSetterCodeFix.cs @@ -0,0 +1,39 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using VSDiagnostics.Utilities; + + +namespace VSDiagnostics.Diagnostics.General.RedundantPrivateSetter +{ + [ExportCodeFixProvider(DiagnosticId.RedundantPrivateSetter + "CF", LanguageNames.CSharp), Shared] + public class RedundantPrivateSetterCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantPrivateSetterAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var declaration = root.FindNode(diagnosticSpan); + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.NullableToShorthandCodeFixTitle, + x => UseReadOnlyPropertyAsync(context.Document, root, declaration), + RedundantPrivateSetterAnalyzer.Rule.Id), diagnostic); + } + + private Task UseReadOnlyPropertyAsync(Document document, SyntaxNode root, SyntaxNode setAccessor) + { + root = root.RemoveNode(setAccessor, SyntaxRemoveOptions.KeepNoTrivia); + return Task.FromResult(document.WithSyntaxRoot(root)); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberAnalyzer.cs index c35308a..d9f0889 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberAnalyzer.cs @@ -35,102 +35,120 @@ internal static DiagnosticDescriptor Rule public override void Initialize(AnalysisContext context) { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.PropertyDeclaration, SyntaxKind.MethodDeclaration); + context.RegisterSyntaxNodeAction(HandleProperty, SyntaxKind.PropertyDeclaration); + context.RegisterSyntaxNodeAction(HandleMethod, SyntaxKind.MethodDeclaration); } - private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + private void HandleProperty(SyntaxNodeAnalysisContext context) { - Diagnostic diagnostic = null; + var propertyDeclaration = (PropertyDeclarationSyntax) context.Node; - var asProperty = context.Node as PropertyDeclarationSyntax; - if (asProperty != null) - { - diagnostic = HandleProperty(asProperty); - } - - var asMethod = context.Node as MethodDeclarationSyntax; - if (asMethod != null) + if (propertyDeclaration.ExpressionBody != null) { - diagnostic = HandleMethod(asMethod); + return; } - if (diagnostic != null) + foreach (var declaration in propertyDeclaration.DescendantNodesAndTokensAndSelf()) { - context.ReportDiagnostic(diagnostic); + foreach (var trivia in declaration.GetLeadingTrivia()) + { + if (!trivia.IsWhitespaceTrivia()) + { + return; + } + } + + foreach (var trivia in declaration.GetTrailingTrivia()) + { + if (!trivia.IsWhitespaceTrivia()) + { + return; + } + } } - } - private Diagnostic HandleProperty(PropertyDeclarationSyntax propertyDeclaration) - { - if (propertyDeclaration.ExpressionBody != null) + foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { - return null; + if (accessor.Keyword.IsKind(SyntaxKind.SetKeyword)) + { + return; + } } - if ( - propertyDeclaration.DescendantNodesAndTokensAndSelf() - .Any(x => x.GetLeadingTrivia().Concat(x.GetTrailingTrivia()).Any(y => !y.IsWhitespaceTrivia()))) + AccessorDeclarationSyntax getter = null; + foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { - return null; + if (accessor.Keyword.IsKind(SyntaxKind.GetKeyword)) + { + getter = accessor; + break; + } } - - if (propertyDeclaration.AccessorList.Accessors.Any(x => x.Keyword.IsKind(SyntaxKind.SetKeyword))) - { - return null; - } - - var getter = - propertyDeclaration.AccessorList.Accessors.FirstOrDefault(x => x.Keyword.IsKind(SyntaxKind.GetKeyword)); if (getter == null) { - return null; + return; } - if (getter.AttributeLists.Any(x => x.Attributes.Any())) + foreach (var list in getter.AttributeLists) { - return null; + if (list.Attributes.Any()) + { + return; + } } if (getter.Body?.Statements.Count != 1) { - return null; + return; } if (!Nodes.Contains(getter.Body.Statements[0].Kind())) { - return null; + return; } var statement = getter.Body.Statements.First(); - return Diagnostic.Create(Rule, statement.GetLocation(), "Property", propertyDeclaration.Identifier); + context.ReportDiagnostic(Diagnostic.Create(Rule, statement.GetLocation(), "Property", propertyDeclaration.Identifier)); } - private Diagnostic HandleMethod(MethodDeclarationSyntax methodDeclaration) + private void HandleMethod(SyntaxNodeAnalysisContext context) { + var methodDeclaration = (MethodDeclarationSyntax) context.Node; + if (methodDeclaration.ExpressionBody != null) { - return null; + return; } - if ( - methodDeclaration.DescendantNodesAndTokensAndSelf() - .Any(x => x.GetLeadingTrivia().Concat(x.GetTrailingTrivia()).Any(y => !y.IsWhitespaceTrivia()))) + foreach (var nodeOrToken in methodDeclaration.DescendantNodesAndTokensAndSelf()) { - return null; + if (nodeOrToken.GetLeadingTrivia().Concat(nodeOrToken.GetTrailingTrivia()).Any(y => !y.IsWhitespaceTrivia())) + { + return; + } } if (methodDeclaration.Body?.Statements.Count != 1) { - return null; + return; + } + + var statement = methodDeclaration.Body.Statements[0]; + if (!Nodes.Contains(statement.Kind())) + { + return; } - if (!Nodes.Contains(methodDeclaration.Body.Statements[0].Kind())) + if (statement.IsKind(SyntaxKind.ReturnStatement)) { - return null; + var returnStatement = (ReturnStatementSyntax) statement; + if (returnStatement.Expression == null) + { + return; + } } - return Diagnostic.Create(Rule, methodDeclaration.Identifier.GetLocation(), "Method", - methodDeclaration.Identifier.ValueText); + context.ReportDiagnostic(Diagnostic.Create(Rule, methodDeclaration.Identifier.GetLocation(), "Method", methodDeclaration.Identifier.ValueText)); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberCodeFix.cs index 9201a5b..1c38b0b 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SimplifyExpressionBodiedMember/SimplifyExpressionBodiedMemberCodeFix.cs @@ -7,10 +7,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.SimplifyExpressionBodiedMember { - [ExportCodeFixProvider(nameof(SimplifyExpressionBodiedMemberCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.SimplifyExpressionBodiedMember + "CF", LanguageNames.CSharp), Shared] public class SimplifyExpressionBodiedMemberCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -32,20 +33,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) diagnostic); } - private Task UseExpressionBodiedMemberAsync(Document document, SyntaxNode root, SyntaxNode statement) + private Task UseExpressionBodiedMemberAsync(Document document, SyntaxNode root, SyntaxNode statement) { var property = statement.AncestorsAndSelf().OfType().FirstOrDefault(); if (property != null) { - var firstStatement = - property.AccessorList.Accessors.FirstOrDefault(x => x.Keyword.IsKind(SyntaxKind.GetKeyword)) - .Body.Statements.First(); - var arrowClause = - SyntaxFactory.ArrowExpressionClause(((ReturnStatementSyntax) firstStatement).Expression); + var firstStatement = property.AccessorList.Accessors.Single(x => x.Keyword.IsKind(SyntaxKind.GetKeyword)).Body.Statements.First(); + var arrowClause = SyntaxFactory.ArrowExpressionClause(((ReturnStatementSyntax) firstStatement).Expression); var newProperty = property.RemoveNode(property.AccessorList, SyntaxRemoveOptions.KeepNoTrivia) - .WithExpressionBody(arrowClause) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - + .WithExpressionBody(arrowClause) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); root = root.ReplaceNode(property, newProperty); } @@ -54,16 +51,16 @@ private Task UseExpressionBodiedMemberAsync(Document document, SyntaxN if (method != null) { var firstStatement = method.Body.Statements.First(); - var expression = firstStatement is ExpressionStatementSyntax + var expression = firstStatement.IsKind(SyntaxKind.ExpressionStatement) ? ((ExpressionStatementSyntax) firstStatement).Expression : ((ReturnStatementSyntax) firstStatement).Expression; var arrowClause = SyntaxFactory.ArrowExpressionClause(expression); root = root.ReplaceNode(method, method.RemoveNode(method.Body, SyntaxRemoveOptions.KeepNoTrivia) - .WithExpressionBody(arrowClause) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))); + .WithExpressionBody(arrowClause) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } - return Task.FromResult(document.WithSyntaxRoot(root).Project.Solution); + return Task.FromResult(document.WithSyntaxRoot(root)); } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorAnalyzer.cs index 9ffee74..a2f1a31 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,18 +21,11 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConstructorDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ConstructorDeclaration); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var constructorDeclaration = context.Node as ConstructorDeclarationSyntax; - if (constructorDeclaration == null) - { - return; - } + var constructorDeclaration = (ConstructorDeclarationSyntax) context.Node; // ctor must be public if (!constructorDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword)) @@ -54,9 +46,12 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) } // ctor must not contain comments - if (constructorDeclaration.Body.CloseBraceToken.LeadingTrivia.Any(t => t.IsCommentTrivia())) + foreach (var trivia in constructorDeclaration.Body.CloseBraceToken.LeadingTrivia) { - return; + if (trivia.IsCommentTrivia()) + { + return; + } } // ctor must not have attributes @@ -66,34 +61,34 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) } var classSymbol = context.SemanticModel.GetDeclaredSymbol(constructorDeclaration.Parent) as INamedTypeSymbol; - if (classSymbol != null && classSymbol.Constructors.Count() != 1) + if (classSymbol != null && classSymbol.Constructors.Length != 1) { return; } - if (constructorDeclaration.GetLeadingTrivia().Any(t => t.IsCommentTrivia())) + foreach (var trivia in constructorDeclaration.GetLeadingTrivia()) { - return; + if (trivia.IsCommentTrivia()) + { + return; + } } - - var childNodes = constructorDeclaration.ChildNodes().ToList(); - - if (childNodes.Any() && childNodes.Any(node => + + foreach (var node in constructorDeclaration.ChildNodes()) { - if (node is ConstructorInitializerSyntax) + var nodeAsConstructorInitializerSyntax = node as ConstructorInitializerSyntax; + if (nodeAsConstructorInitializerSyntax != null) { - var constructorInitializer = (ConstructorInitializerSyntax) node; + var constructorInitializer = nodeAsConstructorInitializerSyntax; // we must return false (to avoid the parent if) only if it is the base keyword // and there are no arguments. - return !constructorInitializer.ThisOrBaseKeyword.IsKind(SyntaxKind.BaseKeyword) || - constructorInitializer.ArgumentList.Arguments.Any(); + if (!constructorInitializer.ThisOrBaseKeyword.IsKind(SyntaxKind.BaseKeyword) || + constructorInitializer.ArgumentList.Arguments.Any()) + { + return; + } } - - return false; - })) - { - return; } context.ReportDiagnostic(Diagnostic.Create(Rule, constructorDeclaration.GetLocation(), diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorCodeFix.cs index 6ce6472..a7e53d2 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SingleEmptyConstructor/SingleEmptyConstructorCodeFix.cs @@ -6,10 +6,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.SingleEmptyConstructor { - [ExportCodeFixProvider(nameof(SingleEmptyConstructorCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.SingleEmptyConstructor + "CF", LanguageNames.CSharp), Shared] internal class SingleEmptyConstructorCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsAnalyzer.cs new file mode 100644 index 0000000..79743b3 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsAnalyzer.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.SwitchDoesNotHandleAllEnumOptions +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class SwitchDoesNotHandleAllEnumOptionsAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.GeneralCategory; + private static readonly string Message = VSDiagnosticsResources.SwitchDoesNotHandleAllEnumOptionsAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.SwitchDoesNotHandleAllEnumOptionsAnalyzerTitle; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.SwitchDoesNotHandleAllEnumOptions, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.SwitchStatement); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var switchBlock = (SwitchStatementSyntax)context.Node; + + var enumType = context.SemanticModel.GetTypeInfo(switchBlock.Expression).Type as INamedTypeSymbol; + if (enumType == null || enumType.TypeKind != TypeKind.Enum) + { + return; + } + + var labelSymbols = new HashSet(); + foreach (var section in switchBlock.Sections) + { + foreach (var label in section.Labels) + { + if (label.IsKind(SyntaxKind.CaseSwitchLabel)) + { + var switchLabel = (CaseSwitchLabelSyntax) label; + var symbol = context.SemanticModel.GetSymbolInfo(switchLabel.Value).Symbol; + if (symbol == null) + { + // potentially malformed case statement + // or an integer being cast to an enum type + return; + } + + labelSymbols.Add(symbol); + } + } + } + + foreach (var member in enumType.GetMembers()) + { + // skip `.ctor` + if (member.IsImplicitlyDeclared) + { + continue; + } + + if (!labelSymbols.Contains(member)) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, switchBlock.Expression.GetLocation())); + return; + } + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsCodeFix.cs new file mode 100644 index 0000000..e8f3af5 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsCodeFix.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.SwitchDoesNotHandleAllEnumOptions +{ + [ExportCodeFixProvider(DiagnosticId.SwitchDoesNotHandleAllEnumOptions + "CF", LanguageNames.CSharp), Shared] + internal class SwitchDoesNotHandleAllEnumOptionsCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var statement = root.FindNode(diagnosticSpan); + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.SwitchDoesNotHandleAllEnumOptionsCodeFixTitle, + x => AddMissingCaseAsync(context.Document, (CompilationUnitSyntax) root, statement), + SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.Id), diagnostic); + } + + private async Task AddMissingCaseAsync(Document document, CompilationUnitSyntax root, SyntaxNode statement) + { + var semanticModel = await document.GetSemanticModelAsync(); + + var switchBlock = (SwitchStatementSyntax) statement.Parent; + + var enumType = (INamedTypeSymbol) semanticModel.GetTypeInfo(switchBlock.Expression).Type; + var caseLabels = switchBlock.Sections.SelectMany(l => l.Labels) + .OfType() + .Select(l => l.Value) + .ToList(); + + var missingLabels = GetMissingLabels(caseLabels, enumType); + + // use simplified form if there are any in simplified form or if there are not any labels at all + var hasSimplifiedLabel = caseLabels.OfType().Any(); + var useSimplifiedForm = (hasSimplifiedLabel || !caseLabels.OfType().Any()) && caseLabels.Any(); + + var qualifier = GetQualifierForException(root); + + var notImplementedException = + SyntaxFactory.ThrowStatement(SyntaxFactory.ParseExpression($" new {qualifier}NotImplementedException()")) + .WithAdditionalAnnotations(Simplifier.Annotation); + var statements = SyntaxFactory.List(new List { notImplementedException }); + + var newSections = SyntaxFactory.List(switchBlock.Sections); + + foreach (var label in missingLabels) + { + // If an existing simplified label exists, it means we can assume that works already and do it ourselves as well (ergo: there is a static using) + var expression = caseLabels.Any() + ? SyntaxFactory.ParseExpression(hasSimplifiedLabel ? $"{label}" : $"{enumType.Name}.{label}") + : SyntaxFactory.ParseExpression($"{enumType.ToDisplayString()}.{label}").WithAdditionalAnnotations(Simplifier.Annotation); + + var caseLabel = SyntaxFactory.CaseSwitchLabel(expression); + + var section = + SyntaxFactory.SwitchSection(SyntaxFactory.List(new List { caseLabel }), statements) + .WithAdditionalAnnotations(Formatter.Annotation); + + // ensure that the new cases are above the default case + newSections = newSections.Insert(0, section); + } + + var newNode = useSimplifiedForm + ? switchBlock.WithSections(newSections).WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation) + : switchBlock.WithSections(newSections).WithAdditionalAnnotations(Formatter.Annotation); + + var newRoot = root.ReplaceNode(switchBlock, newNode); + var newDocument = await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot)); + return newDocument.Project.Solution; + } + + private IEnumerable GetMissingLabels(List caseLabels, INamedTypeSymbol enumType) + { + // these are the labels like `MyEnum.EnumMember` + var labels = caseLabels + .OfType() + .Select(l => l.Name.Identifier.ValueText) + .ToList(); + + // these are the labels like `EnumMember` (such as when using `using static Namespace.MyEnum;`) + labels.AddRange(caseLabels.OfType().Select(l => l.Identifier.ValueText)); + + // don't create members like ".ctor" + return enumType.GetMembers().Where(member => !labels.Contains(member.Name) && !member.IsImplicitlyDeclared).Select(member => member.Name); + } + + private string GetQualifierForException(CompilationUnitSyntax root) + { + var qualifier = "System."; + var usingSystemDirective = + root.Usings.Where(u => u.Name is IdentifierNameSyntax) + .FirstOrDefault(u => ((IdentifierNameSyntax) u.Name).Identifier.ValueText == nameof(System)); + + if (usingSystemDirective != null) + { + qualifier = usingSystemDirective.Alias == null + ? string.Empty + : usingSystemDirective.Alias.Name.Identifier.ValueText + "."; + } + return qualifier; + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelAnalyzer.cs similarity index 50% rename from VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueAnalyzer.cs rename to VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelAnalyzer.cs index 24043a2..f480639 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/ConditionIsAlwaysTrue/ConditionIsAlwaysTrueAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelAnalyzer.cs @@ -5,34 +5,43 @@ using Microsoft.CodeAnalysis.Diagnostics; using VSDiagnostics.Utilities; -namespace VSDiagnostics.Diagnostics.General.ConditionIsAlwaysTrue +namespace VSDiagnostics.Diagnostics.General.SwitchIsMissingDefaultLabel { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class ConditionIsAlwaysTrueAnalyzer : DiagnosticAnalyzer + internal class SwitchIsMissingDefaultLabelAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private static readonly string Category = VSDiagnosticsResources.GeneralCategory; - private static readonly string Message = VSDiagnosticsResources.ConditionIsAlwaysTrueAnalyzerMessage; - private static readonly string Title = VSDiagnosticsResources.ConditionIsAlwaysTrueAnalyzerTitle; + private static readonly string Message = VSDiagnosticsResources.SwitchIsMissingDefaultSectionAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.SwitchIsMissingDefaultSectionAnalyzerTitle; internal static DiagnosticDescriptor Rule - => new DiagnosticDescriptor(DiagnosticId.ConditionIsAlwaysTrue, Title, Message, Category, Severity, true); + => new DiagnosticDescriptor(DiagnosticId.SwitchIsMissingDefaultLabel, Title, Message, Category, Severity, true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IfStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.SwitchStatement); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var ifStatement = (IfStatementSyntax) context.Node; + var switchBlock = (SwitchStatementSyntax)context.Node; + + var hasDefaultLabel = false; + foreach (var section in switchBlock.Sections) + { + foreach (var label in section.Labels) + { + if (label.IsKind(SyntaxKind.DefaultSwitchLabel)) + { + hasDefaultLabel = true; + } + } + } - if (ifStatement.Condition.IsKind(SyntaxKind.TrueLiteralExpression)) + if (!hasDefaultLabel) { - context.ReportDiagnostic(Diagnostic.Create(Rule, ifStatement.Condition.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(Rule, switchBlock.Expression.GetLocation())); } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelCodeFix.cs new file mode 100644 index 0000000..cc8c85b --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchIsMissingDefaultLabel/SwitchIsMissingDefaultLabelCodeFix.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.SwitchIsMissingDefaultLabel +{ + [ExportCodeFixProvider(DiagnosticId.SwitchIsMissingDefaultLabel + "CF", LanguageNames.CSharp), Shared] + internal class SwitchIsMissingDefaultLabelCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(SwitchIsMissingDefaultLabelAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var statement = root.FindNode(diagnosticSpan).Parent; + context.RegisterCodeFix( + CodeAction.Create(VSDiagnosticsResources.SwitchIsMissingDefaultSectionCodeFixTitle, + x => AddDefaultCaseAsync(context.Document, (CompilationUnitSyntax)root, (SwitchStatementSyntax)statement), + SwitchIsMissingDefaultLabelAnalyzer.Rule.Id), diagnostic); + } + + private async Task AddDefaultCaseAsync(Document document, CompilationUnitSyntax root, SwitchStatementSyntax switchBlock) + { + var model = await document.GetSemanticModelAsync(); + + var symbol = model.GetSymbolInfo(switchBlock.Expression).Symbol; + string expression; + if (symbol != null) + { + expression = $"nameof({symbol.Name})"; + } + else if (switchBlock.Expression.IsKind(SyntaxKind.StringLiteralExpression)) + { + expression = $"{switchBlock.Expression}"; + } + else + { + expression = $"{switchBlock.Expression}.ToString()"; + } + + var argumentException = + SyntaxFactory.ThrowStatement(SyntaxFactory.ParseExpression($"new System.ArgumentException({expression})")) + .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation); + var statements = SyntaxFactory.List(new List { argumentException }); + + var defaultCase = SyntaxFactory.SwitchSection(SyntaxFactory.List(new[] { SyntaxFactory.DefaultSwitchLabel() }), statements); + + var newNode = switchBlock.AddSections(defaultCase.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)); + + var newRoot = root.ReplaceNode(switchBlock, newNode); + return await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot)); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullAnalyzer.cs index 233b09e..d40841f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullAnalyzer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -12,7 +13,7 @@ namespace VSDiagnostics.Diagnostics.General.TryCastWithoutUsingAsNotNull [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TryCastWithoutUsingAsNotNullAnalyzer : DiagnosticAnalyzer { - private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + private const DiagnosticSeverity Severity = DiagnosticSeverity.Hidden; private static readonly string Category = VSDiagnosticsResources.GeneralCategory; private static readonly string Message = VSDiagnosticsResources.TryCastWithoutUsingAsNotNullAnalyzerMessage; @@ -23,20 +24,18 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IsExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IsExpression); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var isExpression = context.Node as BinaryExpressionSyntax; + var isExpression = (BinaryExpressionSyntax) context.Node; - var isIdentifierExpression = isExpression?.Left as IdentifierNameSyntax; + var isIdentifierExpression = isExpression.Left as IdentifierNameSyntax; if (isIdentifierExpression == null) { return; } + var isIdentifier = isIdentifierExpression.Identifier.ValueText; var isType = context.SemanticModel.GetTypeInfo(isExpression.Right).Type; if (isType == null) @@ -44,22 +43,33 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - var ifStatement = isExpression.AncestorsAndSelf().OfType().FirstOrDefault(); + var ifStatement = isExpression.AncestorsAndSelf().OfType(SyntaxKind.IfStatement).FirstOrDefault(); if (ifStatement == null) { return; } - var asExpressions = ifStatement.Statement - .DescendantNodes() - .Concat(ifStatement.Condition.DescendantNodesAndSelf()) - .OfType() - .Where(x => x.OperatorToken.IsKind(SyntaxKind.AsKeyword)); + var isExpressionBelongsToIfCondition = ifStatement.Condition.DescendantNodesAndSelf().Contains(isExpression); + if (!isExpressionBelongsToIfCondition) + { + return; + } + + var asExpressions = new List(); + foreach (var statement in ifStatement.Statement + .DescendantNodes() + .Concat(ifStatement.Condition.DescendantNodesAndSelf()) + .OfType()) + { + if (statement.OperatorToken.IsKind(SyntaxKind.AsKeyword)) + { + asExpressions.Add(statement); + } + } - var castExpressions = ifStatement.Statement - .DescendantNodes() - .Concat(ifStatement.Condition.DescendantNodesAndSelf()) - .OfType(); + var castExpressions = new List(); + castExpressions.AddRange(ifStatement.Statement.DescendantNodes().OfType(SyntaxKind.CastExpression)); + castExpressions.AddRange(ifStatement.Condition.DescendantNodesAndSelf().OfType(SyntaxKind.CastExpression)); Action reportDiagnostic = () => context.ReportDiagnostic(Diagnostic.Create(Rule, isExpression.GetLocation(), isIdentifier)); @@ -95,7 +105,11 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) } }; - foreach (var expression in asExpressions.Concat(castExpressions)) + var expressions = new List(asExpressions.Count + castExpressions.Count); + expressions.AddRange(asExpressions); + expressions.AddRange(castExpressions); + + foreach (var expression in expressions) { var binaryExpression = expression as BinaryExpressionSyntax; var binaryIdentifier = binaryExpression?.Left as IdentifierNameSyntax; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullCodeFix.cs index 3186639..218b85f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TryCastWithoutUsingAsNotNull/TryCastWithoutUsingAsNotNullCodeFix.cs @@ -16,7 +16,7 @@ namespace VSDiagnostics.Diagnostics.General.TryCastWithoutUsingAsNotNull { - [ExportCodeFixProvider(nameof(TryCastWithoutUsingAsNotNullCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.TryCastWithoutUsingAsNotNull + "CF", LanguageNames.CSharp), Shared] public class TryCastWithoutUsingAsNotNullCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -33,11 +33,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var statement = root.FindNode(diagnosticSpan); context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.TryCastWithoutUsingAsNotNullCodeFixTitle, - x => UseAsAsync(context.Document, statement, context.CancellationToken), TryCastWithoutUsingAsNotNullAnalyzer.Rule.Id), + x => UseAsAsync(context.Document, statement, x), TryCastWithoutUsingAsNotNullAnalyzer.Rule.Id), diagnostic); } - private async Task UseAsAsync(Document document, SyntaxNode statement, CancellationToken cancellationToken) + private async Task UseAsAsync(Document document, SyntaxNode statement, + CancellationToken cancellationToken) { var documentId = document.Id; var projectId = document.Project.Id; @@ -64,7 +65,9 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, var isExpression = (BinaryExpressionSyntax) root.GetAnnotatedNodes("MyIsStatement").Single(); var isIdentifier = ((IdentifierNameSyntax) isExpression.Left).Identifier.ValueText; var ifStatement = isExpression.Ancestors().OfType().First(); - var newIdentifier = SyntaxFactory.Identifier(GetNewIdentifier(isIdentifier, (TypeSyntax) isExpression.Right, semanticModel, GetOuterIfStatement(ifStatement).Parent)); + var type = GetRelevantType(isExpression, semanticModel); + var newIdentifier = + SyntaxFactory.Identifier(GetNewIdentifier(isIdentifier, type, GetOuterIfStatement(ifStatement).Parent)); // We filter out the descendent if statements to avoid executing the code fix on all nested ifs @@ -97,7 +100,8 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, // See https://github.com/dotnet/roslyn/issues/8154 for more info if (identifier.Ancestors().OfType().Any()) { - editor.ReplaceNode(identifier, identifier.WithAdditionalAnnotations(new SyntaxAnnotation("QueueRename"))); + editor.ReplaceNode(identifier, + identifier.WithAdditionalAnnotations(new SyntaxAnnotation("QueueRename"))); } } } @@ -116,7 +120,7 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, { // We only rename the VariableDeclarators that are directly assigned the casted variable in question // If the variable is used in a separate expression, we don't have to rename our declarator - if (!IsSurroundedByInvocation(node)) + if (!IsSurroundedByInvocation(node) && !IsInConditionalExpression(node)) { nodeToRename = declarator; break; @@ -135,15 +139,20 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, break; } - var renamedSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, nodeToRenameSymbol, newIdentifier.ValueText, null, cancellationToken); + var renamedSolution = + await + Renamer.RenameSymbolAsync(document.Project.Solution, nodeToRenameSymbol, newIdentifier.ValueText, + null, cancellationToken); document = renamedSolution.Projects.Single(x => x.Id == projectId).GetDocument(documentId); } // Third step: use the newly created document + // We need to recalculate all those things because otherwise there would be discrepancies due to different source documents editor = await DocumentEditor.CreateAsync(document, cancellationToken); semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var newRoot = await document.GetSyntaxRootAsync(cancellationToken); - isExpression = (BinaryExpressionSyntax) newRoot.GetAnnotatedNodes("MyIsStatement").Single(); + root = await document.GetSyntaxRootAsync(cancellationToken); + isExpression = (BinaryExpressionSyntax) root.GetAnnotatedNodes("MyIsStatement").Single(); + type = GetRelevantType(isExpression, semanticModel); ifStatement = isExpression.Ancestors().OfType().First(); asExpressions = GetDescendantBinaryAs(ifStatement); castExpressions = GetDescendantCasts(ifStatement); @@ -151,6 +160,7 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, var conditionAlreadyReplaced = false; var variableAlreadyExtracted = false; + foreach (var asExpression in asExpressions) { var identifier = asExpression.Left as IdentifierNameSyntax; @@ -159,24 +169,49 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, continue; } + var castedType = semanticModel.GetTypeInfo(asExpression.Right).Type; + if (castedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + var nullableSyntax = (NullableTypeSyntax) asExpression.Right; + var nullableArgument = semanticModel.GetTypeInfo(nullableSyntax.ElementType).Type; + if (nullableArgument != type) + { + continue; + } + } + else + { + if (castedType != type) + { + continue; + } + } + // Replace condition if it hasn't happened yet - ReplaceCondition(newIdentifier.ValueText, isExpression, editor, ref conditionAlreadyReplaced); + if (!conditionAlreadyReplaced) + { + ReplaceCondition(newIdentifier.ValueText, isExpression, editor); + conditionAlreadyReplaced = true; + } // Create as statement before if block - InsertNewVariableDeclaration( + if (!variableAlreadyExtracted) + { + InsertNewVariableDeclaration( asExpression: asExpression, newIdentifier: newIdentifier, nodeLocation: ifStatement, - editor: editor, - variableAlreadyExtracted: ref variableAlreadyExtracted); + editor: editor); + + variableAlreadyExtracted = true; + } - // If the expression does not have a variable declarator as parent, we just swap the entire expression for the newly generated identifier ReplaceIdentifier(asExpression, newIdentifier, editor); // Remove the local variable // If the expression is surrounded by an invocation we just swap the expression for the identifier // e.g.: bool contains = new[] { ""test"", ""test"", ""test"" }.Contains(o as string); - if (!IsSurroundedByInvocation(asExpression)) + if (!IsSurroundedByInvocation(asExpression) && !IsInConditionalExpression(asExpression)) { RemoveLocal(asExpression, editor); } @@ -190,32 +225,58 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, continue; } - var castedType = semanticModel.GetTypeInfo(castExpression.Type); + var castedType = semanticModel.GetTypeInfo(castExpression.Type).Type; + if (castedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + var nullableSyntax = (NullableTypeSyntax) castExpression.Type; + var nullableArgument = semanticModel.GetTypeInfo(nullableSyntax.ElementType).Type; + if (nullableArgument != type) + { + continue; + } + } + else + { + if (castedType != type) + { + continue; + } + } // Replace condition if it hasn't happened yet - ReplaceCondition(newIdentifier.ValueText, isExpression, editor, ref conditionAlreadyReplaced); + if (!conditionAlreadyReplaced) + { + ReplaceCondition(newIdentifier.ValueText, isExpression, editor); + conditionAlreadyReplaced = true; + } // Create as statement before if block - var typeToCast = castedType.Type.IsNullable() || castedType.Type.IsReferenceType ? castExpression.Type : SyntaxFactory.NullableType(castExpression.Type); - var newAsClause = SyntaxFactory.BinaryExpression(SyntaxKind.AsExpression, castExpression.Expression, typeToCast); - InsertNewVariableDeclaration( - asExpression: newAsClause, - newIdentifier: newIdentifier, - nodeLocation: ifStatement, - editor: editor, - variableAlreadyExtracted: ref variableAlreadyExtracted); + if (!variableAlreadyExtracted) + { + var typeToCast = castedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T || castedType.IsReferenceType + ? castExpression.Type + : SyntaxFactory.NullableType(castExpression.Type); + var newAsClause = SyntaxFactory.BinaryExpression(SyntaxKind.AsExpression, castExpression.Expression, + typeToCast); + InsertNewVariableDeclaration( + asExpression: newAsClause, + newIdentifier: newIdentifier, + nodeLocation: ifStatement, + editor: editor); + + variableAlreadyExtracted = true; + } - // If the expression does not have a variable declarator as parent, we just swap the entire expression for the newly generated identifier // If we have a direct cast (yes) and the existing type was a non-nullable value type, we have to add the `.Value` property accessor ourselves // While it is not necessary to add the property access in the case of a nullable collection, we do it anyway because that's a very difficult thing to calculate otherwise // e.g. new double?[] { 5.0, 6.0, 7.0 }.Contains(oAsDouble.Value) // The above can be written with or without `.Value` when the collection is double?[] but requires `.Value` in the case of double[] - ReplaceIdentifier(castExpression, newIdentifier, editor, requiresNullableValueAccess: castedType.Type.IsValueType && !castedType.Type.IsNullable()); + ReplaceIdentifier(castExpression, newIdentifier, editor, requiresNullableValueAccess: castedType.IsValueType && castedType.OriginalDefinition.SpecialType != SpecialType.System_Nullable_T); // Remove the local variable // If the expression is surrounded by an invocation we just swap the expression for the identifier // e.g.: bool contains = new[] { ""test"", ""test"", ""test"" }.Contains(o as string); - if (!IsSurroundedByInvocation(castExpression)) + if (!IsSurroundedByInvocation(castExpression) && !IsInConditionalExpression(castExpression)) { RemoveLocal(castExpression, editor); } @@ -224,51 +285,31 @@ private async Task UseAsAsync(Document document, SyntaxNode statement, return editor.GetChangedDocument(); } - private bool IsSurroundedByInvocation(ExpressionSyntax expression) - { - return expression.Ancestors().OfType().Any(); - } + private static bool IsSurroundedByInvocation(ExpressionSyntax expression) => expression.Ancestors().OfType().Any(); - private IEnumerable GetDescendantCasts(IfStatementSyntax ifStatement) - { - return ifStatement.Statement.DescendantNodes() - .Concat(ifStatement.Condition.DescendantNodesAndSelf()) - .Where(x => !(x is IfStatementSyntax)) - .OfType() - .ToArray(); - } + private static IEnumerable GetDescendantCasts(IfStatementSyntax ifStatement) => ifStatement.Statement.DescendantNodes() + .Concat(ifStatement.Condition.DescendantNodesAndSelf()) + .Where(x => !(x is IfStatementSyntax)) + .OfType() + .ToArray(); - private IEnumerable GetDescendantBinaryAs(IfStatementSyntax ifStatement) - { - return ifStatement.Statement - .DescendantNodes() - .Concat(ifStatement.Condition.DescendantNodesAndSelf()) - .Where(x => !(x is IfStatementSyntax)) - .OfType() - .Where(x => x.OperatorToken.IsKind(SyntaxKind.AsKeyword)) - .ToArray(); - } + private static IEnumerable GetDescendantBinaryAs(IfStatementSyntax ifStatement) => ifStatement.Statement + .DescendantNodes() + .Concat(ifStatement.Condition.DescendantNodesAndSelf()) + .Where(x => !(x is IfStatementSyntax)) + .OfType() + .Where(x => x.OperatorToken.IsKind(SyntaxKind.AsKeyword)) + .ToArray(); - private void ReplaceCondition(string newIdentifier, SyntaxNode isExpression, DocumentEditor editor, ref bool conditionAlreadyReplaced) + private static void ReplaceCondition(string newIdentifier, SyntaxNode isExpression, DocumentEditor editor) { - if (conditionAlreadyReplaced) - { - return; - } - var newCondition = SyntaxFactory.ParseExpression($"{newIdentifier} != null").WithAdditionalAnnotations(Formatter.Annotation); editor.ReplaceNode(isExpression, newCondition); - conditionAlreadyReplaced = true; } - private string GetNewIdentifier(string currentIdentifier, TypeSyntax type, SemanticModel semanticModel, SyntaxNode context) + private static string GetNewIdentifier(string currentIdentifier, ITypeSymbol type, SyntaxNode context) { - var nullableType = type as NullableTypeSyntax; - var typeName = nullableType != null - ? semanticModel.GetTypeInfo(nullableType.ElementType).Type.Name - : semanticModel.GetTypeInfo(type).Type.Name; - - var newName = $"{currentIdentifier}As{typeName}"; + var newName = $"{currentIdentifier}As{type.Name}"; // We add a suffix counter in case there are naming collisions. var collidingIdentifier = context.DescendantNodesAndSelf() @@ -278,21 +319,21 @@ private string GetNewIdentifier(string currentIdentifier, TypeSyntax type, Seman .OrderByDescending(x => x) .FirstOrDefault(); - if (collidingIdentifier != null) + if (collidingIdentifier == null) { - var indexOfUnderscore = collidingIdentifier.LastIndexOf('_'); - int index; - if (indexOfUnderscore > 0 && int.TryParse(collidingIdentifier.Substring(indexOfUnderscore + 1), out index)) - { - return $"{newName}_{++index}"; - } - return $"{newName}_1"; + return newName; } - return newName; + var indexOfUnderscore = collidingIdentifier.LastIndexOf('_'); + int index; + if (indexOfUnderscore > 0 && int.TryParse(collidingIdentifier.Substring(indexOfUnderscore + 1), out index)) + { + return $"{newName}_{++index}"; + } + return $"{newName}_1"; } - private void RemoveLocal(ExpressionSyntax expression, DocumentEditor editor) + private static void RemoveLocal(ExpressionSyntax expression, DocumentEditor editor) { var variableDeclaration = expression.Ancestors().OfType().FirstOrDefault(); if (variableDeclaration == null) @@ -327,42 +368,21 @@ private void RemoveLocal(ExpressionSyntax expression, DocumentEditor editor) /// from int to int?. /// Note that this should only happen in the case of a value type. /// - private void ReplaceIdentifier(ExpressionSyntax expression, SyntaxToken newIdentifier, DocumentEditor editor, bool requiresNullableValueAccess = false) + private static void ReplaceIdentifier(ExpressionSyntax expression, SyntaxToken newIdentifier, DocumentEditor editor, bool requiresNullableValueAccess = false) { - var newIdentifierName = SyntaxFactory.IdentifierName(newIdentifier); - - if (expression.Ancestors().OfType().Any()) - { - if (requiresNullableValueAccess) - { - var newAccess = SyntaxFactory.ParseExpression($"{newIdentifier.ValueText}.Value"); - editor.ReplaceNode(expression, newAccess); - } - else - { - editor.ReplaceNode(expression, newIdentifierName); - } - return; - } + var newIdentifierName = requiresNullableValueAccess + ? SyntaxFactory.ParseExpression($"{newIdentifier.ValueText}.Value") + : SyntaxFactory.IdentifierName(newIdentifier); - if (!expression.Ancestors().OfType().Any()) - { - editor.ReplaceNode(expression, newIdentifierName); - } + editor.ReplaceNode(expression, newIdentifierName.WithAdditionalAnnotations(Formatter.Annotation)); } - private void InsertNewVariableDeclaration( + private static void InsertNewVariableDeclaration( BinaryExpressionSyntax asExpression, SyntaxToken newIdentifier, SyntaxNode nodeLocation, - DocumentEditor editor, - ref bool variableAlreadyExtracted) + DocumentEditor editor) { - if (variableAlreadyExtracted) - { - return; - } - var newEqualsClause = SyntaxFactory.EqualsValueClause(asExpression); var newDeclarator = SyntaxFactory.VariableDeclarator(newIdentifier.WithAdditionalAnnotations(RenameAnnotation.Create()), null, newEqualsClause); var newDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName("var"), SyntaxFactory.SeparatedList(new[] { newDeclarator })); @@ -376,10 +396,9 @@ private void InsertNewVariableDeclaration( nodeLocation = GetOuterIfStatement(nodeLocation); editor.InsertBefore(nodeLocation, newLocal); - variableAlreadyExtracted = true; } - private SyntaxNode GetOuterIfStatement(SyntaxNode nodeLocation) + private static SyntaxNode GetOuterIfStatement(SyntaxNode nodeLocation) { while (true) { @@ -403,5 +422,19 @@ private SyntaxNode GetOuterIfStatement(SyntaxNode nodeLocation) } } } + + private static bool IsInConditionalExpression(SyntaxNode expression) => expression.AncestorsAndSelf().Any(x => x.IsKind(SyntaxKind.ConditionalExpression)); + + /// + /// Determines the relevant type that we're comparing with (e.g. the raw type or the nested type in case of + /// + /// + private static ITypeSymbol GetRelevantType(BinaryExpressionSyntax isExpression, SemanticModel semanticModel) + { + var nullableType = isExpression.Right as NullableTypeSyntax; + return nullableType != null + ? semanticModel.GetTypeInfo(nullableType.ElementType).Type + : semanticModel.GetTypeInfo(isExpression.Right).Type; + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarAnalyzer.cs index f486b42..ed62d94 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,16 +21,13 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.LocalDeclarationStatement); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.LocalDeclarationStatement); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var localDeclaration = context.Node as LocalDeclarationStatementSyntax; + var localDeclaration = (LocalDeclarationStatementSyntax) context.Node; - if (localDeclaration?.Declaration == null) + if (localDeclaration.Declaration == null) { return; } @@ -42,7 +38,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) return; } - if (localDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.ConstKeyword))) + if (localDeclaration.Modifiers.Contains(SyntaxKind.ConstKeyword)) { return; } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarCodeFix.cs index a12ba22..d5c0f5e 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/TypeToVar/TypeToVarCodeFix.cs @@ -6,10 +6,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.TypeToVar { - [ExportCodeFixProvider(nameof(TypeToVarCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.TypeToVar + "CF", LanguageNames.CSharp), Shared] public class TypeToVarCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(TypeToVarAnalyzer.Rule.Id); @@ -31,8 +32,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private Task UseVarAsync(Document document, SyntaxNode root, SyntaxNode statement) { var varIdentifier = SyntaxFactory.IdentifierName("var") - .WithLeadingTrivia(statement.GetLeadingTrivia()) - .WithTrailingTrivia(statement.GetTrailingTrivia()); + .WithLeadingTrivia(statement.GetLeadingTrivia()) + .WithTrailingTrivia(statement.GetTrailingTrivia()); var newRoot = root.ReplaceNode(statement, varIdentifier); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeAnalyzer.cs index be418b9..2e2ae9e 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeAnalyzer.cs @@ -12,7 +12,6 @@ namespace VSDiagnostics.Diagnostics.General.UseAliasesInsteadOfConcreteType public class UseAliasesInsteadOfConcreteTypeAnalyzer : DiagnosticAnalyzer { private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - private static readonly string Category = VSDiagnosticsResources.GeneralCategory; private static readonly string Message = VSDiagnosticsResources.UseAliasesInsteadOfConcreteTypeAnalyzerMessage; private static readonly string Title = VSDiagnosticsResources.UseAliasesInsteadOfConcreteTypeAnalyzerTitle; @@ -21,22 +20,15 @@ public class UseAliasesInsteadOfConcreteTypeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IdentifierName); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.IdentifierName); private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { - var identifier = context.Node as IdentifierNameSyntax; - if (identifier == null) - { - return; - } + var identifier = (IdentifierNameSyntax) context.Node; // A nameof() expression cannot contain aliases // There is no way to distinguish between a self-defined method 'nameof' and the nameof operator so we have to ignore all invocations that call into 'nameof' - var surroundingInvocation = identifier.Ancestors().OfType().FirstOrDefault(); + var surroundingInvocation = identifier.Ancestors().OfType(SyntaxKind.InvocationExpression).FirstOrDefault(); if (surroundingInvocation != null && surroundingInvocation.IsNameofInvocation()) { return; @@ -44,10 +36,6 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // If we're dealing with a self-defined type 'Char' then we ignore it var identifierSymbol = context.SemanticModel.GetSymbolInfo(identifier); - if (identifierSymbol.Symbol == null) - { - return; - } var typeSymbol = identifierSymbol.Symbol as INamedTypeSymbol; if (typeSymbol == null || typeSymbol.SpecialType == SpecialType.None) @@ -59,7 +47,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) // We don't need it in this step but we have to point the analyzer to the right location // This will make sure that we accept the entire qualified name in the code fix var location = identifier.GetLocation(); - var qualifiedName = identifier.AncestorsAndSelf().OfType().FirstOrDefault(); + var qualifiedName = identifier.AncestorsAndSelf().OfType(SyntaxKind.QualifiedName).FirstOrDefault(); if (qualifiedName?.Parent is UsingDirectiveSyntax) { return; @@ -70,10 +58,14 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) location = qualifiedName.GetLocation(); } - string alias; - if (identifier.Identifier.Text.HasAlias() && typeSymbol.MetadataName.HasAlias(out alias)) + // This ensures that we are not using aliases. Both the identifier and the actual type must have a registered alias. + // If the identifier alias and the alias for the actual type do not match (as when `using Single = System.String`), + // the aliases do not match, so we do not create a diagnostic because the Simplifier does not create an alias in this case. + string identifierAlias; + string metadataAlias; + if (identifier.Identifier.Text.HasAlias(out identifierAlias) && typeSymbol.MetadataName.HasAlias(out metadataAlias) && identifierAlias == metadataAlias) { - context.ReportDiagnostic(Diagnostic.Create(Rule, location, alias, typeSymbol.MetadataName)); + context.ReportDiagnostic(Diagnostic.Create(Rule, location, metadataAlias, typeSymbol.MetadataName)); } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeCodeFix.cs index fc7fa30..2b878c3 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/UseAliasesInsteadOfConcreteType/UseAliasesInsteadOfConcreteTypeCodeFix.cs @@ -1,19 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.General.UseAliasesInsteadOfConcreteType { - [ExportCodeFixProvider(nameof(UseAliasesInsteadOfConcreteTypeCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.UseAliasesInsteadOfConcreteType + "CF", LanguageNames.CSharp), Shared] public class UseAliasesInsteadOfConcreteTypeCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -36,35 +33,10 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private async Task UseAliasAsync(Document document, SyntaxNode root, SyntaxNode statement) { - var semanticModel = await document.GetSemanticModelAsync(); - var typeName = semanticModel.GetSymbolInfo(statement).Symbol.MetadataName; - var aliasToken = MapConcreteTypeToPredefinedTypeAlias[typeName]; - - var newExpression = SyntaxFactory.PredefinedType(SyntaxFactory.Token(aliasToken)).WithTriviaFrom(statement); - var newRoot = root.ReplaceNode(statement, newExpression); - var newDocument = document.WithSyntaxRoot(newRoot); + var newRoot = root.ReplaceNode(statement, statement.WithAdditionalAnnotations(Simplifier.Annotation)); + var newDocument = await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot)); return newDocument.Project.Solution; } - - private static readonly Dictionary MapConcreteTypeToPredefinedTypeAlias = - new Dictionary - { - {nameof(Int16), SyntaxKind.ShortKeyword}, - {nameof(Int32), SyntaxKind.IntKeyword}, - {nameof(Int64), SyntaxKind.LongKeyword}, - {nameof(UInt16), SyntaxKind.UShortKeyword}, - {nameof(UInt32), SyntaxKind.UIntKeyword}, - {nameof(UInt64), SyntaxKind.ULongKeyword}, - {nameof(Object), SyntaxKind.ObjectKeyword}, - {nameof(Byte), SyntaxKind.ByteKeyword}, - {nameof(SByte), SyntaxKind.SByteKeyword}, - {nameof(Char), SyntaxKind.CharKeyword}, - {nameof(Boolean), SyntaxKind.BoolKeyword}, - {nameof(Single), SyntaxKind.FloatKeyword}, - {nameof(Double), SyntaxKind.DoubleKeyword}, - {nameof(Decimal), SyntaxKind.DecimalKeyword}, - {nameof(String), SyntaxKind.StringKeyword} - }; } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyAnalyzer.cs index 4b6b2d5..c59ded8 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyAnalyzer.cs @@ -25,40 +25,39 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.StringLiteralExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.StringLiteralExpression); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - if (context.Node.AncestorsAndSelf().OfType().Any()) + if (context.Node.AncestorsAndSelf().OfType(SyntaxKind.AttributeArgument).Any()) { return; } - var stringLiteral = context.Node as LiteralExpressionSyntax; - if (stringLiteral == null) - { - return; - } + var stringLiteral = (LiteralExpressionSyntax) context.Node; if (stringLiteral.Token.Text != "\"\"") { return; } - if (stringLiteral.Ancestors().Any(x => x.IsKind(SyntaxKind.Parameter))) + foreach (var node in stringLiteral.Ancestors()) { - return; + if (node.IsKind(SyntaxKind.Parameter)) + { + return; + } } - var variableDeclaration = stringLiteral.Ancestors().OfType().FirstOrDefault(); + var variableDeclaration = stringLiteral.Ancestors().OfType(SyntaxKind.FieldDeclaration).FirstOrDefault(); if (variableDeclaration != null) { - if (variableDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.ConstKeyword))) + foreach (var modifier in variableDeclaration.Modifiers) { - return; + if (modifier.IsKind(SyntaxKind.ConstKeyword)) + { + return; + } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyCodeFix.cs index 7ab89cc..b12b9fe 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/ReplaceEmptyStringWithStringDotEmpty/ReplaceEmptyStringWithStringDotEmptyCodeFix.cs @@ -8,10 +8,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Strings.ReplaceEmptyStringWithStringDotEmpty { - [ExportCodeFixProvider(nameof(ReplaceEmptyStringWithStringDotEmptyCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.ReplaceEmptyStringWithStringDotEmpty + "CF", LanguageNames.CSharp), Shared] public class ReplaceEmptyStringWithStringDotEmptyCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -35,7 +36,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } private static Task UseStringDotEmptyAsync(Document document, SyntaxNode root, - LiteralExpressionSyntax literalDeclaration) + LiteralExpressionSyntax literalDeclaration) { var stringDotEmptyInvocation = SyntaxFactory.ParseExpression("string.Empty").WithTriviaFrom(literalDeclaration); var newRoot = diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringDotFormatWithDifferentAmountOfArguments/StringDotFormatWithDifferentAmountOfArgumentsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringDotFormatWithDifferentAmountOfArguments/StringDotFormatWithDifferentAmountOfArgumentsAnalyzer.cs index e2a6529..a85e025 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringDotFormatWithDifferentAmountOfArguments/StringDotFormatWithDifferentAmountOfArgumentsAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringDotFormatWithDifferentAmountOfArguments/StringDotFormatWithDifferentAmountOfArgumentsAnalyzer.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; @@ -22,35 +23,48 @@ public class StringDotFormatWithDifferentAmountOfArgumentsAnalyzer : DiagnosticA public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var invocation = context.Node as InvocationExpressionSyntax; - if (invocation?.ArgumentList == null) + var invocation = (InvocationExpressionSyntax) context.Node; + if (invocation.ArgumentList == null) { return; } // Get the format string // This corresponds to the argument passed to the parameter with name 'format' - var invokedMethod = context.SemanticModel.GetSymbolInfo(invocation); - var methodSymbol = invokedMethod.Symbol as IMethodSymbol; + var methodSymbol = context.SemanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; + if (methodSymbol == null) + { + return; + } // Verify we're dealing with a call to a method that accepts a variable named 'format' and a object, params object[] or a plain object[] // params object[] and object[] can both be verified by looking for the latter // This allows us to support similar calls like Console.WriteLine("{0}", "test") as well which carry an implicit string.Format - var formatParam = methodSymbol?.Parameters.FirstOrDefault(x => x.Name == "format"); + IParameterSymbol formatParam = null; + foreach (var parameter in methodSymbol.Parameters) + { + if (parameter.Name == "format") + { + formatParam = parameter; + break; + } + } + if (formatParam == null) { return; } var formatIndex = formatParam.Ordinal; - var formatParameters = methodSymbol.Parameters.Skip(formatIndex + 1).ToArray(); + var formatParameters = new List(); + for (var i = formatIndex + 1; i < methodSymbol.Parameters.Length; i++) + { + formatParameters.Add(methodSymbol.Parameters[i]); + } // If the method definition doesn't contain any parameter to pass format arguments, we ignore it if (!formatParameters.Any()) @@ -58,10 +72,26 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) return; } - var hasObjectArray = formatParameters.Length == 1 && - formatParameters.All(x => x.Type.Kind == SymbolKind.ArrayType && - ((IArrayTypeSymbol) x.Type).ElementType.SpecialType == SpecialType.System_Object); - var hasObject = formatParameters.All(x => x.Type.SpecialType == SpecialType.System_Object); + var symbolsAreNotArraysOrObjects = true; + foreach (var symbol in formatParameters) + { + if (symbol.Type.Kind != SymbolKind.ArrayType || ((IArrayTypeSymbol) symbol.Type).ElementType.SpecialType != SpecialType.System_Object) + { + symbolsAreNotArraysOrObjects = false; + break; + } + } + var hasObjectArray = formatParameters.Count == 1 && symbolsAreNotArraysOrObjects; + + var hasObject = true; + foreach (var symbol in formatParameters) + { + if (symbol.Type.SpecialType != SpecialType.System_Object) + { + hasObject = false; + break; + } + } if (!(hasObject || hasObjectArray)) { @@ -89,8 +119,13 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) // If the first one is the literal (aka: the format specified) then every other argument is an argument to the format // If not, it means the first one is the CultureInfo, the second is the format and all others are format arguments // We also have to check whether or not the arguments are passed in through an explicit array or whether they use the params syntax - var formatArguments = invocation.ArgumentList.Arguments.Skip(formatIndex + 1).ToArray(); - var amountOfFormatArguments = formatArguments.Length; + var formatArguments = new List(); + for (var i = formatIndex + 1; i < invocation.ArgumentList.Arguments.Count; i++) + { + formatArguments.Add(invocation.ArgumentList.Arguments[i]); + } + + var amountOfFormatArguments = formatArguments.Count; if (amountOfFormatArguments == 1) { @@ -105,14 +140,22 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) { // We check for an invocation first to account for the scenario where you have both an invocation and an array initializer // Think about something like this: string.Format(""{0}{1}{2}"", new[] { 1 }.Concat(new[] {2}).ToArray()); - var methodInvocation = formatArguments[0].DescendantNodes().OfType().FirstOrDefault(); + var methodInvocation = formatArguments[0].DescendantNodes().OfType(SyntaxKind.InvocationExpression).FirstOrDefault(); if (methodInvocation != null) { // We don't handle method calls that return an array in the case of a single argument return; } - var inlineArrayCreation = formatArguments[0].DescendantNodes().OfType().FirstOrDefault(); + InitializerExpressionSyntax inlineArrayCreation = null; + foreach (var argument in formatArguments[0].DescendantNodes()) + { + if (argument is InitializerExpressionSyntax) + { + inlineArrayCreation = (InitializerExpressionSyntax)argument; + } + } + if (inlineArrayCreation != null) { amountOfFormatArguments = inlineArrayCreation.Expressions.Count; @@ -134,12 +177,12 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) placeholderVerification: // Get the placeholders we use stripped off their format specifier, get the highest value // and verify that this value + 1 (to account for 0-based indexing) is not greater than the amount of placeholder arguments - var placeholders = PlaceholderHelpers.GetPlaceholders((string) formatString.Value) - .Cast() - .Select(x => x.Value) - .Select(PlaceholderHelpers.GetPlaceholderIndex) - .Select(int.Parse) - .ToList(); + var placeholders = new List(); + + foreach (Match placeholder in PlaceholderHelpers.GetPlaceholders((string) formatString.Value)) + { + placeholders.Add(int.Parse(PlaceholderHelpers.GetPlaceholderIndex(placeholder.Value))); + } if (!placeholders.Any()) { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderAnalyzer.cs index ae57eb2..da0ccf4 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderAnalyzer.cs @@ -23,21 +23,14 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var invocation = context.Node as InvocationExpressionSyntax; - if (invocation == null) - { - return; - } + var invocation = (InvocationExpressionSyntax) context.Node; // Verify we're dealing with a string.Format() call - if (!invocation.IsAnInvocationOf(typeof (string), nameof(string.Format), context.SemanticModel)) + if (!invocation.IsAnInvocationOf(typeof(string), nameof(string.Format), context.SemanticModel)) { return; } @@ -54,7 +47,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) var firstArgumentSymbol = context.SemanticModel.GetSymbolInfo(firstArgument.Expression); if (!(firstArgument.Expression is LiteralExpressionSyntax) && - (firstArgumentSymbol.Symbol?.MetadataName == typeof (CultureInfo).Name && + (firstArgumentSymbol.Symbol?.MetadataName == typeof(CultureInfo).Name && !(secondArgument?.Expression is LiteralExpressionSyntax))) { return; diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderCodeFix.cs index b6e0a97..5488683 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Strings/StringPlaceholdersInWrongOrder/StringPlaceholdersInWrongOrderCodeFix.cs @@ -10,10 +10,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Strings.StringPlaceholdersInWrongOrder { - [ExportCodeFixProvider(nameof(StringPlaceHoldersInWrongOrderCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.StringPlaceholdersInWrongOrder + "CF", LanguageNames.CSharp), Shared] public class StringPlaceHoldersInWrongOrderCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -40,14 +41,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } private static Task ReOrderPlaceholdersAsync(Document document, SyntaxNode root, - InvocationExpressionSyntax stringFormatInvocation) + InvocationExpressionSyntax stringFormatInvocation) { var firstArgumentIsLiteral = stringFormatInvocation.ArgumentList.Arguments[0].Expression is LiteralExpressionSyntax; var formatString = ((LiteralExpressionSyntax) stringFormatInvocation.ArgumentList.Arguments[firstArgumentIsLiteral ? 0 : 1].Expression).GetText() - .ToString(); + .ToString(); var elements = PlaceholderHelpers.GetPlaceholdersSplit(formatString); var matches = PlaceholderHelpers.GetPlaceholders(formatString); @@ -107,8 +108,8 @@ private static Task ReOrderPlaceholdersAsync(Document document, Syntax // Create a new list for the arguments which are injected in the formatting string // In order to do this we iterate over the mapping which is in essence a guideline that tells us which index IEnumerable args = firstArgumentIsLiteral - ? new[] {newArgument} - : new[] {stringFormatInvocation.ArgumentList.Arguments[0], newArgument}; + ? new[] { newArgument } + : new[] { stringFormatInvocation.ArgumentList.Arguments[0], newArgument }; // Skip the formatting literal and, if applicable, the formatprovider var argumentsToSkip = firstArgumentIsLiteral ? 1 : 2; @@ -116,7 +117,7 @@ private static Task ReOrderPlaceholdersAsync(Document document, Syntax { args = args.Concat(new[] - {stringFormatInvocation.ArgumentList.Arguments[placeholderIndexOrder[index] + argumentsToSkip]}); + { stringFormatInvocation.ArgumentList.Arguments[placeholderIndexOrder[index] + argumentsToSkip] }); } // If there are less arguments in the new list compared to the old one, it means there was an unused argument @@ -129,7 +130,7 @@ private static Task ReOrderPlaceholdersAsync(Document document, Syntax { if (!args.Contains(arg)) { - args = args.Concat(new[] {arg}); + args = args.Concat(new[] { arg }); } } } diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructShouldNotMutateSelf/StructShouldNotMutateSelfAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructShouldNotMutateSelf/StructShouldNotMutateSelfAnalyzer.cs index 1f28264..9c3464f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructShouldNotMutateSelf/StructShouldNotMutateSelfAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructShouldNotMutateSelf/StructShouldNotMutateSelfAnalyzer.cs @@ -21,20 +21,13 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleAssignmentExpression); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleAssignmentExpression); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { // Looking for // this = someValueType; - var assignmentExpression = context.Node as AssignmentExpressionSyntax; - if (assignmentExpression == null) - { - return; - } + var assignmentExpression = (AssignmentExpressionSyntax) context.Node; if (!(assignmentExpression.Left is ThisExpressionSyntax)) { diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenAnalyzer.cs new file mode 100644 index 0000000..5ee33f7 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenAnalyzer.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Structs.StructWithoutElementaryMethodsOverridden +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class StructWithoutElementaryMethodsOverriddenAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.StructsCategory; + private static readonly string Message = VSDiagnosticsResources.StructWithoutElementaryMethodsOverriddenAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.StructWithoutElementaryMethodsOverriddenAnalyzerTitle; + + internal static DiagnosticDescriptor Rule + => new DiagnosticDescriptor(DiagnosticId.StructWithoutElementaryMethodsOverridden, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.StructDeclaration); + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var objectSymbol = context.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Object); + IMethodSymbol objectEquals = null; + IMethodSymbol objectGetHashCode = null; + IMethodSymbol objectToString = null; + + foreach (var symbol in objectSymbol.GetMembers()) + { + if (!(symbol is IMethodSymbol)) + { + continue; + } + + var method = (IMethodSymbol)symbol; + if (method.MetadataName == nameof(Equals) && method.Parameters.Count() == 1) + { + objectEquals = method; + } + + if (method.MetadataName == nameof(GetHashCode) && !method.Parameters.Any()) + { + objectGetHashCode = method; + } + + if (method.MetadataName == nameof(ToString) && !method.Parameters.Any()) + { + objectToString = method; + } + } + + var structDeclaration = (StructDeclarationSyntax)context.Node; + + var equalsImplemented = false; + var getHashCodeImplemented = false; + var toStringImplemented = false; + + foreach (var node in structDeclaration.Members) + { + if (!node.IsKind(SyntaxKind.MethodDeclaration)) + { + continue; + } + + var methodDeclaration = (MethodDeclarationSyntax)node; + if (!methodDeclaration.Modifiers.Contains(SyntaxKind.OverrideKeyword)) + { + continue; + } + + var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration).OverriddenMethod; + + // this will happen if the base class is deleted and there is still a derived class + if (methodSymbol == null) + { + return; + } + + while (methodSymbol.IsOverride) + { + methodSymbol = methodSymbol.OverriddenMethod; + } + + if (methodSymbol == objectEquals) + { + equalsImplemented = true; + } + + if (methodSymbol == objectGetHashCode) + { + getHashCodeImplemented = true; + } + + if (methodSymbol == objectToString) + { + toStringImplemented = true; + } + } + + if (!equalsImplemented || !getHashCodeImplemented || !toStringImplemented) + { + var isEqualsImplemented = new KeyValuePair("IsEqualsImplemented", equalsImplemented.ToString()); + var isGetHashcodeImplemented = new KeyValuePair("IsGetHashCodeImplemented", getHashCodeImplemented.ToString()); + var isGetToStringImplemented = new KeyValuePair("IsToStringImplemented", toStringImplemented.ToString()); + + var properties = ImmutableDictionary.CreateRange(new[] + {isEqualsImplemented, isGetHashcodeImplemented, isGetToStringImplemented}); + + context.ReportDiagnostic(Diagnostic.Create(Rule, structDeclaration.Identifier.GetLocation(), properties, structDeclaration.Identifier)); + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenCodeFix.cs new file mode 100644 index 0000000..3f41515 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Structs/StructWithoutElementaryMethodsOverridden/StructWithoutElementaryMethodsOverriddenCodeFix.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Structs.StructWithoutElementaryMethodsOverridden +{ + [ExportCodeFixProvider(DiagnosticId.StructWithoutElementaryMethodsOverridden + "CF", LanguageNames.CSharp), Shared] + public class StructWithoutElementaryMethodsOverriddenCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + string implementEqualsString; + string implementGetHashCodeString; + string implementToStringString; + + diagnostic.Properties.TryGetValue("IsEqualsImplemented", out implementEqualsString); + diagnostic.Properties.TryGetValue("IsGetHashCodeImplemented", out implementGetHashCodeString); + diagnostic.Properties.TryGetValue("IsToStringImplemented", out implementToStringString); + + var implementEquals = bool.Parse(implementEqualsString); + var implementGetHashCode = bool.Parse(implementGetHashCodeString); + var implementToString = bool.Parse(implementToStringString); + + var dict = new Dictionary + { + {"Equals()", implementEquals}, + {"GetHashCode()", implementGetHashCode}, + {"ToString()", implementToString} + }; + + var statement = root.FindNode(diagnosticSpan); + + context.RegisterCodeFix(CodeAction.Create( + string.Format(VSDiagnosticsResources.StructWithoutElementaryMethodsOverriddenCodeFixTitle, FormatMissingMembers(dict)), + x => AddMissingMethodsAsync(context.Document, root, (StructDeclarationSyntax) statement, + implementEquals, implementGetHashCode, implementToString), + StructWithoutElementaryMethodsOverriddenAnalyzer.Rule.Id), diagnostic); + } + + private static readonly MethodDeclarationSyntax EqualsMethod = GetEqualsMethod(); + private static readonly MethodDeclarationSyntax GetHashCodeMethod = GetGetHashCodeMethod(); + private static readonly MethodDeclarationSyntax ToStringMethod = GetToStringMethod(); + + private Task AddMissingMethodsAsync(Document document, SyntaxNode root, + StructDeclarationSyntax statement, bool implementEquals, bool implementGetHashCode, + bool implementToString) + { + var newStatement = statement; + + if (!implementEquals) + { + newStatement = newStatement.AddMembers(EqualsMethod); + } + + if (!implementGetHashCode) + { + newStatement = newStatement.AddMembers(GetHashCodeMethod); + } + + if (!implementToString) + { + newStatement = newStatement.AddMembers(ToStringMethod); + } + + var newRoot = root.ReplaceNode(statement, newStatement); + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + } + + private static MethodDeclarationSyntax GetEqualsMethod() + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + var bodyStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();") + .WithAdditionalAnnotations(Simplifier.Annotation); + var parameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("obj")) + .WithType(SyntaxFactory.ParseTypeName("object")); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("bool"), "Equals") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(bodyStatement) + .AddParameterListParameters(parameter) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private static MethodDeclarationSyntax GetGetHashCodeMethod() + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + var bodyStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();") + .WithAdditionalAnnotations(Simplifier.Annotation); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("int"), "GetHashCode") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(bodyStatement) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private static MethodDeclarationSyntax GetToStringMethod() + { + var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword); + var overrideModifier = SyntaxFactory.Token(SyntaxKind.OverrideKeyword); + var bodyStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();") + .WithAdditionalAnnotations(Simplifier.Annotation); + + return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("string"), "ToString") + .AddModifiers(publicModifier, overrideModifier) + .AddBodyStatements(bodyStatement) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private string FormatMissingMembers(Dictionary members) + { + // if we get this far, there are at least 1 missing members + var missingMemberCount = 0; + foreach (var member in members) + { + if (!member.Value) + { + missingMemberCount++; + } + } + + var value = string.Empty; + for (var i = 0; i < members.Count; i++) + { + if (members.ElementAt(i).Value) + { + continue; + } + + if (missingMemberCount == 2 && !string.IsNullOrEmpty(value)) + { + value += " and "; + } + + value += members.ElementAt(i).Key; + + if (missingMemberCount == 3 && i == 0) + { + value += ", "; + } + else if (missingMemberCount == 3 && i == 1) + { + value += ", and "; + } + } + + return value; + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixAnalyzer.cs index 52ca746..17ae4f8 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixAnalyzer.cs @@ -1,5 +1,5 @@ -using System.Collections.Immutable; -using System.Linq; +using System; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,43 +22,23 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); - } - + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); + private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var method = context.Node as MethodDeclarationSyntax; - if (method == null) - { - return; - } - - if (!method.Identifier.Text.EndsWith("Test", System.StringComparison.CurrentCultureIgnoreCase)) + var method = (MethodDeclarationSyntax) context.Node; + + if (!method.Identifier.Text.EndsWith("Test", StringComparison.CurrentCultureIgnoreCase)) { return; } - if (!IsTestMethod(method)) + if (!method.HasTestAttribute()) { return; } context.ReportDiagnostic(Diagnostic.Create(Rule, method.Identifier.GetLocation(), method.Identifier.Text)); } - - private static bool IsTestMethod(MethodDeclarationSyntax method) - { - var methodAttributes = new[] {"Test", "TestMethod", "Fact"}; - var attributes = method.AttributeLists.FirstOrDefault()?.Attributes; - - if (attributes == null) - { - return false; - } - - return attributes.Value.Any(x => methodAttributes.Contains(x.Name.ToString())); - } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixCodeFix.cs index d21687c..9c6f613 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/RemoveTestSuffix/RemoveTestSuffixCodeFix.cs @@ -7,12 +7,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Tests.RemoveTestSuffix { - [ExportCodeFixProvider(nameof(RemoveTestSuffixCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.RemoveTestSuffix + "CF", LanguageNames.CSharp), Shared] public class RemoveTestSuffixCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds @@ -32,12 +31,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create(VSDiagnosticsResources.RemoveTestSuffixCodeFixTitle, - x => RemoveTestSuffix(context.Document, root, methodDeclaration, context.CancellationToken), RemoveTestSuffixAnalyzer.Rule.Id), + x => RemoveTestSuffixAsync(context.Document, root, methodDeclaration, x), RemoveTestSuffixAnalyzer.Rule.Id), diagnostic); } - private async Task RemoveTestSuffix(Document document, SyntaxNode root, - MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) + private async Task RemoveTestSuffixAsync(Document document, SyntaxNode root, + MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) { var newMethodName = methodDeclaration.Identifier.Text.Remove(methodDeclaration.Identifier.Text.Length - 4); return await RenameHelper.RenameSymbolAsync(document, root, methodDeclaration.Identifier, newMethodName, cancellationToken); diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestHelpers.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestHelpers.cs new file mode 100644 index 0000000..4f3ad93 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestHelpers.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace VSDiagnostics.Diagnostics.Tests +{ + internal static class TestHelpers + { + private static readonly string[] MethodAttributes = { "Test", "TestMethod", "Fact" }; + + internal static bool HasTestAttribute(this MethodDeclarationSyntax method) + { + var attributes = method.AttributeLists.FirstOrDefault()?.Attributes; + + if (attributes == null) + { + return false; + } + + foreach (var attribute in attributes.Value) + { + var attributeName = attribute.Name.ToString(); + if (MethodAttributes.Contains(attributeName)) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierAnalyzer.cs index 504ea0e..3503c96 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierAnalyzer.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierAnalyzer.cs @@ -22,40 +22,17 @@ internal static DiagnosticDescriptor Rule public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); - } + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var method = context.Node as MethodDeclarationSyntax; - if (method == null) - { - return; - } + var method = (MethodDeclarationSyntax) context.Node; - if (IsTestMethod(method)) + if (method.HasTestAttribute() && !method.Modifiers.Any(SyntaxKind.PublicKeyword)) { - if (!method.Modifiers.Any(SyntaxKind.PublicKeyword)) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, method.Identifier.GetLocation(), - method.Identifier.Text)); - } + context.ReportDiagnostic(Diagnostic.Create(Rule, method.Identifier.GetLocation(), + method.Identifier.Text)); } } - - private static bool IsTestMethod(MethodDeclarationSyntax method) - { - var methodAttributes = new[] {"Test", "TestMethod", "Fact"}; - var attributes = method.AttributeLists.FirstOrDefault()?.Attributes; - - if (attributes == null) - { - return false; - } - - return attributes.Value.Any(x => methodAttributes.Contains(x.Name.ToString())); - } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierCodeFix.cs index 69292ee..402bbd6 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierCodeFix.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutPublicModifier/TestMethodWithoutPublicModifierCodeFix.cs @@ -7,10 +7,11 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; +using VSDiagnostics.Utilities; namespace VSDiagnostics.Diagnostics.Tests.TestMethodWithoutPublicModifier { - [ExportCodeFixProvider(nameof(TestMethodWithoutPublicModifierCodeFix), LanguageNames.CSharp), Shared] + [ExportCodeFixProvider(DiagnosticId.TestMethodWithoutPublicModifier + "CF", LanguageNames.CSharp), Shared] public class TestMethodWithoutPublicModifierCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutTestAttribute/TestMethodWithoutTestAttributeAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutTestAttribute/TestMethodWithoutTestAttributeAnalyzer.cs new file mode 100644 index 0000000..299929b --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/Tests/TestMethodWithoutTestAttribute/TestMethodWithoutTestAttributeAnalyzer.cs @@ -0,0 +1,99 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.Tests.TestMethodWithoutTestAttribute +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class TestMethodWithoutTestAttributeAnalyzer : DiagnosticAnalyzer + { + private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + private static readonly string Category = VSDiagnosticsResources.TestsCategory; + private static readonly string Message = VSDiagnosticsResources.TestMethodWithoutTestAttributeAnalyzerMessage; + private static readonly string Title = VSDiagnosticsResources.TestMethodWithoutTestAttributeAnalyzerTitle; + private static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.TestMethodWithoutTestAttribute, Title, Message, Category, Severity, true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var method = (MethodDeclarationSyntax) context.Node; + + // Check if we're in a unit-test context + // For NUnit and MSTest we can see if the enclosing class/struct has a [TestClass] or [TestFixture] attribute + // For xUnit.NET we will have to see if there are other methods in the current class that contain a [Fact] attribute + + var enclosingType = method.GetEnclosingTypeNode(); + if (!enclosingType.IsKind(SyntaxKind.ClassDeclaration) && !enclosingType.IsKind(SyntaxKind.StructDeclaration)) + { + return; + } + + var symbol = context.SemanticModel.GetDeclaredSymbol(enclosingType) as INamedTypeSymbol; + if (symbol == null) + { + return; + } + + var isTestClass = false; + foreach (var attribute in symbol.GetAttributes()) + { + if (attribute.AttributeClass.Name == "TestClass" || attribute.AttributeClass.Name == "TestFixture") + { + isTestClass = true; + break; + } + } + + // If it has different attributes then we won't bother with it either + if (method.AttributeLists.SelectMany(x => x.Attributes).Any()) + { + return; + } + + if (!isTestClass) + { + // Look at other methods in the class to see if they have a test attribute + // We do this only for xUnit.NET because the others should already have been caught with the previous test + // If they weren't, it means the entire class wasn't marked as a test which is not in the scope of this analyzer + + foreach (var member in enclosingType.DescendantNodes().OfType(SyntaxKind.MethodDeclaration)) + { + foreach (var attributeList in member.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (attribute.Name.ToString() == "Fact" || attribute.Name.ToString() == "Theory") + { + isTestClass = true; + } + } + } + } + } + + if (!isTestClass) + { + return; + } + + var returnType = context.SemanticModel.GetTypeInfo(method.ReturnType).Type; + var voidType = context.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Void); + var taskType = context.SemanticModel.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task"); + var taskTType = context.SemanticModel.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task`1"); + if (!(returnType.Equals(voidType) || returnType.Equals(taskType) || returnType.OriginalDefinition.Equals(taskTType))) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Rule, method.Identifier.GetLocation(), method.Identifier)); + } + } +} diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Properties/AssemblyInfo.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Properties/AssemblyInfo.cs index 2f483b6..8acc79a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Properties/AssemblyInfo.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Properties/AssemblyInfo.cs @@ -34,5 +34,4 @@ [assembly: AssemblyVersion("1.9.3")] [assembly: AssemblyFileVersion("1.9.3.0")] [assembly: AssemblyInformationalVersion("1.9.3")] - [assembly: InternalsVisibleTo("VSDiagnostics.Test")] \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs index 5529931..4c7497a 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/DiagnosticId.cs @@ -20,12 +20,10 @@ public static class DiagnosticId public const string CompareBooleanToTrueLiteral = "VSD0016"; public const string ConditionalOperatorReturnsDefaultOptions = "VSD0017"; public const string ConditionalOperatorReturnsInvertedDefaultOptions = "VSD0018"; - public const string ConditionIsAlwaysFalse = "VSD0019"; - public const string ConditionIsAlwaysTrue = "VSD0020"; + public const string ConditionIsConstant = "VSD0020"; public const string ExplicitAccessModifiers = "VSD0021"; public const string GotoDetection = "VSD0022"; - public const string IfStatementWithoutBraces = "VSD0023"; - public const string LoopStatementWithoutBraces = "VSD0024"; + public const string MissingBraces = "VSD0023"; public const string NamingConventions = "VSD0025"; public const string NonEncapsulatedOrMutableField = "VSD0026"; public const string NullableToShorthand = "VSD0027"; @@ -41,8 +39,30 @@ public static class DiagnosticId public const string RemoveTestSuffix = "VSD0037"; public const string TestMethodWithoutPublicModifier = "VSD0038"; public const string FlagsEnumValuesDontFit = "VSD0039"; - public const string NamingConventionsConflictingMember = "VSD0040"; + public const string NamingConventionsConflictingMember = "VSD0040"; // Unused. What to do? public const string SyncMethodWithAsyncSuffix = "VSD0041"; public const string StringDotFormatWithDifferentAmountOfArguments = "VSD0042"; + public const string LoopedRandomInstantiation = "VSD0043"; + public const string SwitchDoesNotHandleAllEnumOptions = "VSD0044"; + public const string DivideIntegerByInteger = "VSD0045"; + public const string EqualsAndGetHashcodeNotImplementedTogether = "VSD0046"; + public const string ElementaryMethodsOfTypeInCollectionNotOverridden = "VSD0047"; + public const string RedundantPrivateSetter = "VSD0048"; + public const string SwitchIsMissingDefaultLabel = "VSD0049"; + public const string StructWithoutElementaryMethodsOverridden = "VSD0050"; + public const string ExceptionThrownFromImplicitOperator = "VSD0052"; + public const string ExceptionThrownFromPropertyGetter = "VSD0053"; + public const string ExceptionThrownFromStaticConstructor = "VSD0054"; + public const string ExceptionThrownFromFinallyBlock = "VSD0055"; + public const string ExceptionThrownFromEqualityOperator = "VSD0056"; + public const string ExceptionThrownFromDispose = "VSD0057"; + public const string ExceptionThrownFromFinalizer = "VSD0058"; + public const string ExceptionThrownFromGetHashCode = "VSD0059"; + public const string ExceptionThrownFromEquals = "VSD0060"; + public const string ImplementEqualsAndGetHashCode = "VSD0061"; + 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/Utilities/Extensions.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs index 142e4b3..6b2782f 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -31,26 +30,45 @@ public static class Extensions { nameof(String), "string" } }; - - public static bool ImplementsInterface(this ClassDeclarationSyntax classDeclaration, SemanticModel semanticModel, - Type interfaceType) + public static bool ImplementsInterfaceOrBaseClass(this INamedTypeSymbol typeSymbol, Type interfaceType) { - if (classDeclaration == null) + if (typeSymbol == null) { return false; } - var declaredSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); + if (typeSymbol.BaseType.MetadataName == interfaceType.Name) + { + return true; + } - return declaredSymbol != null && - (declaredSymbol.Interfaces.Any(i => i.MetadataName == interfaceType.Name) || - declaredSymbol.BaseType.MetadataName == typeof(INotifyPropertyChanged).Name); + foreach (var @interface in typeSymbol.AllInterfaces) + { + if (@interface.MetadataName == interfaceType.Name) + { + return true; + } + } - // For some peculiar reason, "class Foo : INotifyPropertyChanged" doesn't have any interfaces, - // But "class Foo : IFoo, INotifyPropertyChanged" has two. "IFoo" is an interface defined by me. - // However, the BaseType for the first is the "INotifyPropertyChanged" symbol. - // Also, "class Foo : INotifyPropertyChanged, IFoo" has just one - "IFoo", - // But the BaseType again is "INotifyPropertyChanged". + return false; + } + + public static bool ImplementsInterface(this INamedTypeSymbol typeSymbol, Type interfaceType) + { + if (typeSymbol == null) + { + return false; + } + + foreach (var @interface in typeSymbol.AllInterfaces) + { + if (@interface.MetadataName == interfaceType.Name) + { + return true; + } + } + + return false; } public static bool InheritsFrom(this ISymbol typeSymbol, Type type) @@ -68,7 +86,7 @@ public static bool InheritsFrom(this ISymbol typeSymbol, Type type) { return true; } - baseType = ((ITypeSymbol) baseType).BaseType; + baseType = ((ITypeSymbol)baseType).BaseType; } return false; @@ -76,37 +94,33 @@ public static bool InheritsFrom(this ISymbol typeSymbol, Type type) public static bool IsCommentTrivia(this SyntaxTrivia trivia) { - var commentTrivias = new[] + switch (trivia.Kind()) { - SyntaxKind.SingleLineCommentTrivia, - SyntaxKind.MultiLineCommentTrivia, - SyntaxKind.DocumentationCommentExteriorTrivia, - SyntaxKind.SingleLineDocumentationCommentTrivia, - SyntaxKind.MultiLineDocumentationCommentTrivia, - SyntaxKind.EndOfDocumentationCommentToken, - SyntaxKind.XmlComment, - SyntaxKind.XmlCommentEndToken, - SyntaxKind.XmlCommentStartToken - }; - - return commentTrivias.Any(x => trivia.IsKind(x)); + case SyntaxKind.SingleLineCommentTrivia: + case SyntaxKind.MultiLineCommentTrivia: + case SyntaxKind.DocumentationCommentExteriorTrivia: + case SyntaxKind.SingleLineDocumentationCommentTrivia: + case SyntaxKind.MultiLineDocumentationCommentTrivia: + case SyntaxKind.EndOfDocumentationCommentToken: + case SyntaxKind.XmlComment: + case SyntaxKind.XmlCommentEndToken: + case SyntaxKind.XmlCommentStartToken: + return true; + default: + return false; + } } public static bool IsWhitespaceTrivia(this SyntaxTrivia trivia) { - var whitespaceTrivia = new[] + switch (trivia.Kind()) { - SyntaxKind.WhitespaceTrivia, - SyntaxKind.EndOfLineTrivia - }; - - return whitespaceTrivia.Any(x => trivia.IsKind(x)); - } - - public static bool IsNullable(this ITypeSymbol typeSymbol) - { - //TODO: this is really ugly. - return typeSymbol.IsValueType && typeSymbol.MetadataName.StartsWith(typeof(Nullable).Name); + case SyntaxKind.WhitespaceTrivia: + case SyntaxKind.EndOfLineTrivia: + return true; + default: + return false; + } } public static string ToAlias(this string type) @@ -135,16 +149,6 @@ public static bool HasAlias(this string type, out string alias) return AliasMapping.TryGetValue(type, out alias); } - public static bool HasAlias(this string type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - return AliasMapping.Keys.Contains(type); - } - /// /// Determines whether or not the specified is the symbol of an asynchronous method. This /// can be a method declared as async (e.g. returning or ), or a method @@ -152,9 +156,9 @@ public static bool HasAlias(this string type) /// public static bool IsAsync(this IMethodSymbol methodSymbol) { - return methodSymbol.IsAsync - || methodSymbol.ReturnType.MetadataName == typeof(Task).Name - || methodSymbol.ReturnType.MetadataName == typeof(Task<>).Name; + return methodSymbol.IsAsync || + methodSymbol.ReturnType.MetadataName == typeof(Task).Name || + methodSymbol.ReturnType.MetadataName == typeof(Task<>).Name; } public static bool IsDefinedInAncestor(this IMethodSymbol methodSymbol) @@ -168,12 +172,14 @@ public static bool IsDefinedInAncestor(this IMethodSymbol methodSymbol) var interfaces = containingType.AllInterfaces; foreach (var @interface in interfaces) { - var interfaceMethods = - @interface.GetMembers().Select(containingType.FindImplementationForInterfaceMember).Where(x => x != null); + var interfaceMethods = @interface.GetMembers().Select(containingType.FindImplementationForInterfaceMember); - if (interfaceMethods.Any(method => method.Equals(methodSymbol))) + foreach (var method in interfaceMethods) { - return true; + if (method != null && method.Equals(methodSymbol)) + { + return true; + } } } @@ -181,10 +187,15 @@ public static bool IsDefinedInAncestor(this IMethodSymbol methodSymbol) while (baseType != null) { var baseMethods = baseType.GetMembers().OfType(); - if (baseMethods.Any(method => method.Equals(methodSymbol.OverriddenMethod))) + + foreach (var method in baseMethods) { - return true; + if (method.Equals(methodSymbol.OverriddenMethod)) + { + return true; + } } + baseType = baseType.BaseType; } @@ -207,12 +218,6 @@ public static bool IsAnInvocationOf(this InvocationExpressionSyntax invocation, invokedMethod.Symbol.MetadataName == method; } - // TODO: tests - public static T ElementAtOrDefault(this IEnumerable list, int index, T @default) - { - return index >= 0 && index < list.Count() ? list.ElementAt(index) : @default; - } - // TODO: tests public static bool IsNameofInvocation(this InvocationExpressionSyntax invocation) { @@ -227,5 +232,103 @@ public static bool IsNameofInvocation(this InvocationExpressionSyntax invocation return identifier != null && identifier.Identifier.ValueText == "nameof"; } + + /// + /// Gets the innermost surrounding class, struct or interface declaration + /// + /// The node to start from + /// The surrounding declaration node + /// Thrown when there is no surrounding class, struct or interface declaration"/> + public static SyntaxNode GetEnclosingTypeNode(this SyntaxNode syntaxNode) + { + foreach (var ancestor in syntaxNode.AncestorsAndSelf()) + { + if (ancestor.IsKind(SyntaxKind.ClassDeclaration) || ancestor.IsKind(SyntaxKind.StructDeclaration) || ancestor.IsKind(SyntaxKind.InterfaceDeclaration)) + { + return ancestor; + } + } + + throw new ArgumentException("The node is not contained in a type", nameof(syntaxNode)); + } + + public static IEnumerable OfType(this IEnumerable enumerable, SyntaxKind kind) where T : SyntaxNode + { + foreach (var node in enumerable) + { + if (node.IsKind(kind)) + { + yield return (T) node; + } + } + } + + public static bool ContainsAny(this SyntaxTokenList list, params SyntaxKind[] kinds) + { + foreach (var item in list) + { + foreach (var kind in kinds) + { + if (item.IsKind(kind)) + { + return true; + } + } + } + + return false; + } + + public static bool Contains(this SyntaxTokenList list, SyntaxKind kind) + { + foreach (var item in list) + { + if (item.Kind() == kind) + { + return true; + } + } + + return false; + } + + public static bool Contains(this IEnumerable list, SyntaxKind kind) + { + foreach (var syntaxKind in list) + { + if (syntaxKind == kind) + { + return true; + } + } + + return false; + } + + public static bool Any(this IEnumerable list, SyntaxKind kind) + { + foreach (var node in list) + { + if (node.IsKind(kind)) + { + return true; + } + } + + return false; + } + + public static bool IsAnyKind(this SyntaxNode node, params SyntaxKind[] kinds) + { + foreach (var kind in kinds) + { + if (node.IsKind(kind)) + { + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/RenameHelper.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/RenameHelper.cs index 6df3c75..d3607e6 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/RenameHelper.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/RenameHelper.cs @@ -1,9 +1,4 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -// Found at https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/RenameHelper.cs - -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -37,7 +32,7 @@ internal static class RenameHelper "CS0412", // 'generic': a parameter or local variable cannot have the same name as a method type parameter "CS0473", // Explicit interface implementation 'method name' matches more than one interface member. "CS0542", // 'user-defined type' : member names cannot be the same as their enclosing type, - "CS1061", // 'type' does not contain a definition for 'member' and no extension method 'name' accepting (..) + "CS1061" // 'type' does not contain a definition for 'member' and no extension method 'name' accepting (..) }); public static async Task RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken) diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj index 07a2a73..f9102f6 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj @@ -32,8 +32,11 @@ 4 + + + @@ -48,9 +51,11 @@ + + @@ -63,19 +68,21 @@ - - - - + + + + + + - - - - + + + + @@ -85,10 +92,17 @@ + + + + + + + @@ -102,10 +116,14 @@ + + + + @@ -118,6 +136,7 @@ + Designer PreserveNewest @@ -130,6 +149,18 @@ PreserveNewest + + + ResXFileCodeGenerator + VSDiagnosticsResources.Designer.cs + Designer + + + + + + + ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll @@ -143,10 +174,6 @@ ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll True - - ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.VisualBasic.dll - True - ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll True @@ -157,39 +184,29 @@ ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - False + True ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - False + True ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - False + True ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - False + True ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - False + True ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll True - - - - - - - ResXFileCodeGenerator - VSDiagnosticsResources.Designer.cs - - @@ -209,7 +226,7 @@ -