From 9f9d9fac1b1c81bb56155636c59214aa494ea34e Mon Sep 17 00:00:00 2001 From: Collin Alpert Date: Mon, 30 Jan 2023 21:57:24 +0100 Subject: [PATCH] Analyzer: Prefer .Length/Count/IsEmpty over Any() (#6236) * Add analyzer and fixer. * Update src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicPreferLengthCountIsEmptyOverAnyFixer.vb * Extended type check for 'Length' and changed wording of message. * Updated wording on message descriptions. * Change 'GetReceiverType' to return an 'ITypeSymbol'. --- .../Core/ImmutableObjectMethodAnalyzer.cs | 5 +- ...rpPreferLengthCountIsEmptyOverAny.Fixer.cs | 97 ++++ .../Core/AnalyzerReleases.Unshipped.md | 1 + ...eCollectionOnAnImmutableCollectionValue.cs | 2 +- .../MicrosoftNetCoreAnalyzersResources.resx | 26 +- .../PreferLengthCountIsEmptyOverAny.Fixer.cs | 46 ++ ...PreferLengthCountIsEmptyOverAnyAnalyzer.cs | 156 ++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.de.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.es.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.it.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 40 ++ ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 40 ++ .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 40 ++ ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 40 ++ ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 40 ++ .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 1 + .../Performance/PreferCountOverAnyTests.cs | 448 +++++++++++++++++ .../Performance/PreferIsEmptyOverAnyTests.cs | 469 ++++++++++++++++++ .../Performance/PreferLengthOverAnyTests.cs | 258 ++++++++++ ...sicPreferLengthCountIsEmptyOverAnyFixer.vb | 132 +++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- .../Extensions/IOperationExtensions.cs | 12 +- 29 files changed, 2195 insertions(+), 12 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferLengthCountIsEmptyOverAny.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAny.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAnyAnalyzer.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferCountOverAnyTests.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferIsEmptyOverAnyTests.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferLengthOverAnyTests.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicPreferLengthCountIsEmptyOverAnyFixer.vb diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/ImmutableObjectMethodAnalyzer.cs b/src/Microsoft.CodeAnalysis.Analyzers/Core/ImmutableObjectMethodAnalyzer.cs index 13b77c4591..bf1039ea94 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/ImmutableObjectMethodAnalyzer.cs +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/ImmutableObjectMethodAnalyzer.cs @@ -89,10 +89,9 @@ public static void AnalyzeInvocationForIgnoredReturnValue(OperationAnalysisConte return; } - INamedTypeSymbol? type = invocation.GetReceiverType(context.Compilation, beforeConversion: false, context.CancellationToken); - // If we're not in one of the known immutable types, quit - if (type is not null && type.GetBaseTypesAndThis().Any(immutableTypeSymbols.Contains)) + if (invocation.GetReceiverType(context.Compilation, beforeConversion: false, context.CancellationToken) is INamedTypeSymbol type + && type.GetBaseTypesAndThis().Any(immutableTypeSymbols.Contains)) { context.ReportDiagnostic(invocation.CreateDiagnostic(DoNotIgnoreReturnValueDiagnosticRule, type.Name, methodName)); } diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferLengthCountIsEmptyOverAny.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferLengthCountIsEmptyOverAny.Fixer.cs new file mode 100644 index 0000000000..e0752883d2 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferLengthCountIsEmptyOverAny.Fixer.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.NetCore.Analyzers.Performance; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Microsoft.NetCore.CSharp.Analyzers.Performance +{ + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + public sealed class CSharpPreferLengthCountIsEmptyOverAnyFixer : PreferLengthCountIsEmptyOverAnyFixer + { + protected override SyntaxNode? ReplaceAnyWithIsEmpty(SyntaxNode root, SyntaxNode node) + { + if (node is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } invocation) + { + return null; + } + + var expression = memberAccess.Expression; + if (invocation.ArgumentList.Arguments.Count > 0) + { + expression = invocation.ArgumentList.Arguments[0].Expression; + } + + var newMemberAccess = MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression, + IdentifierName(PreferLengthCountIsEmptyOverAnyAnalyzer.IsEmptyText) + ); + if (invocation.Parent.IsKind(SyntaxKind.LogicalNotExpression)) + { + return root.ReplaceNode(invocation.Parent, newMemberAccess.WithTriviaFrom(invocation.Parent)); + } + + var negatedExpression = PrefixUnaryExpression( + SyntaxKind.LogicalNotExpression, + newMemberAccess + ); + + return root.ReplaceNode(invocation, negatedExpression.WithTriviaFrom(invocation)); + } + + protected override SyntaxNode? ReplaceAnyWithLength(SyntaxNode root, SyntaxNode node) + { + return ReplaceAnyWithPropertyCheck(root, node, PreferLengthCountIsEmptyOverAnyAnalyzer.LengthText); + } + + protected override SyntaxNode? ReplaceAnyWithCount(SyntaxNode root, SyntaxNode node) + { + return ReplaceAnyWithPropertyCheck(root, node, PreferLengthCountIsEmptyOverAnyAnalyzer.CountText); + } + + private static SyntaxNode? ReplaceAnyWithPropertyCheck(SyntaxNode root, SyntaxNode node, string propertyName) + { + if (node is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } invocation) + { + return null; + } + + var expression = memberAccess.Expression; + if (invocation.ArgumentList.Arguments.Count > 0) + { + // .Any() used like a normal static method and not like an extension method. + expression = invocation.ArgumentList.Arguments[0].Expression; + } + + static BinaryExpressionSyntax GetBinaryExpression(ExpressionSyntax expression, string member, SyntaxKind expressionKind) + { + return BinaryExpression( + expressionKind, + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression, + IdentifierName(member) + ), + LiteralExpression( + SyntaxKind.NumericLiteralExpression, + Literal(0) + ) + ); + } + + if (invocation.Parent.IsKind(SyntaxKind.LogicalNotExpression)) + { + var binaryExpression = GetBinaryExpression(expression, propertyName, SyntaxKind.EqualsExpression); + + return root.ReplaceNode(invocation.Parent, binaryExpression.WithTriviaFrom(invocation.Parent)); + } + + return root.ReplaceNode(invocation, GetBinaryExpression(expression, propertyName, SyntaxKind.NotEqualsExpression).WithTriviaFrom(invocation)); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 166c3c4e66..e65a2c2803 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -12,6 +12,7 @@ CA1856 | Performance | Error | ConstantExpectedAnalyzer, [Documentation](https:/ CA1857 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857) CA1858 | Performance | Info | UseStartsWithInsteadOfIndexOfComparisonWithZero, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858) CA1859 | Performance | Info | UseConcreteTypeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859) +CA1860 | Performance | Info | PreferLengthCountIsEmptyOverAnyAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860) CA2021 | Reliability | Info | DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/ImmutableCollections/DoNotCallToImmutableCollectionOnAnImmutableCollectionValue.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/ImmutableCollections/DoNotCallToImmutableCollectionOnAnImmutableCollectionValue.cs index 0af739ebc7..9e6cc03404 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/ImmutableCollections/DoNotCallToImmutableCollectionOnAnImmutableCollectionValue.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/ImmutableCollections/DoNotCallToImmutableCollectionOnAnImmutableCollectionValue.cs @@ -91,7 +91,7 @@ public override void Initialize(AnalysisContext context) return; } - var receiverType = invocation.GetReceiverType(operationContext.Compilation, beforeConversion: true, cancellationToken: operationContext.CancellationToken); + var receiverType = (INamedTypeSymbol?)invocation.GetReceiverType(operationContext.Compilation, beforeConversion: true, cancellationToken: operationContext.CancellationToken); if (receiverType != null && receiverType.DerivesFromOrImplementsAnyConstructionOf(immutableCollectionType)) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 586e290212..ae08576fc7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2013,4 +2013,28 @@ Widening and user defined conversions are not supported with generic types. Change type of parameter '{0}' from '{1}' to '{2}' for improved performance - + + Use 'Length' check instead of 'Any()' + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + + Use 'Count' check instead of 'Any()' + + + Avoid using 'Enumerable.Any()' extension method + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + Use 'IsEmpty' check instead of 'Any()' + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAny.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAny.Fixer.cs new file mode 100644 index 0000000000..5cc2e52fc8 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAny.Fixer.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + public abstract class PreferLengthCountIsEmptyOverAnyFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(PreferLengthCountIsEmptyOverAnyAnalyzer.RuleId); + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + + foreach (var diagnostic in context.Diagnostics) + { + var (newRoot, codeFixTitle) = diagnostic.Properties[PreferLengthCountIsEmptyOverAnyAnalyzer.DiagnosticPropertyKey] switch + { + PreferLengthCountIsEmptyOverAnyAnalyzer.IsEmptyText => (ReplaceAnyWithIsEmpty(root, node), MicrosoftNetCoreAnalyzersResources.PreferIsEmptyOverAnyCodeFixTitle), + PreferLengthCountIsEmptyOverAnyAnalyzer.LengthText => (ReplaceAnyWithLength(root, node), MicrosoftNetCoreAnalyzersResources.PreferLengthOverAnyCodeFixTitle), + PreferLengthCountIsEmptyOverAnyAnalyzer.CountText => (ReplaceAnyWithCount(root, node), MicrosoftNetCoreAnalyzersResources.PreferCountOverAnyCodeFixTitle), + _ => throw new NotSupportedException() + }; + if (newRoot is null) + { + continue; + } + + var codeAction = CodeAction.Create(codeFixTitle, _ => Task.FromResult(context.Document.WithSyntaxRoot(newRoot)), codeFixTitle); + context.RegisterCodeFix(codeAction, diagnostic); + } + } + + protected abstract SyntaxNode? ReplaceAnyWithIsEmpty(SyntaxNode root, SyntaxNode node); + protected abstract SyntaxNode? ReplaceAnyWithLength(SyntaxNode root, SyntaxNode node); + protected abstract SyntaxNode? ReplaceAnyWithCount(SyntaxNode root, SyntaxNode node); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAnyAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAnyAnalyzer.cs new file mode 100644 index 0000000000..e6350ffd4c --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferLengthCountIsEmptyOverAnyAnalyzer.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Immutable; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + /// + /// Prefer using 'IsEmpty' or comparing 'Count' / 'Length' property to 0 rather than using 'Any()', both for clarity and for performance. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferLengthCountIsEmptyOverAnyAnalyzer : DiagnosticAnalyzer + { + private const string AnyText = nameof(Enumerable.Any); + + internal const string IsEmptyText = nameof(ImmutableArray.IsEmpty); + internal const string LengthText = nameof(Array.Length); + internal const string CountText = nameof(ICollection.Count); + + internal const string RuleId = "CA1860"; + internal const string DiagnosticPropertyKey = nameof(DiagnosticPropertyKey); + + internal static readonly DiagnosticDescriptor IsEmptyDescriptor = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyTitle)), + CreateLocalizableResourceString(nameof(PreferIsEmptyOverAnyMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyDescription)), + isPortedFxCopRule: false, + isDataflowRule: false + ); + + internal static readonly DiagnosticDescriptor LengthDescriptor = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyTitle)), + CreateLocalizableResourceString(nameof(PreferLengthOverAnyMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyDescription)), + isPortedFxCopRule: false, + isDataflowRule: false + ); + + internal static readonly DiagnosticDescriptor CountDescriptor = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyTitle)), + CreateLocalizableResourceString(nameof(PreferCountOverAnyMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferLengthCountIsEmptyOverAnyDescription)), + isPortedFxCopRule: false, + isDataflowRule: false + ); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + LengthDescriptor, + CountDescriptor, + IsEmptyDescriptor + ); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(ctx => + { + var typeProvider = WellKnownTypeProvider.GetOrCreate(ctx.Compilation); + var iEnumerable = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsIEnumerable); + var iEnumerableOfT = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericIEnumerable1); + var anyMethod = typeProvider + .GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemLinqEnumerable) + ?.GetMembers(AnyText) + .OfType() + .FirstOrDefault(m => m.IsExtensionMethod && m.Parameters.Length == 1); + if (iEnumerable is not null && iEnumerableOfT is not null && anyMethod is not null) + { + ctx.RegisterOperationAction(c => OnInvocationAnalysis(c, iEnumerable, iEnumerableOfT, anyMethod), OperationKind.Invocation); + } + }); + } + + private static void OnInvocationAnalysis(OperationAnalysisContext context, INamedTypeSymbol iEnumerable, INamedTypeSymbol iEnumerableOfT, IMethodSymbol anyMethod) + { + var invocation = (IInvocationOperation)context.Operation; + var originalMethod = invocation.TargetMethod.OriginalDefinition; + if (originalMethod.MethodKind == MethodKind.ReducedExtension) + { + originalMethod = originalMethod.ReducedFrom; + } + + if (originalMethod.Equals(anyMethod, SymbolEqualityComparer.Default)) + { + var type = invocation.GetReceiverType(context.Compilation, beforeConversion: true, context.CancellationToken); + if (type is null || (!type.AllInterfaces.Contains(iEnumerable, SymbolEqualityComparer.Default) && !type.AllInterfaces.Contains(iEnumerableOfT))) + { + return; + } + + if (HasEligibleIsEmptyProperty(type)) + { + var properties = ImmutableDictionary.CreateBuilder(); + properties.Add(DiagnosticPropertyKey, IsEmptyText); + context.ReportDiagnostic(invocation.CreateDiagnostic(IsEmptyDescriptor, properties: properties.ToImmutable())); + } + else if (HasEligibleLengthProperty(type)) + { + var properties = ImmutableDictionary.CreateBuilder(); + properties.Add(DiagnosticPropertyKey, LengthText); + context.ReportDiagnostic(invocation.CreateDiagnostic(LengthDescriptor, properties: properties.ToImmutable())); + } + + else if (HasEligibleCountProperty(type)) + { + var properties = ImmutableDictionary.CreateBuilder(); + properties.Add(DiagnosticPropertyKey, CountText); + context.ReportDiagnostic(invocation.CreateDiagnostic(CountDescriptor, properties: properties.ToImmutable())); + } + } + } + + private static bool HasEligibleIsEmptyProperty(ITypeSymbol typeSymbol) + { + return typeSymbol.GetMembers(IsEmptyText) + .OfType() + .Any(property => property.Type.SpecialType == SpecialType.System_Boolean); + } + + private static bool HasEligibleLengthProperty(ITypeSymbol typeSymbol) + { + if (typeSymbol is IArrayTypeSymbol) + { + return true; + } + + return typeSymbol.GetMembers(LengthText) + .OfType() + .Any(property => property.Type.SpecialType is SpecialType.System_Int32 or SpecialType.System_UInt32); + } + + private static bool HasEligibleCountProperty(ITypeSymbol typeSymbol) + { + return typeSymbol.GetMembers(CountText) + .OfType() + .Any(property => property.Type.SpecialType is SpecialType.System_Int32 or SpecialType.System_UInt32); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 3c7fec79a6..28aa7fa22b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Preferovat AsSpan místo Substring + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Použít ContainsKey @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Preferovat statickou metodu .HashData před ComputeHash + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream má přetížení ReadAsync, které jako první argument přijímá Memory<Byte>, a přetížení WriteAsync, které jako první argument přijímá ReadOnlyMemory<Byte>. Upřednostňujte volání přetížení založených na paměti, která jsou efektivnější. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 3c32e8a463..7c28489c44 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types."AsSpan" gegenüber "Substring" bevorzugen + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' "ContainsKey" verwenden @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Statische Methode „HashData“ gegenüber „ComputeHash“ bevorzugen. + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream verfügt über eine Überladung "ReadAsync", die "Memory<Byte>" als erstes Argument akzeptiert, sowie über eine Überladung "WriteAsync", die "ReadOnlyMemory<Byte>" als erstes Argument akzeptiert. Rufen Sie möglichst arbeitsspeicherbasierte Überladungen auf, da diese effizienter sind. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 542233b588..6d89f08cd6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Preferir "AsSpan" a "Substring" + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Usar 'ContainsKey' @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Preferir el método estático "HashData" a "ComputeHash" + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" tiene una sobrecarga "ReadAsync" que toma "Memory<Byte>" como primer argumento y una sobrecarga "WriteAsync" que toma "ReadOnlyMemory<Byte>" como primer argumento. Es preferible llamar a las sobrecargas basadas en memory, que son más eficaces. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 54701481eb..ab9261039f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Préférer ’AsSpan’ à ’Substring' + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Utiliser « ContainsKey » @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Préférer les statiques. Méthode HashData sur « ComputeHash » + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream’ a une surcharge ’ReadAsync’ qui prend un ’Memory<Byte>' comme premier argument et une surcharge ’WriteAsync’ qui prend un ’ReadOnlyMemory<Byte>' comme premier argument. Préférez l'appel des surcharges basées sur la mémoire, car elles sont plus efficaces. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 6ed67554ee..89b21b91a9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Preferire 'AsSpan' a 'Substring' + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Usare 'ContainsKey' @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Preferire il metodo 'HashData' rispetto a 'ComputeHash' + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' contiene un overload 'ReadAsync' che accetta 'Memory<Byte>' come primo argomento e un overload 'WriteAsync' che accetta 'ReadOnlyMemory<Byte>' come primo argomento. Per la chiamata preferire gli overload basati su Memory, che sono più efficaci. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index a0e3c9be01..850f776474 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.'Substring' よりも 'AsSpan' を優先します + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' 'ContainsKey' を使用する @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.静的な 'HashData' メソッドは 'ComputeHash' に優先します + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' には、最初の引数として 'Memory<Byte>' を取る 'ReadAsync' オーバーロードと、最初の引数として 'ReadOnlyMemory<Byte>' を取る 'WriteAsync' オーバーロードがあります。より効率的なメモリ ベースのオーバーロードを呼び出すことをお勧めします。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 703601078b..a0b277333c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.''Substring'보다 'AsSpan' 우선적으로 사용 + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' 'ContainsKey' 사용 @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.'ComputeHash'보다 정적 'HashData' 메서드를 선호합니다. + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream'에 첫 번째 인수로 'Memory<Byte>'를 사용하는 'ReadAsync' 오버로드와 첫 번째 인수로 'ReadOnlyMemory<Byte>'를 사용하는 'WriteAsync' 오버로드가 있습니다. 더 효율적인 메모리 기반 오버로드를 호출하는 것이 좋습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index fa16e67807..33fe08f032 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Preferuj ciąg „AsSpan” niż „Substring” + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Użyj elementu „ContainsKey” @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Preferuj metodę statyczną „HashData”, nie „ComputeHash” + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Element „Stream” ma przeciążenie „ReadAsync”, które przyjmuje „Memory<Byte>” jako pierwszy argument i przeciążenie „WriteAsync”, które przyjmuje „ReadOnlyMemory<Byte>” jako pierwszy argument. Preferuj wywoływanie przeciążeń opartych na pamięci, co jest bardziej wydajne. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index a04b6b1c7e..2af717e7e6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Preferir 'AsSpan' em vez de 'Subcadeia de caracteres' + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Usar 'ContainsKey' @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Preferir o método estático “HashData” em vez de “ComputeHash” + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' tem uma sobrecarga 'ReadAsync' que recebe um 'Memory<Byte>' como o primeiro argumento e uma sobrecarga 'WriteAsync' que recebe um 'ReadOnlyMemory<Byte>' como o primeiro argumento. Prefira chamar as sobrecargas com base na memória, que são mais eficientes. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 9c6aca8d79..e31441201b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.Предпочтительное использование "AsSpan" вместо "Substring" + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' Использовать "ContainsKey" @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.Предпочитать статический метод "HashData" методу "ComputeHash" + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" содержит перегрузку "ReadAsync", которая принимает в качестве первого аргумента "Memory<Byte>", и перегрузку "WriteAsync", которая принимает в качестве первого аргумента "ReadOnlyMemory<Byte>". Старайтесь использовать перегрузки на основе памяти, которые являются более эффективными. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 338a9b614e..d2ab89fe65 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.'Substring' yerine 'AsSpan' tercih edin + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' 'ContainsKey' kullanın @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.'ComputeHash' yerine statik 'HashData' yöntemini tercih edin + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream', ilk bağımsız değişken olarak 'Memory<Byte>' alan bir 'ReadAsync' aşırı yüklemesine ve ilk bağımsız değişken olarak 'ReadOnlyMemory<Byte>' alan bir 'WriteAsync' aşırı yüklemesine sahip. Daha verimli olan bellek tabanlı aşırı yüklemeleri çağırmayı tercih edin. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index a67c866774..c19c6f268b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.首选 “AsSpan” 而不是 “Substring” + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' 使用 “ContainsKey” @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.首选静态“HashData”而不是的“ComputeHash” + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" 有一个将 "Memory<Byte>" 作为第一个参数的 "ReadAsync" 重载和一个将 "Memory<Byte>" 作为第一个参数的 "WriteAsync" 重载。首选调用基于内存的重载,它们的效率更高。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1a934c83cd..4cb4a591d9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1958,6 +1958,16 @@ Widening and user defined conversions are not supported with generic types.建議使用 'AsSpan' 而不是 'Substring' + + Use 'Count' check instead of 'Any()' + Use 'Count' check instead of 'Any()' + + + + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance + + Use 'ContainsKey' 請使用 'ContainsKey' @@ -2013,6 +2023,36 @@ Widening and user defined conversions are not supported with generic types.偏好靜態 'HashData' 方法而非 'ComputeHash' + + Use 'IsEmpty' check instead of 'Any()' + Use 'IsEmpty' check instead of 'Any()' + + + + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance + + + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + + + + Avoid using 'Enumerable.Any()' extension method + Avoid using 'Enumerable.Any()' extension method + + + + Use 'Length' check instead of 'Any()' + Use 'Length' check instead of 'Any()' + + + + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + Prefer comparing 'Length' to 0 rather than using 'Any()', both for clarity and for performance + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' 具有採用 'Memory<Byte>' 作為第一個引數的 'ReadAsync' 多載,以及採用 'ReadOnlyMemory<Byte>' 作為第一個引數的 'WriteAsync' 多載。建議呼叫採用記憶體的多載,較有效率。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 4cf3e6c4c6..78a563fdbe 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1680,6 +1680,18 @@ Using concrete types avoids virtual or interface call overhead and enables inlin |CodeFix|False| --- +## [CA1860](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860): Avoid using 'Enumerable.Any()' extension method + +Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 34084d60cd..7695feb426 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3115,6 +3115,26 @@ ] } }, + "CA1860": { + "id": "CA1860", + "shortDescription": "Avoid using 'Enumerable.Any()' extension method", + "fullDescription": "Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "PreferLengthCountIsEmptyOverAnyAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 1af3e2eebe..1316348ed0 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -9,4 +9,5 @@ CA1513 | | Incorrect usage of ConstantExpected attribute | CA1857 | | A constant is expected for the parameter | CA1859 | | Use concrete types when possible for improved performance | +CA1860 | | Avoid using 'Enumerable.Any()' extension method | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferCountOverAnyTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferCountOverAnyTests.cs new file mode 100644 index 0000000000..0b8292aeac --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferCountOverAnyTests.cs @@ -0,0 +1,448 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpPreferLengthCountIsEmptyOverAnyFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Performance.BasicPreferLengthCountIsEmptyOverAnyFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class PreferCountOverAnyTests + { + private static readonly DiagnosticResult ExpectedDiagnostic = new DiagnosticResult(PreferLengthCountIsEmptyOverAnyAnalyzer.CountDescriptor).WithLocation(0); + + [Fact] + public Task TestLocalDeclarationAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public void M() { + var list = new List(); + _ = {|#0:list.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public void M() { + var list = new List(); + _ = list.Count != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestLocalDeclarationAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function M() + Dim list = new List(Of Integer)() + Dim x = {|#0:list.Any()|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function M() + Dim list = new List(Of Integer)() + Dim x = list.Count <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestParameterDeclarationAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return {|#0:list.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return list.Count != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestParameterDeclarationAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return {|#0:list.Any()|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Count <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestNegatedAnyAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool IsEmpty(List list) { + return !{|#0:list.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool IsEmpty(List list) { + return list.Count == 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestNegatedAnyAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(list As List(Of Integer)) As Boolean + Return Not {|#0:list.Any()|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(list As List(Of Integer)) As Boolean + Return list.Count = 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task DontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return list.Select(x => x).Any(); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Select(Function(x) x).Any() + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task DontWarnOnAnyWithPredicateAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return list.Any(x => x > 5); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnAnyWithPredicateAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Any(Function(x) x > 5) + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task DontWarnOnInvalidCallAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasAny() + { + return System.Linq.Enumerable.{|CS1501:Any|}(); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnInvalidCallAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function M() As Boolean + Return System.Linq.Enumerable.{|BC30516:Any|}() + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task TestQualifiedCallAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return {|#0:Enumerable.Any(list)|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return list.Count != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestQualifiedCallAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return {|#0:Enumerable.Any(list)|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Count <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestFullyQualifiedCallAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return {|#0:System.Linq.Enumerable.Any(list)|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(List list) { + return list.Count != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestFullyQualifiedCallAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return {|#0:System.Linq.Enumerable.Any(list)|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Count <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestWithoutParenthesesAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return {|#0:list.Any|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Count <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestNegatedWithoutParenthesesAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return Not {|#0:list.Any|} + End Function +End Class"; + + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(list As List(Of Integer)) As Boolean + Return list.Count = 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task DontWarnOnCustomType() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(MyCollection collection) { + return collection.Any(); + } +} + +public class MyCollection { + public bool Any() => throw null; + public int Count => throw null; +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferIsEmptyOverAnyTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferIsEmptyOverAnyTests.cs new file mode 100644 index 0000000000..8f787e799d --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferIsEmptyOverAnyTests.cs @@ -0,0 +1,469 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpPreferLengthCountIsEmptyOverAnyFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Performance.BasicPreferLengthCountIsEmptyOverAnyFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class PreferIsEmptyOverAnyTests + { + private static readonly DiagnosticResult ExpectedDiagnostic = new DiagnosticResult(PreferLengthCountIsEmptyOverAnyAnalyzer.IsEmptyDescriptor).WithLocation(0); + + [Fact] + public Task TestLocalDeclarationAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public void M() { + var array = ImmutableList.Empty; + _ = {|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public void M() { + var array = ImmutableList.Empty; + _ = !array.IsEmpty; + } +}"; + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestLocalDeclarationAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function M() + Dim array = ImmutableList(Of Integer).Empty + Dim x = {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function M() + Dim array = ImmutableList(Of Integer).Empty + Dim x = Not array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestParameterDeclarationAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return {|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return !array.IsEmpty; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestParameterDeclarationAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return Not array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestNegatedAnyAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool IsEmpty(ImmutableList array) { + return !{|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool IsEmpty(ImmutableList array) { + return array.IsEmpty; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestNegatedAnyAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(array As ImmutableList(Of Integer)) As Boolean + Return Not {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(array As ImmutableList(Of Integer)) As Boolean + Return array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task DontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return array.Select(x => x).Any(); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return array.Select(Function(x) x).Any() + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task DontWarnOnAnyWithPredicateAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return array.Any(x => x > 5); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnAnyWithPredicateAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return array.Any(Function(x) x > 5) + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task TestQualifiedCallAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return {|#0:Enumerable.Any(array)|}; + } +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return !array.IsEmpty; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestQualifiedCallAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return {|#0:Enumerable.Any(array)|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return Not array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestFullyQualifiedCallAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return {|#0:System.Linq.Enumerable.Any(array)|}; + } +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public bool HasContents(ImmutableList array) { + return !array.IsEmpty; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestFullyQualifiedCallAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return {|#0:System.Linq.Enumerable.Any(array)|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return Not array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestWithoutParenthesesAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return {|#0:array.Any|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return Not array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestNegatedWithoutParenthesesAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return Not {|#0:array.Any|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As ImmutableList(Of Integer)) As Boolean + Return array.IsEmpty + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestPassedAsArgumentAsync() + { + const string code = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public void Run(ImmutableList array) { + X({|#0:array.Any()|}); + } + + public void X(bool b) => throw null; +}"; + const string fixedCode = @" +using System.Collections.Immutable; +using System.Linq; + +public class Tests { + public void Run(ImmutableList array) { + X(!array.IsEmpty); + } + + public void X(bool b) => throw null; +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestPassedAsArgumentAsync() + { + const string code = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function Run(array As ImmutableList(Of Integer)) + X({|#0:array.Any|}) + End Function + + Public Function X(b As Boolean) + Throw New System.Exception() + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Immutable +Imports System.Linq + +Public Class Tests + Public Function Run(array As ImmutableList(Of Integer)) + X(Not array.IsEmpty) + End Function + + Public Function X(b As Boolean) + Throw New System.Exception() + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task DontWarnOnCustomType() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(MyCollection collection) { + return collection.Any(); + } +} + +public class MyCollection { + public bool Any() => throw null; + public bool IsEmpty => throw null; +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferLengthOverAnyTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferLengthOverAnyTests.cs new file mode 100644 index 0000000000..400e64c61e --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferLengthOverAnyTests.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpPreferLengthCountIsEmptyOverAnyFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferLengthCountIsEmptyOverAnyAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Performance.BasicPreferLengthCountIsEmptyOverAnyFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class PreferLengthOverAnyTests + { + private static readonly DiagnosticResult ExpectedDiagnostic = new DiagnosticResult(PreferLengthCountIsEmptyOverAnyAnalyzer.LengthDescriptor).WithLocation(0); + + [Fact] + public Task TestLocalDeclarationAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public void M() { + var array = new int[0]; + _ = {|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public void M() { + var array = new int[0]; + _ = array.Length != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestLocalDeclarationAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function M() + Dim array = new Integer() {} + Dim x = {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function M() + Dim array = new Integer() {} + Dim x = array.Length <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestParameterDeclarationAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContent(int[] array) { + return {|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContent(int[] array) { + return array.Length != 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestParameterDeclarationAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As Integer()) As Boolean + Return {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As Integer()) As Boolean + Return array.Length <> 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task TestNegatedAnyAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool IsEmpty(int[] array) { + return !{|#0:array.Any()|}; + } +}"; + const string fixedCode = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool IsEmpty(int[] array) { + return array.Length == 0; + } +}"; + + return VerifyCS.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task VbTestNegatedAnyAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(array As Integer()) As Boolean + Return Not {|#0:array.Any()|} + End Function +End Class"; + const string fixedCode = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function IsEmpty(array As Integer()) As Boolean + Return array.Length = 0 + End Function +End Class"; + + return VerifyVB.VerifyCodeFixAsync(code, ExpectedDiagnostic, fixedCode); + } + + [Fact] + public Task DontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(int[] array) { + return array.Select(x => x).Any(); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnChainedLinqWithAnyAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As Integer()) As Boolean + Return array.Select(Function(x) x).Any() + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task DontWarnOnAnyWithPredicateAsync() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(int[] array) { + return array.Any(x => x > 5); + } +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VbDontWarnOnAnyWithPredicateAsync() + { + const string code = @" +Imports System.Collections.Generic +Imports System.Linq + +Public Class Tests + Public Function HasContents(array As Integer()) As Boolean + Return array.Any(Function(x) x > 5) + End Function +End Class"; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task DontWarnOnCustomType() + { + const string code = @" +using System.Collections.Generic; +using System.Linq; + +public class Tests { + public bool HasContents(MyCollection collection) { + return collection.Any(); + } +} + +public class MyCollection { + public bool Any() => throw null; + public int Length => throw null; +}"; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicPreferLengthCountIsEmptyOverAnyFixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicPreferLengthCountIsEmptyOverAnyFixer.vb new file mode 100644 index 0000000000..6e3da5eaed --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicPreferLengthCountIsEmptyOverAnyFixer.vb @@ -0,0 +1,132 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Performance + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance + + Public NotInheritable Class BasicPreferLengthCountIsEmptyOverAnyFixer + Inherits PreferLengthCountIsEmptyOverAnyFixer + + Protected Overrides Function ReplaceAnyWithIsEmpty(root As SyntaxNode, node As SyntaxNode) As SyntaxNode + Dim invocation = TryCast(node, InvocationExpressionSyntax) + Dim memberAccess As MemberAccessExpressionSyntax + If invocation Is Nothing Then + memberAccess = TryCast(node, MemberAccessExpressionSyntax) + If memberAccess Is Nothing Then + Return Nothing + End If + + Dim newMemberAccess = memberAccess.WithName( + SyntaxFactory.IdentifierName(PreferLengthCountIsEmptyOverAnyAnalyzer.IsEmptyText) + ) + Dim unaryParent = TryCast(memberAccess.Parent, UnaryExpressionSyntax) + If unaryParent IsNot Nothing And unaryParent.IsKind(SyntaxKind.NotExpression) Then + Return root.ReplaceNode(unaryParent, newMemberAccess.WithTriviaFrom(unaryParent)) + End If + + Dim negatedExpression = SyntaxFactory.UnaryExpression( + SyntaxKind.NotExpression, + SyntaxFactory.Token(SyntaxKind.NotKeyword), + newMemberAccess + ) + + Return root.ReplaceNode(memberAccess, negatedExpression.WithTriviaFrom(memberAccess)) + Else + memberAccess = TryCast(invocation.Expression, MemberAccessExpressionSyntax) + If memberAccess Is Nothing Then + Return Nothing + End If + + Dim expression = memberAccess.Expression + If invocation.ArgumentList.Arguments.Count > 0 Then + expression = invocation.ArgumentList.Arguments(0).GetExpression() + End If + + Dim newMemberAccess = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression, + SyntaxFactory.Token(SyntaxKind.DotToken), + SyntaxFactory.IdentifierName(PreferLengthCountIsEmptyOverAnyAnalyzer.IsEmptyText) + ) + Dim unaryParent = TryCast(invocation.Parent, UnaryExpressionSyntax) + If unaryParent IsNot Nothing And unaryParent.IsKind(SyntaxKind.NotExpression) Then + Return root.ReplaceNode(unaryParent, newMemberAccess.WithTriviaFrom(unaryParent)) + End If + + Dim negatedExpression = SyntaxFactory.UnaryExpression( + SyntaxKind.NotExpression, + SyntaxFactory.Token(SyntaxKind.NotKeyword), + newMemberAccess + ) + + Return root.ReplaceNode(invocation, negatedExpression.WithTriviaFrom(invocation)) + End If + End Function + + Protected Overrides Function ReplaceAnyWithLength(root As SyntaxNode, node As SyntaxNode) As SyntaxNode + Return ReplaceAnyWithPropertyCheck(root, node, PreferLengthCountIsEmptyOverAnyAnalyzer.LengthText) + End Function + + Protected Overrides Function ReplaceAnyWithCount(root As SyntaxNode, node As SyntaxNode) As SyntaxNode + Return ReplaceAnyWithPropertyCheck(root, node, PreferLengthCountIsEmptyOverAnyAnalyzer.CountText) + End Function + + Private Shared Function ReplaceAnyWithPropertyCheck(root As SyntaxNode, node As SyntaxNode, propertyName As String) As SyntaxNode + Dim invocation = TryCast(node, InvocationExpressionSyntax) + Dim memberAccess As MemberAccessExpressionSyntax + If invocation Is Nothing Then + memberAccess = TryCast(node, MemberAccessExpressionSyntax) + If memberAccess Is Nothing Then + Return Nothing + End If + + If memberAccess.Parent.IsKind(SyntaxKind.NotExpression) Then + Dim binaryExpression = GetBinaryExpression(memberAccess.Expression, propertyName, SyntaxKind.EqualsExpression) + Return root.ReplaceNode(memberAccess.Parent, binaryExpression.WithTriviaFrom(memberAccess.Parent)) + End If + + Return root.ReplaceNode(memberAccess, GetBinaryExpression(memberAccess.Expression, propertyName, SyntaxKind.NotEqualsExpression).WithTriviaFrom(memberAccess)) + Else + memberAccess = TryCast(invocation.Expression, MemberAccessExpressionSyntax) + If memberAccess Is Nothing Then + Return Nothing + End If + + Dim expression = memberAccess.Expression + If invocation.ArgumentList.Arguments.Count > 0 Then + expression = invocation.ArgumentList.Arguments(0).GetExpression() + End If + + If invocation.Parent.IsKind(SyntaxKind.NotExpression) Then + Dim binaryExpression = GetBinaryExpression(expression, propertyName, SyntaxKind.EqualsExpression) + Return root.ReplaceNode(invocation.Parent, binaryExpression.WithTriviaFrom(invocation.Parent)) + End If + + Return root.ReplaceNode(invocation, GetBinaryExpression(expression, propertyName, SyntaxKind.NotEqualsExpression).WithTriviaFrom(invocation)) + End If + End Function + + Private Shared Function GetBinaryExpression(expression As ExpressionSyntax, member As String, expressionKind As SyntaxKind) As BinaryExpressionSyntax + Dim tokenKind = If(expressionKind = SyntaxKind.EqualsExpression, SyntaxKind.EqualsToken, SyntaxKind.LessThanGreaterThanToken) + return SyntaxFactory.BinaryExpression( + expressionKind, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression, + SyntaxFactory.Token(SyntaxKind.DotToken), + SyntaxFactory.IdentifierName(member) + ), + SyntaxFactory.Token(tokenKind), + SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(0) + ) + ) + End Function + End Class +End Namespace \ No newline at end of file diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 8641989f9e..789add83ee 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1859 +Performance: HA, CA1800-CA1860 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/Extensions/IOperationExtensions.cs b/src/Utilities/Compiler/Extensions/IOperationExtensions.cs index 4126f6dc47..4cc6f5ba77 100644 --- a/src/Utilities/Compiler/Extensions/IOperationExtensions.cs +++ b/src/Utilities/Compiler/Extensions/IOperationExtensions.cs @@ -25,13 +25,13 @@ internal static partial class IOperationExtensions /// If the invocation actually involves a conversion from A to some other type, say 'C', on which B is invoked, /// then this method returns type A if is true, and C if false. /// - public static INamedTypeSymbol? GetReceiverType(this IInvocationOperation invocation, Compilation compilation, bool beforeConversion, CancellationToken cancellationToken) + public static ITypeSymbol? GetReceiverType(this IInvocationOperation invocation, Compilation compilation, bool beforeConversion, CancellationToken cancellationToken) { if (invocation.Instance != null) { return beforeConversion ? GetReceiverType(invocation.Instance.Syntax, compilation, cancellationToken) : - invocation.Instance.Type as INamedTypeSymbol; + invocation.Instance.Type; } else if (invocation.TargetMethod.IsExtensionMethod && !invocation.TargetMethod.Parameters.IsEmpty) { @@ -40,22 +40,22 @@ internal static partial class IOperationExtensions { return beforeConversion ? GetReceiverType(firstArg.Value.Syntax, compilation, cancellationToken) : - firstArg.Value.Type as INamedTypeSymbol; + firstArg.Value.Type; } else if (invocation.TargetMethod.Parameters[0].IsParams) { - return invocation.TargetMethod.Parameters[0].Type as INamedTypeSymbol; + return invocation.TargetMethod.Parameters[0].Type; } } return null; } - private static INamedTypeSymbol? GetReceiverType(SyntaxNode receiverSyntax, Compilation compilation, CancellationToken cancellationToken) + private static ITypeSymbol? GetReceiverType(SyntaxNode receiverSyntax, Compilation compilation, CancellationToken cancellationToken) { var model = compilation.GetSemanticModel(receiverSyntax.SyntaxTree); var typeInfo = model.GetTypeInfo(receiverSyntax, cancellationToken); - return typeInfo.Type as INamedTypeSymbol; + return typeInfo.Type; } public static bool HasNullConstantValue(this IOperation operation)