Skip to content

Commit

Permalink
Add HLQ009_RemoveOptionalMethods (#30)
Browse files Browse the repository at this point in the history
* Add HLQ009

* Add IsEmptyDispose and IsEmptyReset methods

* Refactor and test methods IsEmptyMethod and IsEmptyAsyncMethod

* Cleanup

* Implement rule

* Add documentation
  • Loading branch information
aalmada authored May 12, 2020
1 parent 7992ef0 commit 0ca1906
Show file tree
Hide file tree
Showing 40 changed files with 838 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void Verify_NoDiagnostics(string path)

[Theory]
[InlineData("TestData/HLQ007/Diagnostic/IAsyncEnumerable.Arrow.cs", "Enumerator", 11, 16)]
[InlineData("TestData/HLQ007/Diagnostic/IAsyncEnumerable.Block.cs", "Enumerator", 11, 16)]
[InlineData("TestData/HLQ007/Diagnostic/IAsyncEnumerable.Block.cs", "Enumerator", 13, 16)]
[InlineData("TestData/HLQ007/Diagnostic/IEnumerable.cs", "Enumerator", 10, 16)]
public void Verify_Diagnostic(string path, string enumeratorName, int line, int column)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using System.IO;
using System.Linq;
using TestHelper;
using Xunit;

namespace NetFabric.Hyperlinq.Analyzer.UnitTests
{
public class RemoveOptionalMethodsAnalyzerTests : DiagnosticVerifier
{
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() =>
new RemoveOptionalMethodsAnalyzer();

[Theory]
[InlineData("TestData/HLQ009/NoDiagnostic/AsyncEnumerable.cs")]
[InlineData("TestData/HLQ009/NoDiagnostic/Dispose.cs")]
[InlineData("TestData/HLQ009/NoDiagnostic/DisposeAsync.cs")]
[InlineData("TestData/HLQ009/NoDiagnostic/Enumerable.cs")]
[InlineData("TestData/HLQ009/NoDiagnostic/Reset.cs")]
public void Verify_NoDiagnostics(string path)
{
var paths = new[]
{
path,
};
VerifyCSharpDiagnostic(paths.Select(path => File.ReadAllText(path)).ToArray());
}

[Theory]
[InlineData("TestData/HLQ009/Diagnostic/Dispose.cs", "Dispose", 16, 25)]
[InlineData("TestData/HLQ009/Diagnostic/DisposeAsync.cs", "DisposeAsync", 18, 30)]
[InlineData("TestData/HLQ009/Diagnostic/Reset.cs", "Reset", 16, 25)]
public void Verify_Diagnostic(string path, string name, int line, int column)
{
var paths = new[]
{
path,
};
var sources = paths.Select(path => File.ReadAllText(path)).ToArray();
var expected = new DiagnosticResult
{
Id = "HLQ009",
Message = $"Consider removing the empty optional enumerator method '{name}'.",
Severity = DiagnosticSeverity.Info,
Locations = new[] {
new DiagnosticResultLocation("Test0.cs", line, column)
},
};

VerifyCSharpDiagnostic(paths.Select(path => File.ReadAllText(path)).ToArray(), expected);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.IO;
using System.Linq;
using Xunit;

namespace NetFabric.Hyperlinq.Analyzer.UnitTests
{
public class MethodDeclarationSyntaxExtensionsTests
{
[Theory]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/ReturnsVoid.cs", nameof(global::ReturnsVoid.ReturnsVoidMethod), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/ReturnsVoid.cs", nameof(global::ReturnsVoid.ReturnsTypeMethod), false)]
public void ReturnsVoid_Should_Succeed(string path, string methodName, bool expected)
{
// Arrange
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(path));
var method = tree.GetRoot()
.DescendantNodes().OfType<MethodDeclarationSyntax>()
.FirstOrDefault(method => method.Identifier.ValueText == methodName);

// Act
var result = method.ReturnsVoid();

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyArrowDefaultAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyArrowNewAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyArrowThrowAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyBlockDefaultAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyBlockNewAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.EmptyBlockThrowAsync), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.NotEmptyBlockDefaultAsync), false)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.NotEmptyBlockNewAsync), false)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyAsyncMethod.cs", nameof(global::IsEmptyAsyncMethod.NotEmptyBlockThrowAsync), false)]
public void IsEmptyAsyncMethod_Should_Succeed(string path, string methodName, bool expected)
{
// Arrange
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(path));
var method = tree.GetRoot()
.DescendantNodes().OfType<MethodDeclarationSyntax>()
.FirstOrDefault(method => method.Identifier.ValueText == methodName);

// Act
var result = method.IsEmptyAsyncMethod();

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyMethod.cs", nameof(global::IsEmptyMethod.EmptyArrowThrow), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyMethod.cs", nameof(global::IsEmptyMethod.EmptyBlock), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyMethod.cs", nameof(global::IsEmptyMethod.EmptyBlockThrow), true)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyMethod.cs", nameof(global::IsEmptyMethod.NotEmptyBlock), false)]
[InlineData("TestData/MethodDeclarationSyntaxExtensions/IsEmptyMethod.cs", nameof(global::IsEmptyMethod.NotEmptyBlockThrow), false)]
public void IsEmptyMethod_Should_Succeed(string path, string methodName, bool expected)
{
// Arrange
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(path));
var method = tree.GetRoot()
.DescendantNodes().OfType<MethodDeclarationSyntax>()
.FirstOrDefault(method => method.Identifier.ValueText == methodName);

