From 6ae9486ae91b2c4343927357764e806cf0ab564c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 21:10:04 -0400 Subject: [PATCH] Fix MA0011 not reported for types with ToString(IFormatProvider) but no IFormattable (#910) --- .../CultureSensitiveFormattingContext.cs | 20 ++- .../Internals/OverloadFinder.cs | 146 ++++++++++++------ .../Internals/OverloadOptions.cs | 5 + .../Internals/OverloadParameterType.cs | 5 + ...otUseBlockingCallInAsyncContextAnalyzer.cs | 4 +- ...CaughtExceptionAsInnerExceptionAnalyzer.cs | 2 +- ...verloadThatHasCancellationTokenAnalyzer.cs | 2 +- ...seAnOverloadThatHasTimeProviderAnalyzer.cs | 2 +- .../Rules/UseIFormatProviderAnalyzer.cs | 10 +- .../Rules/UseStringComparerAnalyzer.cs | 8 +- .../Rules/UseStringComparisonAnalyzer.cs | 2 +- .../Rules/UseIFormatProviderAnalyzerTests.cs | 17 ++ 12 files changed, 156 insertions(+), 67 deletions(-) create mode 100644 src/Meziantou.Analyzer/Internals/OverloadOptions.cs create mode 100644 src/Meziantou.Analyzer/Internals/OverloadParameterType.cs diff --git a/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs b/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs index e315a5b1..cdf10d05 100755 --- a/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs +++ b/src/Meziantou.Analyzer/Internals/CultureSensitiveFormattingContext.cs @@ -63,12 +63,12 @@ public bool IsCultureSensitiveOperation(IOperation operation, CultureSensitiveOp if (_excludedMethods.Contains(invocation.TargetMethod)) return false; + if (invocation.HasArgumentOfType(FormatProviderSymbol, inherits: true)) + return false; + var methodName = invocation.TargetMethod.Name; if (methodName is "ToString") { - if (invocation.HasArgumentOfType(FormatProviderSymbol, inherits: true)) - return false; - // Try get the format. Most of ToString have only 1 string parameter to define the format IOperation? format = null; if (invocation.Arguments.Length > 0) @@ -281,13 +281,25 @@ private bool IsCultureSensitiveType(ITypeSymbol? typeSymbol, CultureSensitiveOpt if (typeSymbol.IsOrInheritFrom(NuGetVersioningSemanticVersionSymbol)) return false; - if (!typeSymbol.Implements(SystemIFormattableSymbol)) + if (!IsFormattableType(typeSymbol)) return false; if (!IsCultureSensitiveTypeUsingAttribute(typeSymbol)) return false; return true; + + bool IsFormattableType(ITypeSymbol type) + { + if (type.Implements(SystemIFormattableSymbol)) + return true; + + // May have ToString(IFormatProvider) even if IFormattable is not implemented directly + if (type.GetAllMembers().OfType().Any(m => m is { Name: "ToString", IsStatic: false, ReturnType: { SpecialType: SpecialType.System_String }, Parameters: [var param1] } && param1.Type.IsOrInheritFrom(FormatProviderSymbol) && m.DeclaredAccessibility is Accessibility.Public)) + return true; + + return false; + } } private bool IsCultureSensitiveTypeUsingAttribute(ITypeSymbol typeSymbol) diff --git a/src/Meziantou.Analyzer/Internals/OverloadFinder.cs b/src/Meziantou.Analyzer/Internals/OverloadFinder.cs index a1ca6eeb..44c2a753 100644 --- a/src/Meziantou.Analyzer/Internals/OverloadFinder.cs +++ b/src/Meziantou.Analyzer/Internals/OverloadFinder.cs @@ -1,77 +1,115 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; namespace Meziantou.Analyzer.Internals; + internal sealed class OverloadFinder(Compilation compilation) { private readonly ITypeSymbol? _obsoleteSymbol = compilation.GetBestTypeByMetadataName("System.ObsoleteAttribute"); - public bool HasOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - params ITypeSymbol[] additionalParameterTypes) + private static ReadOnlySpan Wrap(ReadOnlySpan types) { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, additionalParameterTypes) is not null; + var result = new OverloadParameterType[types.Length]; + for (var i = 0; i < types.Length; i++) + { + result[i] = new OverloadParameterType(types[i]); + } + + return result; } - public bool HasOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - IOperation currentOperation, - params ITypeSymbol[] additionalParameterTypes) + private static ReadOnlySpan RemoveNulls(ReadOnlySpan types) { - if (currentOperation.SemanticModel is null) - return false; + foreach (var type in types) + { + if (type.Symbol is not null) + continue; + + var list = new List(types.Length - 1); // We know there is at least one null item + foreach (var t in types) + { + if (t.Symbol is not null) + { + list.Add(t); + } + } - return FindOverloadWithAdditionalParameterOfType(methodSymbol, syntaxNode: currentOperation.Syntax, includeObsoleteMethods: false, allowOptionalParameters: false, additionalParameterTypes) is not null; + return list.ToArray(); + } + + return types; } - private IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - params ITypeSymbol[] additionalParameterTypes) + public bool HasOverloadWithAdditionalParameterOfType(IObjectCreationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, includeObsoleteMethods: false, allowOptionalParameters: false, additionalParameterTypes); + return FindOverloadWithAdditionalParameterOfType(operation, options, additionalParameterTypes) is not null; } - public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - bool includeObsoleteMethods, - bool allowOptionalParameters, - params ITypeSymbol[] additionalParameterTypes) + public bool HasOverloadWithAdditionalParameterOfType(IInvocationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, syntaxNode: null, includeObsoleteMethods, allowOptionalParameters, additionalParameterTypes); + return FindOverloadWithAdditionalParameterOfType(operation, options, additionalParameterTypes) is not null; } - public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - IOperation operation, - bool includeObsoleteMethods, - bool allowOptionalParameters, - params ITypeSymbol[] additionalParameterTypes) + public bool HasOverloadWithAdditionalParameterOfType(IInvocationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) { - if (operation.SemanticModel is null) - return null; + return FindOverloadWithAdditionalParameterOfType(operation, options, additionalParameterTypes) is not null; + } - return FindOverloadWithAdditionalParameterOfType(methodSymbol, operation.Syntax, includeObsoleteMethods, allowOptionalParameters, additionalParameterTypes); + public bool HasOverloadWithAdditionalParameterOfType(IMethodSymbol methodSymbol, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + return FindOverloadWithAdditionalParameterOfType(methodSymbol, options, additionalParameterTypes) is not null; } - public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - IMethodSymbol methodSymbol, - SyntaxNode? syntaxNode, - bool includeObsoleteMethods, - bool allowOptionalParameters, - params ITypeSymbol[] additionalParameterTypes) + public bool HasOverloadWithAdditionalParameterOfType(IMethodSymbol methodSymbol, OverloadOptions options, ReadOnlySpan additionalParameterTypes) { - if (additionalParameterTypes is null) + return FindOverloadWithAdditionalParameterOfType(methodSymbol, options, additionalParameterTypes) is not null; + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType(IInvocationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + if (options.SyntaxNode is null) + { + options = options with { SyntaxNode = operation.Syntax }; + } + + return FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, options, Wrap(additionalParameterTypes)); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType(IInvocationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + if (options.SyntaxNode is null) + { + options = options with { SyntaxNode = operation.Syntax }; + } + + return FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, options, additionalParameterTypes); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType(IObjectCreationOperation operation, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + if (operation.Constructor is null) return null; - additionalParameterTypes = [.. additionalParameterTypes.Where(type => type is not null)]; - if (additionalParameterTypes.Length == 0) + return FindOverloadWithAdditionalParameterOfType(operation.Constructor, options, Wrap(additionalParameterTypes)); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType(IMethodSymbol methodSymbol, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + return FindOverloadWithAdditionalParameterOfType(methodSymbol, options, Wrap(additionalParameterTypes)); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType(IMethodSymbol methodSymbol, OverloadOptions options, ReadOnlySpan additionalParameterTypes) + { + additionalParameterTypes = RemoveNulls(additionalParameterTypes); + if (additionalParameterTypes.IsEmpty) return null; ImmutableArray members; - if (syntaxNode is not null) + if (options.SyntaxNode is not null) { - var semanticModel = compilation.GetSemanticModel(syntaxNode.SyntaxTree); - members = semanticModel.LookupSymbols(syntaxNode.GetLocation().SourceSpan.End, methodSymbol.ContainingType, methodSymbol.Name, includeReducedExtensionMethods: true); + var semanticModel = compilation.GetSemanticModel(options.SyntaxNode.SyntaxTree); + members = semanticModel.LookupSymbols(options.SyntaxNode.GetLocation().SourceSpan.End, methodSymbol.ContainingType, methodSymbol.Name, includeReducedExtensionMethods: true); } else { @@ -82,10 +120,10 @@ public bool HasOverloadWithAdditionalParameterOfType( { if (member is IMethodSymbol method) { - if (!includeObsoleteMethods && IsObsolete(method)) + if (!options.IncludeObsoleteMembers && IsObsolete(method)) continue; - if (HasSimilarParameters(methodSymbol, method, allowOptionalParameters, additionalParameterTypes)) + if (HasSimilarParameters(methodSymbol, method, options.AllowOptionalParameters, additionalParameterTypes)) return method; } } @@ -93,6 +131,11 @@ public bool HasOverloadWithAdditionalParameterOfType( return null; } + public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol otherMethod, bool allowOptionalParameters, params ReadOnlySpan additionalParameterTypes) + { + return HasSimilarParameters(method, otherMethod, allowOptionalParameters, Wrap(additionalParameterTypes)); + } + /// /// Methods are similar if: /// @@ -102,7 +145,7 @@ public bool HasOverloadWithAdditionalParameterOfType( /// If , can have more parameters if they are optional /// /// - public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol otherMethod, bool allowOptionalParameters, params ITypeSymbol[] additionalParameterTypes) + public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol otherMethod, bool allowOptionalParameters, params ReadOnlySpan additionalParameterTypes) { if (method.IsEqualTo(otherMethod)) return false; @@ -135,13 +178,13 @@ public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol othe break; var additionalParameter = additionalParameterTypes[additionalParameterIndex]; - if (methodParameter.Type.IsEqualTo(additionalParameter)) + if (IsEqualTo(methodParameter.Type, additionalParameter)) { i++; continue; } - if (otherMethodParameter.Type.IsEqualTo(additionalParameter)) + if (IsEqualTo(otherMethodParameter.Type, additionalParameter)) { j++; continue; @@ -181,7 +224,7 @@ public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol othe var found = false; for (var i = 0; i < otherMethodParameters.Length; i++) { - if (otherMethodParameters[i].Type.IsEqualTo(paramType)) + if (IsEqualTo(otherMethodParameters[i].Type, paramType)) { otherMethodParameters = otherMethodParameters.RemoveAt(i); found = true; @@ -204,6 +247,13 @@ public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol othe return false; } + + static bool IsEqualTo(ITypeSymbol left, OverloadParameterType right) + { + return right.AllowInherits + ? left.IsOrInheritFrom(right.Symbol) + : left.IsEqualTo(right.Symbol); + } } private bool IsObsolete(IMethodSymbol methodSymbol) diff --git a/src/Meziantou.Analyzer/Internals/OverloadOptions.cs b/src/Meziantou.Analyzer/Internals/OverloadOptions.cs new file mode 100644 index 00000000..9d619e48 --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/OverloadOptions.cs @@ -0,0 +1,5 @@ +using Microsoft.CodeAnalysis; + +namespace Meziantou.Analyzer.Internals; + +internal record struct OverloadOptions(bool IncludeObsoleteMembers = false, bool AllowOptionalParameters = false, bool IncludeExtensionsMethods = false, SyntaxNode? SyntaxNode = null); diff --git a/src/Meziantou.Analyzer/Internals/OverloadParameterType.cs b/src/Meziantou.Analyzer/Internals/OverloadParameterType.cs new file mode 100644 index 00000000..c7058e7e --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/OverloadParameterType.cs @@ -0,0 +1,5 @@ +using Microsoft.CodeAnalysis; + +namespace Meziantou.Analyzer.Internals; + +internal record struct OverloadParameterType(ITypeSymbol? Symbol, bool AllowInherits = false); diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs index c4f13fab..8c7d394a 100755 --- a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs @@ -295,10 +295,10 @@ private bool IsPotentialMember(IInvocationOperation operation, IMethodSymbol met if (methodSymbol.HasAttribute(ObsoleteAttributeSymbol)) return false; - if (OverloadFinder.HasSimilarParameters(method, methodSymbol, allowOptionalParameters: false)) + if (OverloadFinder.HasSimilarParameters(method, methodSymbol, allowOptionalParameters: false, default(ReadOnlySpan))) return true; - if (CancellationTokenSymbol is not null && OverloadFinder.HasSimilarParameters(method, methodSymbol, allowOptionalParameters: false, CancellationTokenSymbol)) + if (CancellationTokenSymbol is not null && OverloadFinder.HasSimilarParameters(method, methodSymbol, allowOptionalParameters: false, [CancellationTokenSymbol])) return true; } diff --git a/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs b/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs index dbd71a21..9be3a32f 100644 --- a/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs @@ -55,7 +55,7 @@ private static void AnalyzeThrow(OperationAnalysisContext context, OverloadFinde var argument = objectCreationOperation.Arguments.FirstOrDefault(arg => IsPotentialParameter(arg?.Parameter, exceptionSymbol)); if (argument is null) { - if (overloadFinder.HasOverloadWithAdditionalParameterOfType(objectCreationOperation.Constructor, exceptionSymbol)) + if (overloadFinder.HasOverloadWithAdditionalParameterOfType(objectCreationOperation, options: default, [exceptionSymbol])) { context.ReportDiagnostic(Rule, objectCreationOperation); } diff --git a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs index a34712da..a385b657 100644 --- a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs @@ -111,7 +111,7 @@ private bool HasAnOverloadWithCancellationToken(OperationAnalysisContext context return true; var allowOptionalParameters = context.Options.GetConfigurationValue(operation, "MA0032.allowOverloadsWithOptionalParameters", defaultValue: false); - var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, allowOptionalParameters, CancellationTokenSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation, new OverloadOptions(AllowOptionalParameters: allowOptionalParameters), [CancellationTokenSymbol]); if (overload is not null) { for (var i = 0; i < overload.Parameters.Length; i++) diff --git a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasTimeProviderAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasTimeProviderAnalyzer.cs index db7c11ee..527764e9 100644 --- a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasTimeProviderAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasTimeProviderAnalyzer.cs @@ -75,7 +75,7 @@ private bool HasAnOverloadWithTimeProvider(IInvocationOperation operation, [NotN if (IsArgumentImplicitlyDeclared(operation, TimeProviderSymbol, out parameterInfo)) return true; - var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, allowOptionalParameters: true, TimeProviderSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation, new OverloadOptions(IncludeObsoleteMembers: false, AllowOptionalParameters: true), [TimeProviderSymbol]); if (overload is not null) { for (var i = 0; i < overload.Parameters.Length; i++) diff --git a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs index adce8883..e17245c9 100644 --- a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs @@ -59,7 +59,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) return; } - var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, allowOptionalParameters: false, _cultureSensitiveContext.FormatProviderSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation, new OverloadOptions(IncludeObsoleteMembers: false, AllowOptionalParameters: false), [_cultureSensitiveContext.FormatProviderSymbol]); if (overload is not null) { if (_cultureSensitiveContext.IsCultureSensitiveOperation(operation, CultureSensitiveOptions.None)) @@ -71,7 +71,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) } var targetMethodType = operation.TargetMethod.ContainingType; - if (targetMethodType.IsNumberType() && _cultureSensitiveContext.NumberStyleSymbol is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.NumberStyleSymbol)) + if (targetMethodType.IsNumberType() && _cultureSensitiveContext.NumberStyleSymbol is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [_cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.NumberStyleSymbol])) { context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); return; @@ -80,14 +80,14 @@ public void AnalyzeInvocation(OperationAnalysisContext context) var isDateTime = targetMethodType.IsDateTime() || targetMethodType.IsEqualToAny(_cultureSensitiveContext.DateTimeOffsetSymbol, _cultureSensitiveContext.DateOnlySymbol, _cultureSensitiveContext.TimeOnlySymbol); if (isDateTime) { - if (_cultureSensitiveContext.DateTimeStyleSymbol is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.DateTimeStyleSymbol)) + if (_cultureSensitiveContext.DateTimeStyleSymbol is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [_cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.DateTimeStyleSymbol])) { context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); return; } } - if (operation.Arguments.IsEmpty && targetMethodType.Implements(_cultureSensitiveContext.SystemIFormattableSymbol) && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _cultureSensitiveContext.FormatProviderSymbol, compilation.GetSpecialType(SpecialType.System_String))) + if (operation.Arguments.IsEmpty && targetMethodType.Implements(_cultureSensitiveContext.SystemIFormattableSymbol) && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [_cultureSensitiveContext.FormatProviderSymbol, compilation.GetSpecialType(SpecialType.System_String)])) { context.ReportDiagnostic(Rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); return; @@ -96,7 +96,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) if (_cultureSensitiveContext.CultureInfoSymbol is not null && !operation.HasArgumentOfType(_cultureSensitiveContext.CultureInfoSymbol)) { - var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, includeObsoleteMethods: false, allowOptionalParameters: false, _cultureSensitiveContext.CultureInfoSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation, new OverloadOptions(IncludeObsoleteMembers: false, AllowOptionalParameters: false), [_cultureSensitiveContext.CultureInfoSymbol]); if (overload is not null) { if (_cultureSensitiveContext.IsCultureSensitiveOperation(operation, CultureSensitiveOptions.None)) diff --git a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs index c04e42f3..5def1565 100644 --- a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs @@ -89,8 +89,8 @@ public void AnalyzeConstructor(OperationAnalysisContext ctx) if (method is null) return; - if ((EqualityComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, EqualityComparerStringType)) || - (ComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, ComparerStringType))) + if ((EqualityComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, options: default, [EqualityComparerStringType])) || + (ComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, options: default, [ComparerStringType]))) { ctx.ReportDiagnostic(Rule, operation); } @@ -126,8 +126,8 @@ public void AnalyzeInvocation(OperationAnalysisContext ctx) if (operation.IsImplicit && IsQueryOperator(operation) && ctx.Options.GetConfigurationValue(operation, Rule.Id + ".exclude_query_operator_syntaxes", defaultValue: false)) return; - if ((EqualityComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, operation, EqualityComparerStringType)) || - (ComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, operation, ComparerStringType))) + if ((EqualityComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [EqualityComparerStringType])) || + (ComparerStringType is not null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [ComparerStringType]))) { ctx.ReportDiagnostic(Rule, operation, DefaultDiagnosticInvocationReportOptions); return; diff --git a/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs index d1ccde79..dd1dc15f 100644 --- a/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs @@ -67,7 +67,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) return; // Check if there is an overload with a StringComparison - if (_overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _stringComparisonSymbol)) + if (_overloadFinder.HasOverloadWithAdditionalParameterOfType(operation, options: default, [_stringComparisonSymbol])) { if (IsNonCultureSensitiveMethod(operation)) { diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs index f2106efc..752da8f3 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs @@ -297,6 +297,23 @@ class Sample : System.IFormattable public override string ToString() => throw null; public string ToString(string format, System.IFormatProvider formatProvider) => throw null; } +"""; + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task ToString_WithIFormatProviderOverload_WithoutIFormattable() + { + var sourceCode = """ +_ = [|new Location().ToString()|]; + +class Location +{ + public override string ToString() => throw null; + public string ToString(System.IFormatProvider formatProvider) => throw null; +} """; await CreateProjectBuilder() .WithSourceCode(sourceCode)