Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Meziantou.Analyzer/Internals/AwaitableTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Meziantou.Analyzer.Internals;
internal sealed class AwaitableTypes
{
private readonly INamedTypeSymbol[] _taskOrValueTaskSymbols;
private readonly Compilation _compilation;

public AwaitableTypes(Compilation compilation)
{
Expand All @@ -30,6 +31,8 @@ public AwaitableTypes(Compilation compilation)
{
_taskOrValueTaskSymbols = [];
}

_compilation = compilation;
}

private INamedTypeSymbol? TaskSymbol { get; }
Expand Down Expand Up @@ -74,7 +77,7 @@ public bool IsAwaitable(ITypeSymbol? symbol, SemanticModel semanticModel, int po
return false;
}

public bool IsAwaitable(ITypeSymbol? symbol, Compilation compilation)
public bool IsAwaitable(ITypeSymbol? symbol)
{
if (symbol is null)
return false;
Expand All @@ -95,7 +98,7 @@ public bool IsAwaitable(ITypeSymbol? symbol, Compilation compilation)
if (potentialSymbol is not IMethodSymbol getAwaiterMethod)
continue;

if (!compilation.IsSymbolAccessibleWithin(potentialSymbol, compilation.Assembly))
if (!_compilation.IsSymbolAccessibleWithin(potentialSymbol, _compilation.Assembly))
continue;

if (!getAwaiterMethod.Parameters.IsEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void AnalyzeSymbol(SymbolAnalysisContext context)
return;

var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal);
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation))
if (_awaitableTypes.IsAwaitable(method.ReturnType))
{
if (!hasAsyncSuffix)
{
Expand Down Expand Up @@ -119,7 +119,7 @@ public void AnalyzeLocalFunction(OperationAnalysisContext context)
var method = operation.Symbol;

var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal);
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation))
if (_awaitableTypes.IsAwaitable(method.ReturnType))
{
if (!hasAsyncSuffix)
{
Expand Down
19 changes: 14 additions & 5 deletions src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -77,6 +77,8 @@ private sealed class AnalyzerContext(Compilation compilation)
public INamedTypeSymbol? ComparerStringType { get; } = GetIComparerString(compilation);
public INamedTypeSymbol? EnumerableType { get; } = compilation.GetBestTypeByMetadataName("System.Linq.Enumerable");
public INamedTypeSymbol? ISetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.ISet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));
public INamedTypeSymbol? IReadOnlySetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IReadOnlySet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));
public INamedTypeSymbol? IImmutableSetType { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Immutable.IImmutableSet`1")?.Construct(compilation.GetSpecialType(SpecialType.System_String));

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

if (operation.Instance is not null && operation.Instance.GetActualType()?.IsOrImplements(ISetType) == true)
return;
if (type is null)
continue;

if (method.ContainingType.IsOrImplements(type))
return;

if (operation.Instance is not null && operation.Instance.GetActualType()?.IsOrImplements(type) is true)
return;
}

if (operation.IsImplicit && IsQueryOperator(operation) && ctx.Options.GetConfigurationValue(operation, Rule.Id + ".exclude_query_operator_syntaxes", defaultValue: false))
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -219,10 +219,10 @@ private Task<Project> CreateProject()
break;
}

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Meziantou.Analyzer.Rules;
using Meziantou.Analyzer.Test.Helpers;
using TestHelper;
Expand Down Expand Up @@ -458,6 +458,48 @@ await CreateProjectBuilder()
.ValidateAsync();
}

[Fact]
public async Task IReadOnlySet_Contain()
{
const string SourceCode = """
using System.Linq;
class TypeName
{
public void Test()
{
System.Collections.Generic.IReadOnlySet<string> obj = null;
obj.Contains("");
}
}
""";

await CreateProjectBuilder()
.WithTargetFramework(TargetFramework.Net6_0)
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task IImmutableSet_Contain()
{
const string SourceCode = """
using System.Linq;
class TypeName
{
public void Test()
{
System.Collections.Immutable.IImmutableSet<string> obj = null;
obj.Contains("");
}
}
""";

await CreateProjectBuilder()
.WithTargetFramework(TargetFramework.Net9_0)
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task StringArray_QuerySyntax_GroupBy_NoConfiguration()
{
Expand Down