Skip to content

Commit 0ad62c6

Browse files
authored
MA0002 skips IImmutableSet<string> (#787)
1 parent 7e57a0b commit 0ad62c6

File tree

5 files changed

+66
-12
lines changed

5 files changed

+66
-12
lines changed

src/Meziantou.Analyzer/Internals/AwaitableTypes.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Meziantou.Analyzer.Internals;
77
internal sealed class AwaitableTypes
88
{
99
private readonly INamedTypeSymbol[] _taskOrValueTaskSymbols;
10+
private readonly Compilation _compilation;
1011

1112
public AwaitableTypes(Compilation compilation)
1213
{
@@ -30,6 +31,8 @@ public AwaitableTypes(Compilation compilation)
3031
{
3132
_taskOrValueTaskSymbols = [];
3233
}
34+
35+
_compilation = compilation;
3336
}
3437

3538
private INamedTypeSymbol? TaskSymbol { get; }
@@ -74,7 +77,7 @@ public bool IsAwaitable(ITypeSymbol? symbol, SemanticModel semanticModel, int po
7477
return false;
7578
}
7679

77-
public bool IsAwaitable(ITypeSymbol? symbol, Compilation compilation)
80+
public bool IsAwaitable(ITypeSymbol? symbol)
7881
{
7982
if (symbol is null)
8083
return false;
@@ -95,7 +98,7 @@ public bool IsAwaitable(ITypeSymbol? symbol, Compilation compilation)
9598
if (potentialSymbol is not IMethodSymbol getAwaiterMethod)
9699
continue;
97100

98-
if (!compilation.IsSymbolAccessibleWithin(potentialSymbol, compilation.Assembly))
101+
if (!_compilation.IsSymbolAccessibleWithin(potentialSymbol, _compilation.Assembly))
99102
continue;
100103

101104
if (!getAwaiterMethod.Parameters.IsEmpty)

src/Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void AnalyzeSymbol(SymbolAnalysisContext context)
8686
return;
8787

8888
var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal);
89-
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation))
89+
if (_awaitableTypes.IsAwaitable(method.ReturnType))
9090
{
9191
if (!hasAsyncSuffix)
9292
{
@@ -119,7 +119,7 @@ public void AnalyzeLocalFunction(OperationAnalysisContext context)
119119
var method = operation.Symbol;
120120

121121
var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal);
122-
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation))
122+
if (_awaitableTypes.IsAwaitable(method.ReturnType))
123123
{
124124
if (!hasAsyncSuffix)
125125
{

src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.Linq;
@@ -77,6 +77,8 @@ private sealed class AnalyzerContext(Compilation compilation)
7777
public INamedTypeSymbol? ComparerStringType { get; } = GetIComparerString(compilation);
7878
public INamedTypeSymbol? EnumerableType { get; } = compilation.GetBestTypeByMetadataName("System.Linq.Enumerable");
7979
public INamedTypeSymbol? ISetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.ISet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));
80+
public INamedTypeSymbol? IReadOnlySetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IReadOnlySet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));
81+
public INamedTypeSymbol? IImmutableSetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Immutable.IImmutableSet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));
8082

8183
public void AnalyzeConstructor(OperationAnalysisContext ctx)
8284
{
@@ -109,11 +111,18 @@ public void AnalyzeInvocation(OperationAnalysisContext ctx)
109111
// Most ISet implementation already configured the IEqualityComparer in this constructor,
110112
// so it should be ok to skip method calls on those types.
111113
// A concrete use-case is HashSet<string>.Contains which has an extension method IEnumerable.Contains(value, comparer)
112-
if (ISetType is not null && method.ContainingType.IsOrImplements(ISetType))
113-
return;
114+
foreach (var type in (ReadOnlySpan<ITypeSymbol?>)[ISetType, IReadOnlySetType, IImmutableSetType])
115+
{
114116

115-
if (operation.Instance is not null && operation.Instance.GetActualType()?.IsOrImplements(ISetType) == true)
116-
return;
117+
if (type is null)
118+
continue;
119+
120+
if (method.ContainingType.IsOrImplements(type))
121+
return;
122+
123+
if (operation.Instance is not null && operation.Instance.GetActualType()?.IsOrImplements(type) is true)
124+
return;
125+
}
117126

118127
if (operation.IsImplicit && IsQueryOperator(operation) && ctx.Options.GetConfigurationValue(operation, Rule.Id + ".exclude_query_operator_syntaxes", defaultValue: false))
119128
return;

tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.Diagnostics;
@@ -219,10 +219,10 @@ private Task<Project> CreateProject()
219219
break;
220220
}
221221

222-
AddNuGetReference("System.Collections.Immutable", "1.5.0", "lib/netstandard2.0/");
223222

224223
if (TargetFramework is not TargetFramework.Net7_0 and not TargetFramework.Net8_0 and not TargetFramework.Net9_0)
225224
{
225+
AddNuGetReference("System.Collections.Immutable", "1.5.0", "lib/netstandard2.0/");
226226
AddNuGetReference("System.Numerics.Vectors", "4.5.0", "ref/netstandard2.0/");
227227
}
228228

tests/Meziantou.Analyzer.Test/Rules/UseStringComparerAnalyzerTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
22
using Meziantou.Analyzer.Rules;
33
using Meziantou.Analyzer.Test.Helpers;
44
using TestHelper;
@@ -458,6 +458,48 @@ await CreateProjectBuilder()
458458
.ValidateAsync();
459459
}
460460

461+
[Fact]
462+
public async Task IReadOnlySet_Contain()
463+
{
464+
const string SourceCode = """
465+
using System.Linq;
466+
class TypeName
467+
{
468+
public void Test()
469+
{
470+
System.Collections.Generic.IReadOnlySet<string> obj = null;
471+
obj.Contains("");
472+
}
473+
}
474+
""";
475+
476+
await CreateProjectBuilder()
477+
.WithTargetFramework(TargetFramework.Net6_0)
478+
.WithSourceCode(SourceCode)
479+
.ValidateAsync();
480+
}
481+
482+
[Fact]
483+
public async Task IImmutableSet_Contain()
484+
{
485+
const string SourceCode = """
486+
using System.Linq;
487+
class TypeName
488+
{
489+
public void Test()
490+
{
491+
System.Collections.Immutable.IImmutableSet<string> obj = null;
492+
obj.Contains("");
493+
}
494+
}
495+
""";
496+
497+
await CreateProjectBuilder()
498+
.WithTargetFramework(TargetFramework.Net9_0)
499+
.WithSourceCode(SourceCode)
500+
.ValidateAsync();
501+
}
502+
461503
[Fact]
462504
public async Task StringArray_QuerySyntax_GroupBy_NoConfiguration()
463505
{

0 commit comments

Comments
 (0)