-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HLQ006: GetEnumerator() or GetAsyncEnumerator() should not return an …
…interface type (#22) * Add GetEnumeratorReturnTypeAnalyzer * Add documentation
- Loading branch information
Showing
6 changed files
with
380 additions
and
4 deletions.
There are no files selected for viewing
182 changes: 182 additions & 0 deletions
182
NetFabric.Hyperlinq.Analyzer.UnitTests/GetEnumeratorReturnTypeAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
using System; | ||
using Xunit; | ||
using TestHelper; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
|
||
namespace NetFabric.Hyperlinq.Analyzer.UnitTests | ||
{ | ||
public class GetEnumeratorReturnTypeAnalyzerTests : CodeFixVerifier | ||
{ | ||
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => | ||
new GetEnumeratorReturnTypeAnalyzer(); | ||
|
||
[Fact] | ||
public void Verify_NoDiagnostics() | ||
{ | ||
var test = @" | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
interface ICustomEnumerable | ||
{ | ||
IEnumerator GetEnumerator(); | ||
} | ||
interface IValueEnumerable<T, TEnumerator> : IEnumerable<T> | ||
where TEnumerator : struct, IEnumerator<T> | ||
{ | ||
new Enumerator GetEnumerator(); | ||
} | ||
readonly struct Enumerable | ||
{ | ||
public Enumerator GetEnumerator() => new Enumerator(); | ||
public struct Enumerator | ||
{ | ||
public int Current => 0; | ||
public bool MoveNext() => false; | ||
} | ||
} | ||
readonly struct Enumerable2 : IEnumerable<int> | ||
{ | ||
public Enumerator GetEnumerator() => new Enumerator(); | ||
IEnumerator<int> IEnumerable<int>.GetEnumerator() => new Enumerator(); | ||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(); | ||
public struct Enumerator : IEnumerator<int> | ||
{ | ||
public int Current => 0; | ||
public bool MoveNext() => false; | ||
} | ||
} | ||
readonly struct AsyncEnumerable | ||
{ | ||
public Enumerator GetAsyncEnumerator() => new Enumerator(); | ||
public struct Enumerator | ||
{ | ||
public int Current => 0; | ||
public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false); | ||
} | ||
} | ||
readonly struct AsyncEnumerable2 | ||
{ | ||
public Enumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => new Enumerator(); | ||
public struct Enumerator | ||
{ | ||
public int Current => 0; | ||
public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false); | ||
} | ||
} | ||
readonly struct AsyncEnumerable3 : IAsyncEnumerable<int> | ||
{ | ||
public Enumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => new Enumerator(); | ||
IAsyncEnumerator<int> IAsyncEnumerable<int>.GetAsyncEnumerator(CancellationToken cancellationToken = default) => new Enumerator(); | ||
public struct Enumerator : IAsyncEnumerator<int> | ||
{ | ||
public int Current => 0; | ||
public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false); | ||
} | ||
} | ||
"; | ||
|
||
VerifyCSharpDiagnostic(test); | ||
} | ||
|
||
[Fact] | ||
public void Verify_Enumerable() | ||
{ | ||
var test = @" | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
readonly struct Enumerable : IEnumerable<int> | ||
{ | ||
public IEnumerator<int> GetEnumerator() => new Enumerator(); | ||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(); | ||
public struct Enumerator : IEnumerator<int> | ||
{ | ||
public int Current => 0; | ||
object IEnumerator.Current => throw new NotImplementedException(); | ||
public bool MoveNext() => false; | ||
public void Reset() => throw new NotImplementedException(); | ||
public void Dispose() { } | ||
} | ||
} | ||
"; | ||
|
||
var expected = new DiagnosticResult | ||
{ | ||
Id = "HLQ006", | ||
Message = "'GetEnumerator' returns an interface. Consider returning a value-type enumerator.", | ||
Severity = DiagnosticSeverity.Warning, | ||
Locations = new[] { | ||
new DiagnosticResultLocation("Test0.cs", 8, 12) | ||
}, | ||
}; | ||
|
||
VerifyCSharpDiagnostic(test, expected); | ||
} | ||
|
||
|
||
[Fact] | ||
public void Verify_AsyncEnumerable() | ||
{ | ||
var test = @" | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
readonly struct AsyncEnumerable : IAsyncEnumerable<int> | ||
{ | ||
public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default) => new Enumerator(); | ||
public struct Enumerator : IAsyncEnumerator<int> | ||
{ | ||
public int Current => 0; | ||
public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false); | ||
public ValueTask DisposeAsync() => new ValueTask(); | ||
} | ||
} | ||
"; | ||
|
||
var expected = new DiagnosticResult | ||
{ | ||
Id = "HLQ006", | ||
Message = "'GetAsyncEnumerator' returns an interface. Consider returning a value-type enumerator.", | ||
Severity = DiagnosticSeverity.Warning, | ||
Locations = new[] { | ||
new DiagnosticResultLocation("Test0.cs", 9, 12) | ||
}, | ||
}; | ||
|
||
VerifyCSharpDiagnostic(test, expected); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
NetFabric.Hyperlinq.Analyzer/GetEnumeratorReturnTypeAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NetFabric.CodeAnalysis; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
|
||
namespace NetFabric.Hyperlinq.Analyzer | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class GetEnumeratorReturnTypeAnalyzer : DiagnosticAnalyzer | ||
{ | ||
const string DiagnosticId = DiagnosticIds.GetEnumeratorReturnTypeId; | ||
|
||
static readonly LocalizableString Title = | ||
new LocalizableResourceString(nameof(Resources.GetEnumeratorReturnType_Title), Resources.ResourceManager, typeof(Resources)); | ||
static readonly LocalizableString MessageFormat = | ||
new LocalizableResourceString(nameof(Resources.GetEnumeratorReturnType_MessageFormat), Resources.ResourceManager, typeof(Resources)); | ||
static readonly LocalizableString Description = | ||
new LocalizableResourceString(nameof(Resources.GetEnumeratorReturnType_Description), Resources.ResourceManager, typeof(Resources)); | ||
const string Category = "Performance"; | ||
|
||
static readonly DiagnosticDescriptor rule = | ||
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, description: Description, | ||
helpLinkUri: "https://github.com/NetFabric/NetFabric.Hyperlinq.Analyzer/tree/master/docs/reference/HLQ006_GetEnumeratorReturnType.md"); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => | ||
ImmutableArray.Create(rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); | ||
context.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); | ||
} | ||
|
||
static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) | ||
{ | ||
if (!(context.Node is MethodDeclarationSyntax methodDeclarationSyntax)) | ||
return; | ||
|
||
var semanticModel = context.SemanticModel; | ||
|
||
// check if it returns an interface | ||
var returnType = methodDeclarationSyntax.ReturnType; | ||
if (semanticModel.GetTypeInfo(returnType).Type.TypeKind != TypeKind.Interface) | ||
return; | ||
|
||
// check if it's public | ||
if (!methodDeclarationSyntax.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PublicKeyword))) | ||
return; | ||
|
||
// check if it's "GetEnumerator" or "GetAsyncEnumerator" | ||
var identifier = methodDeclarationSyntax.Identifier.ValueText; | ||
if (identifier == "GetEnumerator" && methodDeclarationSyntax.ParameterList.Parameters.Count == 0) | ||
{ | ||
// check if it returns an enumerator | ||
var type = semanticModel.GetTypeInfo(returnType).Type; | ||
if (!type.IsEnumerator(semanticModel.Compilation, out var enumerableSymbols)) | ||
return; | ||
} | ||
else if (identifier == "GetAsyncEnumerator" && methodDeclarationSyntax.ParameterList.Parameters.Count < 2) | ||
{ | ||
// check if it returns an async enumerator | ||
var type = semanticModel.GetTypeInfo(returnType).Type; | ||
if (!type.IsAsyncEnumerator(semanticModel.Compilation, out var enumerableSymbols)) | ||
return; | ||
} | ||
else | ||
{ | ||
return; | ||
} | ||
|
||
var diagnostic = Diagnostic.Create(rule, methodDeclarationSyntax.ReturnType.GetLocation(), identifier); | ||
context.ReportDiagnostic(diagnostic); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.