// Act
var result = method.IsEmptyMethod();

// Assert
Assert.Equal(expected, result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace HLQ001.NoDiagnostic
{
class FieldDeclarationAsync
public class FieldDeclarationAsync
{
OptimizedAsyncEnumerable<TestType> field00 = new OptimizedAsyncEnumerable<TestType>();
IAsyncEnumerable<TestType> field01 = new NonOptimizedAsyncEnumerable<TestType>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#pragma warning disable IDE0052 // Remove unread private members

namespace HLQ001.NoDiagnostic
{
class FieldDeclaration
{
public class FieldDeclaration
{
OptimizedEnumerable<TestType> field00 = new OptimizedEnumerable<TestType>();
IEnumerable<TestType> field01 = new NonOptimizedEnumerable<TestType>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace HLQ001.NoDiagnostic
{
class PropertyDeclarationAsync
public class PropertyDeclarationAsync
{
OptimizedAsyncEnumerable<TestType> Property00 { get; set; } = new OptimizedAsyncEnumerable<TestType>();
IAsyncEnumerable<TestType> Property01 { get; set; } = new NonOptimizedAsyncEnumerable<TestType>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace HLQ001.NoDiagnostic
{
class PropertyDeclaration
public class PropertyDeclaration
{
OptimizedEnumerable<TestType> Property00 { get; set; } = new OptimizedEnumerable<TestType>();
IEnumerable<TestType> Property01 { get; set; } = new NonOptimizedEnumerable<TestType>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
#pragma warning disable IDE0022 // Use expression body for methods

using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System;
using System.Collections.Generic;
using System.Threading;
Expand All @@ -20,9 +22,7 @@ public void Reset() { }

public ValueTask DisposeAsync()
{
#pragma warning disable IDE0022 // Use expression body for methods
return default;
#pragma warning restore IDE0022 // Use expression body for methods
}
}
}
Expand All @@ -37,3 +37,5 @@ async ValueTask Test_AsyncEnumerableBlock()
}
}
}

#pragma warning restore IDE0022 // Use expression body for methods
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System;

namespace HLQ009.Diagnostics
{
readonly struct DisposeEnumerable<T>
{
public Enumerator GetEnumerator() => new Enumerator();

public struct Enumerator : IDisposable
{
public T Current => default;

public bool MoveNext() => false;

public void Dispose() { }
}
}

partial class C
{
public void Test_DisposeEnumerable()
{
foreach (var _ in new DisposeEnumerable<TestType>())
{

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace HLQ009.Diagnostics.Async
{
readonly struct DisposeAsyncEnumerable<T>
{
public Enumerator GetAsyncEnumerator() => new Enumerator();

public struct Enumerator : IAsyncDisposable
{
public T Current => default;

public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false);

public ValueTask DisposeAsync() => new ValueTask();
}
}

partial class C
{
public async ValueTask Test_DisposeAsyncEnumerable()
{
await foreach (var _ in new DisposeAsyncEnumerable<TestType>())
{

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System;

namespace HLQ009.Diagnostics
{
readonly struct ResetEnumerable<T>
{
public Enumerator GetEnumerator() => new Enumerator();

public struct Enumerator
{
public T Current => default;

public bool MoveNext() => false;

public void Reset() => throw new NotImplementedException();
}
}

partial class C
{
public void Test_ResetEnumerable()
{
foreach (var _ in new ResetEnumerable<TestType>())
{

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System.Threading.Tasks;

namespace HLQ009.NoDiagnostics
{
readonly struct AsyncEnumerable<T>
{
public Enumerator GetAsyncEnumerator() => new Enumerator();

public struct Enumerator
{
public T Current => default;

public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false);
}
}

partial class C
{
public async ValueTask Test_AsyncEnumerable()
{
await foreach (var _ in new AsyncEnumerable<TestType>())
{

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using NetFabric.Hyperlinq.Analyzer.UnitTests.TestData;
using System;
using System.Collections;
using System.Collections.Generic;

namespace HLQ009.NoDiagnostics
{
readonly struct DisposeEnumerable<T> : IEnumerable<T>
{
public Enumerator GetEnumerator() => new Enumerator();
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator();
IEnumerator IEnumerable.GetEnumerator() => new Enumerator();

public struct Enumerator : IEnumerator<T>
{
public T Current => default;
object IEnumerator.Current => default;

public bool MoveNext() => false;

public void Reset() => throw new NotImplementedException();

public void Dispose() { }
}
}

partial class C
{
public void Test_DisposeEnumerable()
{
foreach (var _ in new DisposeEnumerable<TestType>())
{

}
}
}
}
Loading

0 comments on commit 0ca1906

Please sign in to comment.