From 5c57d08fe2a1625d1a259b86ffced375f207704e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Fri, 28 Apr 2023 17:23:36 -0400 Subject: [PATCH 1/2] Fix typo --- src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs index f0036f66c..74caf3803 100644 --- a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs @@ -109,7 +109,7 @@ 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 concret use-case is HashSet.Contains which has an extension method IEnumerable.Contains(value, comparer) + // A concrete use-case is HashSet.Contains which has an extension method IEnumerable.Contains(value, comparer) if (ISetType != null && method.ContainingType.IsOrImplements(ISetType)) return; From 6fe0e1956fa34bd9587ec76e0435d079993c6c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Fri, 28 Apr 2023 23:13:23 -0400 Subject: [PATCH 2/2] Performance improvements --- .../Internals/CompilationExtensions.cs | 2 + .../Internals/MethodSymbolExtensions.cs | 118 +---------- .../Internals/OverloadFinder.cs | 187 ++++++++++++++++++ .../Internals/SymbolExtensions.cs | 1 - .../Internals/TypeSymbolExtensions.cs | 5 + ...ConvertDateTimeToDateTimeOffsetAnalyzer.cs | 3 +- ...otUseBlockingCallInAsyncContextAnalyzer.cs | 74 ++++--- ...mplicitCultureSensitiveToStringAnalyzer.cs | 1 - ...CaughtExceptionAsInnerExceptionAnalyzer.cs | 20 +- ...houldNotChangeParameterDefaultsAnalyzer.cs | 1 - .../Rules/OptimizeStartsWithAnalyzer.cs | 1 - .../Rules/TaskInUsingAnalyzer.cs | 3 +- ...verloadThatHasCancellationTokenAnalyzer.cs | 8 +- .../Rules/UseIFormatProviderAnalyzer.cs | 10 +- .../Rules/UseStringComparerAnalyzer.cs | 12 +- .../Rules/UseStringComparisonAnalyzer.cs | 11 +- 16 files changed, 273 insertions(+), 184 deletions(-) create mode 100644 src/Meziantou.Analyzer/Internals/OverloadFinder.cs diff --git a/src/Meziantou.Analyzer/Internals/CompilationExtensions.cs b/src/Meziantou.Analyzer/Internals/CompilationExtensions.cs index f4d154376..9052e7a69 100644 --- a/src/Meziantou.Analyzer/Internals/CompilationExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/CompilationExtensions.cs @@ -6,6 +6,7 @@ namespace Meziantou.Analyzer; internal static class CompilationExtensions { +#if ROSLYN_3_8 public static ImmutableArray GetTypesByMetadataName(this Compilation compilation, string typeMetadataName) { var result = ImmutableArray.CreateBuilder(); @@ -30,6 +31,7 @@ public static ImmutableArray GetTypesByMetadataName(this Compi return result.ToImmutable(); } +#endif // Copy from https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs#L11-L68 /// diff --git a/src/Meziantou.Analyzer/Internals/MethodSymbolExtensions.cs b/src/Meziantou.Analyzer/Internals/MethodSymbolExtensions.cs index 0969bd507..62f3477e6 100644 --- a/src/Meziantou.Analyzer/Internals/MethodSymbolExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/MethodSymbolExtensions.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using System.Linq; +using System.Linq; using Microsoft.CodeAnalysis; namespace Meziantou.Analyzer; @@ -79,119 +78,4 @@ public static bool IsUnitTestMethod(this IMethodSymbol methodSymbol) return false; } - - public static bool HasOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - Compilation compilation, - params ITypeSymbol?[] additionalParameterTypes) - { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, compilation, additionalParameterTypes) != null; - } - - public static bool HasOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - IOperation currentOperation, - params ITypeSymbol?[] additionalParameterTypes) - { - if (currentOperation.SemanticModel == null) - return false; - - return FindOverloadWithAdditionalParameterOfType(methodSymbol, currentOperation.SemanticModel.Compilation, syntaxNode: currentOperation.Syntax, includeObsoleteMethods: false, additionalParameterTypes) != null; - } - - private static IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - Compilation compilation, - params ITypeSymbol?[] additionalParameterTypes) - { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, compilation, includeObsoleteMethods: false, additionalParameterTypes); - } - - public static IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - Compilation compilation, - bool includeObsoleteMethods, - params ITypeSymbol?[] additionalParameterTypes) - { - return FindOverloadWithAdditionalParameterOfType(methodSymbol, compilation, syntaxNode: null, includeObsoleteMethods, additionalParameterTypes); - } - - public static IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - IOperation operation, - bool includeObsoleteMethods, - params ITypeSymbol?[] additionalParameterTypes) - { - if (operation.SemanticModel == null) - return null; - - return FindOverloadWithAdditionalParameterOfType(methodSymbol, operation.SemanticModel.Compilation, operation.Syntax, includeObsoleteMethods, additionalParameterTypes); - } - - public static IMethodSymbol? FindOverloadWithAdditionalParameterOfType( - this IMethodSymbol methodSymbol, - Compilation compilation, - SyntaxNode? syntaxNode, - bool includeObsoleteMethods, - params ITypeSymbol?[] additionalParameterTypes) - { - if (additionalParameterTypes == null) - return null; - - additionalParameterTypes = additionalParameterTypes.Where(type => type != null).ToArray(); - if (additionalParameterTypes.Length == 0) - return null; - - ImmutableArray members; - if (syntaxNode != null) - { - var semanticModel = compilation.GetSemanticModel(syntaxNode.SyntaxTree); - members = semanticModel.LookupSymbols(syntaxNode.GetLocation().SourceSpan.End, methodSymbol.ContainingType, methodSymbol.Name, includeReducedExtensionMethods: true); - } - else - { - members = methodSymbol.ContainingType.GetMembers(methodSymbol.Name); - } - - return members.OfType() - .FirstOrDefault(member => (includeObsoleteMethods || !member.IsObsolete(compilation)) && HasSimilarParameters(methodSymbol, member, additionalParameterTypes)); - } - - public static bool IsObsolete(this IMethodSymbol methodSymbol, Compilation compilation) - { - var obsoleteAttribute = compilation?.GetBestTypeByMetadataName("System.ObsoleteAttribute"); - if (obsoleteAttribute == null) - return false; - - return methodSymbol.HasAttribute(obsoleteAttribute); - } - - public static bool HasSimilarParameters(this IMethodSymbol methodSymbol, IMethodSymbol otherMethod, params ITypeSymbol?[] additionalParameterTypes) - { - if (methodSymbol.IsEqualTo(otherMethod)) - return false; - - if (additionalParameterTypes.Any(type => type == null)) - { - additionalParameterTypes = additionalParameterTypes.WhereNotNull().ToArray(); - } - - var methodParameters = methodSymbol.Parameters.Select(p => p.Type).ToList(); - var otherMethodParameters = otherMethod.Parameters.Select(p => p.Type).ToList(); - - if (otherMethodParameters.Count - methodParameters.Count != additionalParameterTypes.Length) - return false; - - foreach (var param in methodParameters) - { - otherMethodParameters.Remove(param); - } - - foreach (var param in additionalParameterTypes) - { - otherMethodParameters.Remove(param!); - } - - return otherMethodParameters.Count == 0; - } } diff --git a/src/Meziantou.Analyzer/Internals/OverloadFinder.cs b/src/Meziantou.Analyzer/Internals/OverloadFinder.cs new file mode 100644 index 000000000..cb8c39f92 --- /dev/null +++ b/src/Meziantou.Analyzer/Internals/OverloadFinder.cs @@ -0,0 +1,187 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Meziantou.Analyzer.Internals; +internal sealed class OverloadFinder +{ + private readonly ITypeSymbol? _obsoleteSymbol; + private readonly Compilation _compilation; + + public OverloadFinder(Compilation compilation) + { + _compilation = compilation; + _obsoleteSymbol = compilation.GetBestTypeByMetadataName("System.ObsoleteAttribute"); + } + + public bool HasOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + params ITypeSymbol[] additionalParameterTypes) + { + return FindOverloadWithAdditionalParameterOfType(methodSymbol, additionalParameterTypes) != null; + } + + public bool HasOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + IOperation currentOperation, + params ITypeSymbol[] additionalParameterTypes) + { + if (currentOperation.SemanticModel == null) + return false; + + return FindOverloadWithAdditionalParameterOfType(methodSymbol, syntaxNode: currentOperation.Syntax, includeObsoleteMethods: false, additionalParameterTypes) != null; + } + + private IMethodSymbol? FindOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + params ITypeSymbol[] additionalParameterTypes) + { + return FindOverloadWithAdditionalParameterOfType(methodSymbol, includeObsoleteMethods: false, additionalParameterTypes); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + bool includeObsoleteMethods, + params ITypeSymbol[] additionalParameterTypes) + { + return FindOverloadWithAdditionalParameterOfType(methodSymbol, syntaxNode: null, includeObsoleteMethods, additionalParameterTypes); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + IOperation operation, + bool includeObsoleteMethods, + params ITypeSymbol[] additionalParameterTypes) + { + if (operation.SemanticModel == null) + return null; + + return FindOverloadWithAdditionalParameterOfType(methodSymbol, operation.Syntax, includeObsoleteMethods, additionalParameterTypes); + } + + public IMethodSymbol? FindOverloadWithAdditionalParameterOfType( + IMethodSymbol methodSymbol, + SyntaxNode? syntaxNode, + bool includeObsoleteMethods, + params ITypeSymbol[] additionalParameterTypes) + { + if (additionalParameterTypes == null) + return null; + + additionalParameterTypes = additionalParameterTypes.Where(type => type != null).ToArray(); + if (additionalParameterTypes.Length == 0) + return null; + + ImmutableArray members; + if (syntaxNode != null) + { + var semanticModel = _compilation.GetSemanticModel(syntaxNode.SyntaxTree); + members = semanticModel.LookupSymbols(syntaxNode.GetLocation().SourceSpan.End, methodSymbol.ContainingType, methodSymbol.Name, includeReducedExtensionMethods: true); + } + else + { + members = methodSymbol.ContainingType.GetMembers(methodSymbol.Name); + } + + foreach (var member in members) + { + if (member is IMethodSymbol method) + { + if (!includeObsoleteMethods && IsObsolete(method)) + continue; + + if (HasSimilarParameters(methodSymbol, method, additionalParameterTypes)) + return method; + } + } + + return null; + } + + public static bool HasSimilarParameters(IMethodSymbol method, IMethodSymbol otherMethod, params ITypeSymbol[] additionalParameterTypes) + { + if (method.IsEqualTo(otherMethod)) + return false; + + if (otherMethod.Parameters.Length - method.Parameters.Length != additionalParameterTypes.Length) + return false; + + // Most of the time, an overload has the same order for the parameters + { + int i = 0, j = 0; + var additionalParameterIndex = 0; + while (i < method.Parameters.Length && j < method.Parameters.Length) + { + var methodParameter = method.Parameters[i]; + var otherMethodParameter = otherMethod.Parameters[j]; + + if (methodParameter.IsEqualTo(otherMethodParameter)) + { + i++; + j++; + continue; + } + + if (additionalParameterIndex == additionalParameterTypes.Length) + break; + + var additionalParameter = additionalParameterTypes[additionalParameterIndex]; + if (methodParameter.Type.IsEqualTo(additionalParameter)) + { + i++; + continue; + } + + if (otherMethodParameter.Type.IsEqualTo(additionalParameter)) + { + j++; + continue; + } + + break; + } + + if (i == method.Parameters.Length && j == otherMethod.Parameters.Length) + return true; + } + + // Slower search, allows to find overload with different parameter order + { + var otherMethodParameters = otherMethod.Parameters; + + foreach (var param in method.Parameters) + { + for (var i = 0; i < otherMethodParameters.Length; i++) + { + if (otherMethodParameters[i].Type.IsEqualTo(param.Type)) + { + otherMethodParameters = otherMethodParameters.RemoveAt(i); + break; + } + } + } + + foreach (var paramType in additionalParameterTypes) + { + for (var i = 0; i < otherMethodParameters.Length; i++) + { + if (otherMethodParameters[i].Type.IsEqualTo(paramType)) + { + otherMethodParameters = otherMethodParameters.RemoveAt(i); + break; + } + } + } + + return otherMethodParameters.Length == 0; + } + } + + private bool IsObsolete(IMethodSymbol methodSymbol) + { + if (_obsoleteSymbol == null) + return false; + + return methodSymbol.HasAttribute(_obsoleteSymbol); + } +} diff --git a/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs b/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs index 9be87b0f8..5d054acc6 100644 --- a/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; diff --git a/src/Meziantou.Analyzer/Internals/TypeSymbolExtensions.cs b/src/Meziantou.Analyzer/Internals/TypeSymbolExtensions.cs index 367ec52b2..caf73e6fa 100644 --- a/src/Meziantou.Analyzer/Internals/TypeSymbolExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/TypeSymbolExtensions.cs @@ -60,6 +60,11 @@ public static bool IsOrImplements(this ITypeSymbol symbol, ITypeSymbol? interfac if (attributeType == null) return null; + if (attributeType.IsSealed) + { + inherits = false; + } + foreach (var attribute in symbol.GetAttributes()) { if (inherits) diff --git a/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs index df2753046..4e8c80715 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs index aeee904b0..837698489 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs @@ -60,12 +60,15 @@ private sealed class Context { private static readonly Version Version6 = new(6, 0, 0, 0); - private readonly INamedTypeSymbol[] _taskLikeSymbols; + private readonly AwaitableTypes _awaitableTypes; + private readonly INamedTypeSymbol[] _taskAwaiterLikeSymbols; private readonly ConcurrentHashSet _symbolsWithNoAsyncOverloads = new(SymbolEqualityComparer.Default); public Context(Compilation compilation) { + _awaitableTypes = new AwaitableTypes(compilation); + var consoleSymbol = compilation.GetBestTypeByMetadataName("System.Console"); if (consoleSymbol != null) { @@ -78,6 +81,7 @@ public Context(Compilation compilation) ProcessSymbol = compilation.GetBestTypeByMetadataName("System.Diagnostics.Process"); CancellationTokenSymbol = compilation.GetBestTypeByMetadataName("System.Threading.CancellationToken"); + ObsoleteAttributeSymbol = compilation.GetBestTypeByMetadataName("System.ObsoleteAttribute"); TaskSymbol = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); TaskOfTSymbol = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task`1"); @@ -101,13 +105,6 @@ public Context(Compilation compilation) ServiceProviderServiceExtensions_CreateAsyncScopeSymbol = ServiceProviderServiceExtensionsSymbol.GetMembers("CreateAsyncScope").FirstOrDefault(); } - var taskLikeSymbols = new List(4); - taskLikeSymbols.AddIfNotNull(TaskSymbol); - taskLikeSymbols.AddIfNotNull(TaskOfTSymbol); - taskLikeSymbols.AddIfNotNull(ValueTaskSymbol); - taskLikeSymbols.AddIfNotNull(ValueTaskOfTSymbol); - _taskLikeSymbols = taskLikeSymbols.ToArray(); - var taskAwaiterLikeSymbols = new List(4); taskAwaiterLikeSymbols.AddIfNotNull(TaskAwaiterSymbol); taskAwaiterLikeSymbols.AddIfNotNull(TaskAwaiterOfTSymbol); @@ -119,6 +116,7 @@ public Context(Compilation compilation) private ISymbol? ProcessSymbol { get; } private ISymbol[] ConsoleErrorAndOutSymbols { get; } private INamedTypeSymbol? CancellationTokenSymbol { get; } + private INamedTypeSymbol? ObsoleteAttributeSymbol { get; } private INamedTypeSymbol? ServiceProviderServiceExtensionsSymbol { get; } private ISymbol? ServiceProviderServiceExtensions_CreateScopeSymbol { get; } private ISymbol? ServiceProviderServiceExtensions_CreateAsyncScopeSymbol { get; } @@ -165,7 +163,7 @@ private bool HasAsyncEquivalent(Compilation compilation, IInvocationOperation op data = null; var targetMethod = operation.TargetMethod; - if (IsTaskSymbol(targetMethod.ReturnType)) + if (_awaitableTypes.IsAwaitable(targetMethod.ReturnType, operation.SemanticModel!, operation.Syntax.SpanStart)) return false; // Process.WaitForExit => Skip because the async method is not equivalent https://github.com/dotnet/runtime/issues/42556 @@ -242,27 +240,41 @@ private bool HasAsyncEquivalent(Compilation compilation, IInvocationOperation op { var position = operation.Syntax.GetLocation().SourceSpan.End; - var potentionalMethods = new List(); - potentionalMethods.AddRange(operation.SemanticModel!.LookupSymbols(position, targetMethod.ContainingType, name: targetMethod.Name, includeReducedExtensionMethods: true)); - if (!targetMethod.Name.EndsWith("Async", StringComparison.Ordinal)) + var result = ProcessSymbols(operation.SemanticModel!.LookupSymbols(position, targetMethod.ContainingType, name: targetMethod.Name, includeReducedExtensionMethods: true)); + if (result != null) { - potentionalMethods.AddRange(operation.SemanticModel.LookupSymbols(position, targetMethod.ContainingType, name: targetMethod.Name + "Async", includeReducedExtensionMethods: true)); + data = result; + return true; } - foreach (var potentialMethod in potentionalMethods) + if (!targetMethod.Name.EndsWith("Async", StringComparison.Ordinal)) { - if (IsPotentialMember(compilation, targetMethod, potentialMethod)) + result = ProcessSymbols(operation.SemanticModel!.LookupSymbols(position, targetMethod.ContainingType, name: targetMethod.Name + "Async", includeReducedExtensionMethods: true)); + if (result != null) { - data = new($"Use '{potentialMethod.Name}' instead of '{targetMethod.Name}'", DoNotUseBlockingCallInAsyncContextData.Overload, potentialMethod.Name); + data = result; return true; } } + + DiagnosticData? ProcessSymbols(ImmutableArray potentialMethods) + { + foreach (var potentialMethod in potentialMethods) + { + if (IsPotentialMember(operation, targetMethod, potentialMethod)) + { + return new($"Use '{potentialMethod.Name}' instead of '{targetMethod.Name}'", DoNotUseBlockingCallInAsyncContextData.Overload, potentialMethod.Name); + } + } + + return null; + } } return false; } - private bool IsPotentialMember(Compilation compilation, IMethodSymbol method, ISymbol potentialAsyncSymbol) + private bool IsPotentialMember(IInvocationOperation operation, IMethodSymbol method, ISymbol potentialAsyncSymbol) { if (potentialAsyncSymbol.IsEqualTo(method)) return false; @@ -272,30 +284,16 @@ private bool IsPotentialMember(Compilation compilation, IMethodSymbol method, IS if (method.IsStatic && !methodSymbol.IsStatic) return false; - if (!IsTaskSymbol(methodSymbol.ReturnType)) + if (!_awaitableTypes.IsAwaitable(methodSymbol.ReturnType, operation.SemanticModel!, operation.Syntax.SpanStart)) return false; - if (methodSymbol.IsObsolete(compilation)) + if (methodSymbol.HasAttribute(ObsoleteAttributeSymbol)) return false; - if (!method.HasSimilarParameters(methodSymbol) && !method.HasSimilarParameters(methodSymbol, CancellationTokenSymbol)) - return false; - - return true; - } - - return false; - } - - private bool IsTaskSymbol(ITypeSymbol? symbol) - { - if (symbol is null) - return false; + if (OverloadFinder.HasSimilarParameters(method, methodSymbol)) + return true; - var originalDefinition = symbol.OriginalDefinition; - foreach (var taskLikeSymbol in _taskLikeSymbols) - { - if (originalDefinition.IsEqualTo(taskLikeSymbol)) + if (CancellationTokenSymbol != null && OverloadFinder.HasSimilarParameters(method, methodSymbol, CancellationTokenSymbol)) return true; } @@ -337,11 +335,11 @@ private void ReportDiagnosticIfNeeded(OperationAnalysisContext context, Immutabl private bool IsAsyncContext(IOperation operation, CancellationToken cancellationToken) { - // lamdba, delegate, method, local function + // lambda, delegate, method, local function // Check if returns Task or async void if (operation.SemanticModel!.GetEnclosingSymbol(operation.Syntax.SpanStart, cancellationToken) is IMethodSymbol methodSymbol) { - return methodSymbol.IsAsync || methodSymbol.ReturnType.OriginalDefinition.IsEqualToAny(TaskSymbol, TaskOfTSymbol, ValueTaskSymbol, ValueTaskOfTSymbol); + return methodSymbol.IsAsync || _awaitableTypes.IsAwaitable(methodSymbol.ReturnType, operation.SemanticModel, operation.Syntax.SpanStart); } return false; diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseImplicitCultureSensitiveToStringAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseImplicitCultureSensitiveToStringAnalyzer.cs index 60fc30f39..74c8e2fd1 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotUseImplicitCultureSensitiveToStringAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotUseImplicitCultureSensitiveToStringAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Data; using System.Diagnostics; using Meziantou.Analyzer.Configurations; using Meziantou.Analyzer.Internals; diff --git a/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs b/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs index b1512de1b..ab06c9d9b 100644 --- a/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/EmbedCaughtExceptionAsInnerExceptionAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Linq; +using Meziantou.Analyzer.Internals; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -26,20 +27,23 @@ public override void Initialize(AnalysisContext context) context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterOperationAction(AnalyzeThrow, OperationKind.Throw); + context.RegisterCompilationStartAction(context => + { + var overloadFinder = new OverloadFinder(context.Compilation); + var exceptionSymbol = context.Compilation.GetBestTypeByMetadataName("System.Exception"); + if (exceptionSymbol == null) + return; + + context.RegisterOperationAction(context => AnalyzeThrow(context, overloadFinder, exceptionSymbol), OperationKind.Throw); + }); } - private static void AnalyzeThrow(OperationAnalysisContext context) + private static void AnalyzeThrow(OperationAnalysisContext context, OverloadFinder overloadFinder, INamedTypeSymbol exceptionSymbol) { var operation = (IThrowOperation)context.Operation; if (operation.Exception == null) return; - var compilation = context.Compilation; - var exceptionSymbol = compilation.GetBestTypeByMetadataName("System.Exception"); - if (exceptionSymbol == null) - return; - var catchOperation = operation.Ancestors().OfType().FirstOrDefault(); if (catchOperation == null) return; @@ -52,7 +56,7 @@ private static void AnalyzeThrow(OperationAnalysisContext context) var argument = objectCreationOperation.Arguments.FirstOrDefault(arg => IsPotentialParameter(arg?.Parameter, exceptionSymbol)); if (argument == null) { - if (objectCreationOperation.Constructor.HasOverloadWithAdditionalParameterOfType(context.Compilation, exceptionSymbol)) + if (overloadFinder.HasOverloadWithAdditionalParameterOfType(objectCreationOperation.Constructor, exceptionSymbol)) { context.ReportDiagnostic(s_rule, objectCreationOperation); } diff --git a/src/Meziantou.Analyzer/Rules/MethodOverridesShouldNotChangeParameterDefaultsAnalyzer.cs b/src/Meziantou.Analyzer/Rules/MethodOverridesShouldNotChangeParameterDefaultsAnalyzer.cs index 16fb157aa..d547d4579 100644 --- a/src/Meziantou.Analyzer/Rules/MethodOverridesShouldNotChangeParameterDefaultsAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/MethodOverridesShouldNotChangeParameterDefaultsAnalyzer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/Meziantou.Analyzer/Rules/OptimizeStartsWithAnalyzer.cs b/src/Meziantou.Analyzer/Rules/OptimizeStartsWithAnalyzer.cs index bb957b2ca..d017089f9 100644 --- a/src/Meziantou.Analyzer/Rules/OptimizeStartsWithAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/OptimizeStartsWithAnalyzer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Immutable; using System.Linq; -using System.Reflection.Metadata; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; diff --git a/src/Meziantou.Analyzer/Rules/TaskInUsingAnalyzer.cs b/src/Meziantou.Analyzer/Rules/TaskInUsingAnalyzer.cs index 6c8fe6dde..35643d377 100644 --- a/src/Meziantou.Analyzer/Rules/TaskInUsingAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/TaskInUsingAnalyzer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; diff --git a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs index 1d0f47364..a7f7c7e31 100644 --- a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using Meziantou.Analyzer.Internals; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -80,9 +81,12 @@ private sealed class AnalyzerContext { private readonly ConcurrentDictionary<(ITypeSymbol Symbol, int MaxDepth), List?> _membersByType = new(); + private readonly OverloadFinder _overloadFinder; + public AnalyzerContext(Compilation compilation) { Compilation = compilation; + _overloadFinder = new OverloadFinder(compilation); CancellationTokenSymbol = compilation.GetBestTypeByMetadataName("System.Threading.CancellationToken")!; // Not nullable as it is checked before registering the Operation actions CancellationTokenSourceSymbol = compilation.GetBestTypeByMetadataName("System.Threading.CancellationTokenSource"); TaskSymbol = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); @@ -119,7 +123,7 @@ private bool HasAnOverloadWithCancellationToken(IInvocationOperation operation, if (IsArgumentImplicitlyDeclared(operation, CancellationTokenSymbol, out parameterIndex, out parameterName)) return true; - var overload = operation.TargetMethod.FindOverloadWithAdditionalParameterOfType(operation, includeObsoleteMethods: false, CancellationTokenSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, CancellationTokenSymbol); if (overload != null) { for (var i = 0; i < overload.Parameters.Length; i++) @@ -220,7 +224,7 @@ public void AnalyzeLoop(OperationAnalysisContext context) var availableCancellationTokens = FindCancellationTokens(op, context.CancellationToken); if (availableCancellationTokens.Length > 0) { - var properties = CreateProperties(availableCancellationTokens, -1, null); + var properties = CreateProperties(availableCancellationTokens, parameterIndex: -1, parameterName: null); context.ReportDiagnostic(s_flowCancellationTokenInAwaitForEachRuleWhenACancellationTokenIsAvailableRule, properties, op.Collection, string.Join(", ", availableCancellationTokens)); } else diff --git a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs index c6adff9f2..60132d0bd 100644 --- a/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseIFormatProviderAnalyzer.cs @@ -36,10 +36,12 @@ public override void Initialize(AnalysisContext context) private sealed class AnalyzerContext { private readonly CultureSensitiveFormattingContext _cultureSensitiveContext; + private readonly OverloadFinder _overloadFinder; public AnalyzerContext(Compilation compilation) { _cultureSensitiveContext = new CultureSensitiveFormattingContext(compilation); + _overloadFinder = new OverloadFinder(compilation); } public void AnalyzeInvocation(OperationAnalysisContext context) @@ -63,7 +65,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) return; } - var overload = operation.TargetMethod.FindOverloadWithAdditionalParameterOfType(operation, includeObsoleteMethods: false, _cultureSensitiveContext.FormatProviderSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, includeObsoleteMethods: false, _cultureSensitiveContext.FormatProviderSymbol); if (overload != null) { context.ReportDiagnostic(s_rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); @@ -71,7 +73,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) } var targetMethodType = operation.TargetMethod.ContainingType; - if (targetMethodType.IsNumberType() && operation.TargetMethod.HasOverloadWithAdditionalParameterOfType(operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.NumberStyleSymbol)) + if (targetMethodType.IsNumberType() && _cultureSensitiveContext.NumberStyleSymbol != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.NumberStyleSymbol)) { context.ReportDiagnostic(s_rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); return; @@ -80,7 +82,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) var isDateTime = targetMethodType.IsDateTime() || targetMethodType.IsEqualToAny(_cultureSensitiveContext.DateTimeOffsetSymbol, _cultureSensitiveContext.DateOnlySymbol, _cultureSensitiveContext.TimeOnlySymbol); if (isDateTime) { - if (operation.TargetMethod.HasOverloadWithAdditionalParameterOfType(operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.DateTimeStyleSymbol)) + if (_cultureSensitiveContext.DateTimeStyleSymbol != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, _cultureSensitiveContext.FormatProviderSymbol, _cultureSensitiveContext.DateTimeStyleSymbol)) { context.ReportDiagnostic(s_rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.FormatProviderSymbol.ToDisplayString()); return; @@ -90,7 +92,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) if (_cultureSensitiveContext.CultureInfoSymbol != null && !operation.HasArgumentOfType(_cultureSensitiveContext.CultureInfoSymbol)) { - var overload = operation.TargetMethod.FindOverloadWithAdditionalParameterOfType(context.Compilation, includeObsoleteMethods: false, _cultureSensitiveContext.CultureInfoSymbol); + var overload = _overloadFinder.FindOverloadWithAdditionalParameterOfType(operation.TargetMethod, includeObsoleteMethods: false, _cultureSensitiveContext.CultureInfoSymbol); if (overload != null) { context.ReportDiagnostic(s_rule, operation, operation.TargetMethod.Name, _cultureSensitiveContext.CultureInfoSymbol.ToDisplayString()); diff --git a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs index 74caf3803..261857bbc 100644 --- a/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStringComparerAnalyzer.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Linq; using Meziantou.Analyzer.Configurations; +using Meziantou.Analyzer.Internals; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; @@ -69,8 +70,11 @@ public override void Initialize(AnalysisContext context) private sealed class AnalyzerContext { + private readonly OverloadFinder _overloadFinder; + public AnalyzerContext(Compilation compilation) { + _overloadFinder = new OverloadFinder(compilation); EqualityComparerStringType = GetIEqualityComparerString(compilation); ComparerStringType = GetIComparerString(compilation); EnumerableType = compilation.GetBestTypeByMetadataName("System.Linq.Enumerable"); @@ -92,8 +96,8 @@ public void AnalyzeConstructor(OperationAnalysisContext ctx) if (method == null) return; - if (method.HasOverloadWithAdditionalParameterOfType(ctx.Compilation, EqualityComparerStringType) || - method.HasOverloadWithAdditionalParameterOfType(ctx.Compilation, ComparerStringType)) + if ((EqualityComparerStringType != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, EqualityComparerStringType)) || + (ComparerStringType != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, ComparerStringType))) { ctx.ReportDiagnostic(s_rule, operation); } @@ -119,8 +123,8 @@ public void AnalyzeInvocation(OperationAnalysisContext ctx) if (operation.IsImplicit && IsQueryOperator(operation) && ctx.Options.GetConfigurationValue(operation, s_rule.Id + ".exclude_query_operator_syntaxes", defaultValue: false)) return; - if (method.HasOverloadWithAdditionalParameterOfType(operation, EqualityComparerStringType) || - method.HasOverloadWithAdditionalParameterOfType(operation, ComparerStringType)) + if ((EqualityComparerStringType != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, operation, EqualityComparerStringType)) || + (ComparerStringType != null && _overloadFinder.HasOverloadWithAdditionalParameterOfType(method, operation, ComparerStringType))) { ctx.ReportDiagnostic(s_rule, operation); return; diff --git a/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs index 433679b6a..454553710 100644 --- a/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStringComparisonAnalyzer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using Meziantou.Analyzer.Internals; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -36,10 +37,14 @@ public override void Initialize(AnalysisContext context) context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + context.RegisterCompilationStartAction(context => + { + var overloadFinder = new OverloadFinder(context.Compilation); + context.RegisterOperationAction(context => AnalyzeInvocation(context, overloadFinder), OperationKind.Invocation); + }); } - private static void AnalyzeInvocation(OperationAnalysisContext context) + private static void AnalyzeInvocation(OperationAnalysisContext context, OverloadFinder overloadFinder) { var stringComparisonType = context.Compilation.GetBestTypeByMetadataName("System.StringComparison"); var operation = (IInvocationOperation)context.Operation; @@ -55,7 +60,7 @@ private static void AnalyzeInvocation(OperationAnalysisContext context) return; // Check if there is an overload with a StringComparison - if (operation.TargetMethod.HasOverloadWithAdditionalParameterOfType(operation, stringComparisonType)) + if (overloadFinder.HasOverloadWithAdditionalParameterOfType(operation.TargetMethod, operation, stringComparisonType)) { if (IsNonCultureSensitiveMethod(operation)) {