From d5e26079fdcaa3c1cf67f6045c19edaf8d6af5b3 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 12 Jul 2023 01:16:42 +0200 Subject: [PATCH 01/42] Add analyzer and fixer for CA1865 This analyzer detects when either `Set.Add` or `Set.Remove` is guarded by `Set.Contains`. A fix is only suggested when the conditional body has a single statement that is either one of the former two methods. Fix #85490 --- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 72 ++ .../Core/AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 9 + ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 63 ++ .../DoNotGuardSetAddOrRemoveByContains.cs | 200 ++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 666 ++++++++++++++++++ ...oNotGuardSetAddOrRemoveByContains.Fixer.vb | 52 ++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 22 files changed, 1279 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs new file mode 100644 index 0000000000..f24b37cea9 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.NetCore.Analyzers.Performance; + +namespace Microsoft.NetCore.CSharp.Analyzers.Performance +{ + internal class CSharpDoNotGuardSetAddOrRemoveByContainsFixer : DoNotGuardSetAddOrRemoveByContainsFixer + { + protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax) + { + if (conditionalSyntax is ConditionalExpressionSyntax conditionalExpressionSyntax) + return conditionalExpressionSyntax.WhenTrue.ChildNodes().Count() == 1; + + if (conditionalSyntax is IfStatementSyntax) + return true; + + return false; + } + + protected override Document ReplaceConditionWithChild(Document document, SyntaxNode root, SyntaxNode conditionalOperationNode, SyntaxNode childOperationNode) + { + SyntaxNode newRoot; + + if (conditionalOperationNode is ConditionalExpressionSyntax conditionalExpressionSyntax && + conditionalExpressionSyntax.WhenFalse.ChildNodes().Any()) + { + var expression = GetNegatedExpression(document, childOperationNode); + + SyntaxNode newConditionalOperationNode = conditionalExpressionSyntax + .WithCondition((ExpressionSyntax)expression) + .WithWhenTrue(conditionalExpressionSyntax.WhenFalse) + .WithWhenFalse(null!) + .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode); + + newRoot = root.ReplaceNode(conditionalOperationNode, newConditionalOperationNode); + } + else if (conditionalOperationNode is IfStatementSyntax ifStatementSyntax && ifStatementSyntax.Else != null) + { + var expression = GetNegatedExpression(document, childOperationNode); + + SyntaxNode newConditionalOperationNode = ifStatementSyntax + .WithCondition((ExpressionSyntax)expression) + .WithStatement(ifStatementSyntax.Else.Statement) + .WithElse(null) + .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode); + + newRoot = root.ReplaceNode(conditionalOperationNode, newConditionalOperationNode); + } + else + { + SyntaxNode newConditionNode = childOperationNode + .WithAdditionalAnnotations(Formatter.Annotation) + .WithTriviaFrom(conditionalOperationNode); + + newRoot = root.ReplaceNode(conditionalOperationNode, newConditionNode); + } + + return document.WithSyntaxRoot(newRoot); + } + + private static SyntaxNode GetNegatedExpression(Document document, SyntaxNode newConditionNode) + { + var generator = SyntaxGenerator.GetGenerator(document); + return generator.LogicalNotExpression(((ExpressionStatementSyntax)newConditionNode).Expression.WithoutTrivia()); + } + } +} diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index f84a761c29..1d33e3dade 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -4,6 +4,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- +CA1865 | Performance | Info | DoNotGuardSetAddOrRemoveByContains, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865) CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250) CA1510 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510) CA1511 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 571ee4ee1f..6189dfc3a6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1509,6 +1509,15 @@ Unnecessary call to 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs new file mode 100644 index 0000000000..a18b0e8436 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + public abstract class DoNotGuardSetAddOrRemoveByContainsFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DoNotGuardSetAddOrRemoveByContains.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + + if (node is null) + { + return; + } + + var diagnostic = context.Diagnostics.First(); + var conditionalLocation = diagnostic.AdditionalLocations[0]; + var childLocation = diagnostic.AdditionalLocations[1]; + + if (root.FindNode(conditionalLocation.SourceSpan) is not SyntaxNode conditionalSyntax || + root.FindNode(childLocation.SourceSpan) is not SyntaxNode childStatementSyntax) + { + return; + } + + // We only offer a fixer if the conditonal true branch has a single statement, either 'Add' or 'Delete' + if (!SyntaxSupportedByFixer(conditionalSyntax)) + { + return; + } + + var title = MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle; + var codeAction = CodeAction.Create(title, + ct => Task.FromResult(ReplaceConditionWithChild(context.Document, root, conditionalSyntax, childStatementSyntax)), + title); + + context.RegisterCodeFix(codeAction, diagnostic); + } + + protected abstract bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax); + + protected abstract Document ReplaceConditionWithChild(Document document, SyntaxNode root, + SyntaxNode conditionalOperationNode, + SyntaxNode childOperationNode); + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs new file mode 100644 index 0000000000..f30fa90d11 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.PooledObjects; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1865: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class DoNotGuardSetAddOrRemoveByContains : DiagnosticAnalyzer + { + internal const string RuleId = "CA1865"; + + private const string Contains = nameof(Contains); + private const string Add = nameof(Add); + private const string Remove = nameof(Remove); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(DoNotGuardSetAddOrRemoveByContainsTitle)), + CreateLocalizableResourceString(nameof(DoNotGuardSetAddOrRemoveByContainsMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(DoNotGuardSetAddOrRemoveByContainsDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!TryGetRequiredMethods(context.Compilation, out var containsMethod, out var addMethod, out var removeMethod)) + { + return; + } + + context.RegisterOperationAction(context => OnConditional(context, containsMethod, addMethod, removeMethod), OperationKind.Conditional); + } + + private static void OnConditional(OperationAnalysisContext context, IMethodSymbol containsMethod, IMethodSymbol addMethod, IMethodSymbol removeMethod) + { + var conditional = (IConditionalOperation)context.Operation; + + if (!TryExtractContainsInvocation(conditional.Condition, containsMethod, out var containsInvocation, out var containsNegated)) + { + return; + } + + if (!TryExtractAddOrRemoveInvocation(conditional.WhenTrue.Children, addMethod, removeMethod, containsNegated, out var addOrRemoveInvocation)) + { + return; + } + + if (!AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation)) + { + return; + } + + using var locations = ArrayBuilder.GetInstance(2); + locations.Add(conditional.Syntax.GetLocation()); + locations.Add(addOrRemoveInvocation.Syntax.Parent!.GetLocation()); + + context.ReportDiagnostic(containsInvocation.CreateDiagnostic(Rule, additionalLocations: locations.ToImmutable(), null)); + } + + private static bool TryGetRequiredMethods( + Compilation compilation, + [NotNullWhen(true)] out IMethodSymbol? containsMethod, + [NotNullWhen(true)] out IMethodSymbol? addMethod, + [NotNullWhen(true)] out IMethodSymbol? removeMethod) + { + var iSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericISet1); + var iCollectionType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1); + + if (iSetType is null || iCollectionType is null) + { + containsMethod = null; + addMethod = null; + removeMethod = null; + + return false; + } + + addMethod = iSetType.GetMembers(Add).OfType().FirstOrDefault(); + containsMethod = iCollectionType.GetMembers(Contains).OfType().FirstOrDefault(); + removeMethod = iCollectionType.GetMembers(Remove).OfType().FirstOrDefault(); + + return containsMethod is not null && addMethod is not null && removeMethod is not null; + } + + private static bool TryExtractContainsInvocation( + IOperation condition, + IMethodSymbol containsMethod, + [NotNullWhen(true)] out IInvocationOperation? containsInvocation, + out bool containsNegated) + { + containsNegated = false; + containsInvocation = null; + + switch (condition.WalkDownParentheses()) + { + case IInvocationOperation invocation: + containsInvocation = invocation; + break; + case IUnaryOperation unaryOperation when unaryOperation.OperatorKind == UnaryOperatorKind.Not && unaryOperation.Operand is IInvocationOperation operand: + containsNegated = true; + containsInvocation = operand; + break; + default: + return false; + } + + return DoesSignatureMatch(containsInvocation.TargetMethod, containsMethod); + } + + private static bool TryExtractAddOrRemoveInvocation( + IEnumerable operations, + IMethodSymbol addMethod, + IMethodSymbol removeMethod, + bool containsNegated, + [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) + { + addOrRemoveInvocation = null; + var firstOperation = operations.FirstOrDefault(); + + if (firstOperation is null) + { + return false; + } + + switch (firstOperation) + { + case IInvocationOperation invocation: + if ((containsNegated && DoesSignatureMatch(invocation.TargetMethod, addMethod)) || + (!containsNegated && DoesSignatureMatch(invocation.TargetMethod, removeMethod))) + { + addOrRemoveInvocation = invocation; + return true; + } + + break; + case IExpressionStatementOperation expressionStatement: + var nestedAddOrRemove = expressionStatement.Children + .OfType() + .FirstOrDefault(i => containsNegated ? + DoesSignatureMatch(i.TargetMethod, addMethod) : + DoesSignatureMatch(i.TargetMethod, removeMethod)); + + if (nestedAddOrRemove != null) + { + addOrRemoveInvocation = nestedAddOrRemove; + return true; + } + + break; + } + + return false; + } + + private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) + { + return (invocation1.Instance, invocation2.Instance) switch + { + (IFieldReferenceOperation fieldRef1, IFieldReferenceOperation fieldRef2) => fieldRef1.Member == fieldRef2.Member, + (IPropertyReferenceOperation propRef1, IPropertyReferenceOperation propRef2) => propRef1.Member == propRef2.Member, + (IParameterReferenceOperation paramRef1, IParameterReferenceOperation paramRef2) => paramRef1.Parameter == paramRef2.Parameter, + (ILocalReferenceOperation localRef1, ILocalReferenceOperation localRef2) => localRef1.Local == localRef2.Local, + _ => false, + }; + } + + private static bool DoesSignatureMatch(IMethodSymbol suspected, IMethodSymbol comparator) + { + return suspected.OriginalDefinition.ReturnType.Name == comparator.ReturnType.Name + && suspected.Name == comparator.Name + && suspected.Parameters.Length == comparator.Parameters.Length + && suspected.Parameters.Zip(comparator.Parameters, (p1, p2) => p1.OriginalDefinition.Type.Name == p2.Type.Name).All(isParameterEqual => isParameterEqual); + } + } +} 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 6222d73c75..e301c10bc5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -763,6 +763,21 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E Nepotřebné volání Dictionary.ContainsKey(key) + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Nepoužívejte pevně zakódovaný certifikát 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 15cea3dd32..203d961d11 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -763,6 +763,21 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Nicht erforderlicher Aufruf von “Dictionary.ContainsKey(key)” + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Keine Hartcodierung von Zertifikaten 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 eba673dee2..a0ad6013d0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -763,6 +763,21 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Llamada innecesaria a 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate No codificar el certificado de forma rígida 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 90cb521b4c..260b52feff 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -763,6 +763,21 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Appel inutile à 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Ne pas coder en dur le certificat 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 8fedc9bb9b..3b2b6e42f1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -763,6 +763,21 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Chiamata non necessaria a 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Non impostare il certificato come hardcoded 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 a60785ffee..6af7aaef5b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -763,6 +763,21 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 'Dictionary.ContainsKey(key)' への不要な呼び出し + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate 証明書をハードコーディングしない 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 53fe883ef2..92ba83a4c4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -763,6 +763,21 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 'Dictionary.ContainsKey(key)'에 대한 불필요한 호출 + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate 인증서 하드 코딩 안 함 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 a844516d82..cf0577a2b7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -763,6 +763,21 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Niepotrzebne wywołanie metody \"Dictionary.ContainsKey(key)\" + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Nie zapisuj certyfikatu na stałe w kodzie 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 ae43150a2f..5db5fa81db 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 @@ -763,6 +763,21 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip Chamada desnecessária para 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Não embutir o certificado em código 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 c7d55c78d0..44ca1ea290 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -763,6 +763,21 @@ Widening and user defined conversions are not supported with generic types.Ненужный вызов \"Dictionary.ContainsKey(key)\" + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Не используйте жестко заданный сертификат. 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 6c361a5864..a8ead79886 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -763,6 +763,21 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Gereksiz 'Dictionary.ContainsKey(key)' çağrısı + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate Sertifikayı sabit olarak kodlama 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 42c966a160..2ddf79e108 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 @@ -763,6 +763,21 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi 对 “Dictionary.ContainsKey(key)” 的不必要调用 + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate 请勿硬编码证书 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 aa2bb17278..8f2c847ea6 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 @@ -763,6 +763,21 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 不需要呼叫 'Dictionary.ContainsKey(key)' + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + + + + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + + + + Unnecessary call to 'Set.Contains(item)' + Unnecessary call to 'Set.Contains(item)' + + Do not hard-code certificate 不要硬式編碼憑證 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 2f614e1242..7758f2b9ed 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3215,6 +3215,26 @@ ] } }, + "CA1865": { + "id": "CA1865", + "shortDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'", + "fullDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "DoNotGuardSetAddOrRemoveByContains", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs new file mode 100644 index 0000000000..bb8e51198f --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -0,0 +1,666 @@ +// 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 Test.Utilities; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.DoNotGuardSetAddOrRemoveByContains, + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpDoNotGuardSetAddOrRemoveByContainsFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.DoNotGuardSetAddOrRemoveByContains, + Microsoft.NetCore.VisualBasic.Analyzers.Performance.BasicDoNotGuardSetAddOrRemoveByContainsFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class DoNotGuardSetAddOrRemoveByContainsTests + { + #region Tests + [Fact] + public async Task NonInvocationConditionDoesNotThrow_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + public MyClass() + { + if (!true) { } + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task AddIsTheOnlyStatement_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + MySet.Add(""Item""); + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + MySet.Add(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveIsTheOnlyStatement_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddIsTheOnlyStatementInABlock_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + { + MySet.Add(""Item""); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + MySet.Add(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveIsTheOnlyStatementInABlock_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + { + MySet.Remove(""Item""); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddHasElseStatement_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + MySet.Add(""Item""); + else + throw new Exception(""Item already exists""); + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (!MySet.Add(""Item"")) + throw new Exception(""Item already exists""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveHasElseStatement_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + MySet.Remove(""Item""); + else + throw new Exception(""Item doesn't exist""); + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (!MySet.Remove(""Item"")) + throw new Exception(""Item doesn't exist""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddHasElseBlock_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + { + MySet.Add(""Item""); + } + else + { + throw new Exception(""Item already exists""); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (!MySet.Add(""Item"")) + { + throw new Exception(""Item already exists""); + } + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveHasElseBlock_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + { + MySet.Remove(""Item""); + } + else + { + throw new Exception(""Item doesn't exist""); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (!MySet.Remove(""Item"")) + { + throw new Exception(""Item doesn't exist""); + } + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddAdditionalStatements_ReportsDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + { + MySet.Add(""Item""); + Console.WriteLine(); + } + } + " + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveAdditionalStatements_ReportsDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + { + MySet.Remove(""Item""); + Console.WriteLine(); + } + } + " + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task AddWithNonNegatedContains_NoDiagnostics_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (MySet.Contains(""Item"")) + MySet.Add(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveWithNegatedContains_NoDiagnostics_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (!MySet.Contains(""Item"")) + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task AdditionalCondition_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (MySet.Contains(""Item"") && MySet.Count > 2) + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task ConditionInVariable_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + var result = MySet.Contains(""Item""); + if (result) + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveInSeparateLine_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (MySet.Contains(""Item"")) + _ = MySet.Count; + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NotSetRemove_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + private bool Remove(string item) => false; + + public MyClass() + { + if (MySet.Contains(""Item"")) + Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task TriviaIsPreserved_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + // reticulates the splines + if ([|MySet.Contains(""Item"")|]) + { + MySet.Remove(""Item""); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + // reticulates the splines + MySet.Remove(""Item""); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddIsTheOnlyStatement_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then + MySet.Add(""Item"") + End If + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + MySet.Add(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveIsTheOnlyStatement_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If ([|MySet.Contains(""Item"")|]) Then + MySet.Remove(""Item"") + End If + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + MySet.Remove(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddHasElseBlock_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then + MySet.Add(""Item"") + Else + Throw new Exception(""Item already exists"") + End If + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not MySet.Add(""Item"") Then + Throw new Exception(""Item already exists"") + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveHasElseBlock_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If [|MySet.Contains(""Item"")|] Then + MySet.Remove(""Item"") + Else + Throw new Exception(""Item doesn't exist"") + End If + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not MySet.Remove(""Item"") Then + Throw new Exception(""Item doesn't exist"") + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddWithNonNegatedContains_NoDiagnostics_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If MySet.Contains(""Item"") Then MySet.Add(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveWithNegatedContains_NoDiagnostics_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not MySet.Contains(""Item"") Then MySet.Remove(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task AdditionalStatements_ReportsDiagnostic_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If [|MySet.Contains(""Item"")|] Then + MySet.Remove(""Item"") + Console.WriteLine() + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyAnalyzerAsync(source); + } + + [Fact] + [WorkItem(6377, "https://github.com/dotnet/roslyn-analyzers/issues/6377")] + public async Task ContainsAndRemoveCalledOnDifferentInstances_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet SetField1 = new HashSet(); + private readonly HashSet SetField2 = new HashSet(); + + public HashSet SetProperty1 { get; } = new HashSet(); + + public MyClass() + { + if (SetField2.Contains(""Item"")) + SetField1.Remove(""Item""); + + if (!SetField1.Contains(""Item"")) + { + SetField2.Remove(""Item""); + } + + if (SetProperty1.Contains(""Item"")) + SetField1.Remove(""Item""); + + if (!SetField1.Contains(""Item"")) + { + SetProperty1.Remove(""Item""); + } + + var mySetLocal4 = new HashSet(); + if (mySetLocal4.Contains(""Item"")) + SetField1.Remove(""Item""); + + if (!SetField1.Contains(""Item"")) + { + mySetLocal4.Remove(""Item""); + } + } + + private void RemoveItem(HashSet setParam) + { + if (setParam.Contains(""Item"")) + SetField1.Remove(""Item""); + + if (!SetField1.Contains(""Item"")) + { + setParam.Remove(""Item""); + } + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + #endregion + + #region Helpers + private const string CSUsings = @"using System; +using System.Collections.Generic;"; + + private const string CSNamespaceAndClassStart = @"namespace Testopolis +{ + public class MyClass + { +"; + + private const string CSNamespaceAndClassEnd = @" + } +}"; + + private const string VBUsings = @"Imports System +Imports System.Collections.Generic"; + #endregion + } +} diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb new file mode 100644 index 0000000000..b8fe6fd10a --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -0,0 +1,52 @@ +' 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.Editing +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Performance + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance + + Public NotInheritable Class BasicDoNotGuardSetAddOrRemoveByContainsFixer + Inherits DoNotGuardSetAddOrRemoveByContainsFixer + + Protected Overrides Function SyntaxSupportedByFixer(conditionalSyntax As SyntaxNode) As Boolean + If TypeOf conditionalSyntax Is IfStatementSyntax Then + Return True + End If + + If TypeOf conditionalSyntax Is MultiLineIfBlockSyntax Then + Return CType(conditionalSyntax, MultiLineIfBlockSyntax).IfStatement.ChildNodes().Count() = 1 + End If + + Return False + End Function + + Protected Overrides Function ReplaceConditionWithChild(document As Document, root As SyntaxNode, conditionalOperationNode As SyntaxNode, childOperationNode As SyntaxNode) As Document + Dim newConditionNode As SyntaxNode = childOperationNode + + ' If there's an else block, negate the condition and replace the single true statement with it + Dim multiLineIfBlockSyntax = TryCast(conditionalOperationNode, MultiLineIfBlockSyntax) + If multiLineIfBlockSyntax?.ElseBlock?.ChildNodes().Any() Then + Dim generator = SyntaxGenerator.GetGenerator(document) + Dim negatedExpression = generator.LogicalNotExpression(CType(childOperationNode, ExpressionStatementSyntax).Expression.WithoutTrivia()) + + Dim oldElseBlock = multiLineIfBlockSyntax.ElseBlock.Statements + + newConditionNode = multiLineIfBlockSyntax.WithIfStatement(multiLineIfBlockSyntax.IfStatement.WithCondition(CType(negatedExpression, ExpressionSyntax))) _ + .WithStatements(oldElseBlock) _ + .WithElseBlock(Nothing) _ + .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) + Else + newConditionNode = newConditionNode.WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) + End If + + Dim newRoot = root.ReplaceNode(conditionalOperationNode, newConditionNode) + + Return document.WithSyntaxRoot(newRoot) + End Function + End Class +End Namespace diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index d41b6104e2..67c217b4c2 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-CA1864 +Performance: HA, CA1800-CA1865 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2261 Naming: CA1700-CA1727 From 5c6bcdb7129146d268d4d52a60319be73289bae2 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 13 Jul 2023 22:19:06 +0200 Subject: [PATCH 02/42] Add CA1865 to Microsoft.CodeAnalysis.NetAnalyzers.md --- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index accfe817af..05b4331e93 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1740,6 +1740,18 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try |CodeFix|True| --- +## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + +Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + +|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. From 9547162c21ebe632598c601dff42cbbf767d3565 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 13 Jul 2023 22:20:50 +0200 Subject: [PATCH 03/42] Add CA1865 to RulesMissingDocumentation.md --- src/NetAnalyzers/RulesMissingDocumentation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index d6a02865a8..3dea1c0ab3 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -11,5 +11,6 @@ CA1857 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | CA1864 | | Prefer the 'IDictionary.TryAdd(TKey, TValue)' method | +CA1865 | | Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | From 52e7f911a096424a507f7238f2a44b83a228b832 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 13 Jul 2023 22:32:30 +0200 Subject: [PATCH 04/42] Add changes from msbuild pack --- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 2 +- src/NetAnalyzers/RulesMissingDocumentation.md | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 05b4331e93..0c781373b9 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1740,7 +1740,7 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try |CodeFix|True| --- -## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' +## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Unnecessary call to 'Set.Contains(item)' Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 7758f2b9ed..a84785aeea 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3217,7 +3217,7 @@ }, "CA1865": { "id": "CA1865", - "shortDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'", + "shortDescription": "Unnecessary call to 'Set.Contains(item)'", "fullDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw.", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 3dea1c0ab3..f773496d0f 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -10,7 +10,6 @@ CA1856 | | A constant is expected for the parameter | CA1862 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | -CA1864 | | Prefer the 'IDictionary.TryAdd(TKey, TValue)' method | -CA1865 | | Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' | +CA1865 | | Unnecessary call to 'Set.Contains(item)' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | From fea7a0a8fd9b879ee8f4a1369615f21f856f5a0d Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 00:06:32 +0200 Subject: [PATCH 05/42] Update DoNotGuardSetAddOrRemoveByContainsDescription --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- .../xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.de.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.es.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.it.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf | 4 ++-- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 2 +- 16 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 6189dfc3a6..c92c308867 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1510,7 +1510,7 @@ Unnecessary call to 'Dictionary.ContainsKey(key)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' 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 e301c10bc5..9e8758a4d1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -764,8 +764,8 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 203d961d11..069cab97d1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -764,8 +764,8 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 a0ad6013d0..407b5850f3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -764,8 +764,8 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 260b52feff..04e33621e9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -764,8 +764,8 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 3b2b6e42f1..27ffaf6701 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -764,8 +764,8 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 6af7aaef5b..6afc7d7d30 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -764,8 +764,8 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 92ba83a4c4..d24529f789 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -764,8 +764,8 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 cf0577a2b7..d62e1b03d1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -764,8 +764,8 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 5db5fa81db..72414df0f2 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 @@ -764,8 +764,8 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 44ca1ea290..ac8ec6ab40 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -764,8 +764,8 @@ Widening and user defined conversions are not supported with generic types. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 a8ead79886..8c8f60121d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -764,8 +764,8 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 2ddf79e108..d2022c75d0 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 @@ -764,8 +764,8 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. 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 8f2c847ea6..efb44dd723 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 @@ -764,8 +764,8 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 0c781373b9..32adc1c000 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1742,7 +1742,7 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try ## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Unnecessary call to 'Set.Contains(item)' -Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw. +Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. |Item|Value| |-|-| diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index a84785aeea..e610157ed1 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3218,7 +3218,7 @@ "CA1865": { "id": "CA1865", "shortDescription": "Unnecessary call to 'Set.Contains(item)'", - "fullDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists, and will not throw.", + "fullDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed.", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", "properties": { From 02fc23697e4037ee0c54a0289f3fa90fd6cded32 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 14:55:22 +0200 Subject: [PATCH 06/42] Add ExportCodeFixProviderAttribute --- .../CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index f24b37cea9..3a97d12228 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -1,7 +1,9 @@ // 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 System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; @@ -9,7 +11,8 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Performance { - internal class CSharpDoNotGuardSetAddOrRemoveByContainsFixer : DoNotGuardSetAddOrRemoveByContainsFixer + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + public sealed class CSharpDoNotGuardSetAddOrRemoveByContainsFixer : DoNotGuardSetAddOrRemoveByContainsFixer { protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax) { From 68c210f69e190a99c69dbe6a2cfed0594d7cec0e Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 14:56:57 +0200 Subject: [PATCH 07/42] Remove call to First with call to indexer --- .../DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs index 96e744a1e7..ed519c8b05 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Linq; using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis; @@ -30,7 +29,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - var diagnostic = context.Diagnostics.First(); + var diagnostic = context.Diagnostics[0]; var conditionalOperationSpan = diagnostic.AdditionalLocations[0]; var childLocation = diagnostic.AdditionalLocations[1]; if (root.FindNode(conditionalOperationSpan.SourceSpan) is not SyntaxNode conditionalSyntax || From 5a0113ff5f5df1d071c9f20e8e69f6267736c834 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 15:03:07 +0200 Subject: [PATCH 08/42] Change equivalenceKey of CodeAction --- .../Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs index a18b0e8436..de479cc237 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -46,10 +46,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - var title = MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle; - var codeAction = CodeAction.Create(title, + var codeAction = CodeAction.Create(MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle, ct => Task.FromResult(ReplaceConditionWithChild(context.Document, root, conditionalSyntax, childStatementSyntax)), - title); + nameof(MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle)); context.RegisterCodeFix(codeAction, diagnostic); } From 5e841a6f31d8eb34a69d1cddb59f1b0e538f346e Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 15:47:45 +0200 Subject: [PATCH 09/42] Check for interface implementation instead of signature match --- .../DoNotGuardSetAddOrRemoveByContains.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index f30fa90d11..126b5b2865 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -129,7 +129,7 @@ private static bool TryExtractContainsInvocation( return false; } - return DoesSignatureMatch(containsInvocation.TargetMethod, containsMethod); + return DoesImplementInterfaceMethod(containsInvocation.TargetMethod, containsMethod); } private static bool TryExtractAddOrRemoveInvocation( @@ -150,8 +150,8 @@ private static bool TryExtractAddOrRemoveInvocation( switch (firstOperation) { case IInvocationOperation invocation: - if ((containsNegated && DoesSignatureMatch(invocation.TargetMethod, addMethod)) || - (!containsNegated && DoesSignatureMatch(invocation.TargetMethod, removeMethod))) + if ((containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, addMethod)) || + (!containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, removeMethod))) { addOrRemoveInvocation = invocation; return true; @@ -162,8 +162,8 @@ private static bool TryExtractAddOrRemoveInvocation( var nestedAddOrRemove = expressionStatement.Children .OfType() .FirstOrDefault(i => containsNegated ? - DoesSignatureMatch(i.TargetMethod, addMethod) : - DoesSignatureMatch(i.TargetMethod, removeMethod)); + DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : + DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); if (nestedAddOrRemove != null) { @@ -189,12 +189,9 @@ private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation }; } - private static bool DoesSignatureMatch(IMethodSymbol suspected, IMethodSymbol comparator) + private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSymbol interfaceMethod) { - return suspected.OriginalDefinition.ReturnType.Name == comparator.ReturnType.Name - && suspected.Name == comparator.Name - && suspected.Parameters.Length == comparator.Parameters.Length - && suspected.Parameters.Zip(comparator.Parameters, (p1, p2) => p1.OriginalDefinition.Type.Name == p2.Type.Name).All(isParameterEqual => isParameterEqual); + return method.IsImplementationOfInterfaceMethod(method.Parameters[0].Type, interfaceMethod.ContainingType, interfaceMethod.Name); } } } From 07943c49cfb5078d3bc0ff65aeaab87d9f589163 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 16:13:57 +0200 Subject: [PATCH 10/42] Revert "Remove call to First with call to indexer" This reverts commit 68c210f69e190a99c69dbe6a2cfed0594d7cec0e. --- .../DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs index ed519c8b05..96e744a1e7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKey.Fixer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis; @@ -29,7 +30,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - var diagnostic = context.Diagnostics[0]; + var diagnostic = context.Diagnostics.First(); var conditionalOperationSpan = diagnostic.AdditionalLocations[0]; var childLocation = diagnostic.AdditionalLocations[1]; if (root.FindNode(conditionalOperationSpan.SourceSpan) is not SyntaxNode conditionalSyntax || From 9b43e006bd6dd14c0bfdb60da3aebc11239f118a Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 14 Jul 2023 16:15:08 +0200 Subject: [PATCH 11/42] Remove call to First with call to indexer --- .../Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs index de479cc237..3679835e6f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; -using System.Linq; using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis; @@ -30,7 +29,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - var diagnostic = context.Diagnostics.First(); + var diagnostic = context.Diagnostics[0]; var conditionalLocation = diagnostic.AdditionalLocations[0]; var childLocation = diagnostic.AdditionalLocations[1]; From a8e4875e5e8ee150650c63b622f8afeeab93705e Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 15 Jul 2023 00:03:29 +0200 Subject: [PATCH 12/42] Add additional tests --- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 189 +++++++++++++++++- 1 file changed, 182 insertions(+), 7 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index bb8e51198f..5ca3219c80 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -241,7 +241,7 @@ public MyClass() } [Fact] - public async Task AddAdditionalStatements_ReportsDiagnostic_CS() + public async Task AddWithAdditionalStatements_ReportsDiagnostic_CS() { string source = CSUsings + CSNamespaceAndClassStart + @" private readonly HashSet MySet = new HashSet(); @@ -256,11 +256,11 @@ public MyClass() } " + CSNamespaceAndClassEnd; - await VerifyCS.VerifyAnalyzerAsync(source); + await VerifyCS.VerifyCodeFixAsync(source, source); } [Fact] - public async Task RemoveAdditionalStatements_ReportsDiagnostic_CS() + public async Task RemoveWithAdditionalStatements_ReportsDiagnostic_CS() { string source = CSUsings + CSNamespaceAndClassStart + @" private readonly HashSet MySet = new HashSet(); @@ -275,7 +275,7 @@ public MyClass() } " + CSNamespaceAndClassEnd; - await VerifyCS.VerifyAnalyzerAsync(source); + await VerifyCS.VerifyCodeFixAsync(source, source); } [Fact] @@ -407,6 +407,66 @@ Namespace Testopolis Public Class SomeClass Public MySet As New HashSet(Of String)() + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then MySet.Add(""Item"") + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + MySet.Add(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveIsTheOnlyStatement_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If ([|MySet.Contains(""Item"")|]) Then MySet.Remove(""Item"") + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + MySet.Remove(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task AddIsTheOnlyStatementInBlock_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + Public Sub New() If Not [|MySet.Contains(""Item"")|] Then MySet.Add(""Item"") @@ -431,7 +491,7 @@ End Class } [Fact] - public async Task RemoveIsTheOnlyStatement_OffersFixer_VB() + public async Task RemoveIsTheOnlyStatementInBlock_OffersFixer_VB() { string source = @" " + VBUsings + @" @@ -462,6 +522,66 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } + [Fact] + public async Task AddHasElseStatement_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then MySet.Add(""Item"") Else Throw new Exception(""Item already exists"") + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not MySet.Add(""Item"") Then Throw new Exception(""Item already exists"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveHasElseStatement_OffersFixer_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If [|MySet.Contains(""Item"")|] Then MySet.Remove(""Item"") Else Throw new Exception(""Item doesn't exist"") + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not MySet.Remove(""Item"") Then Throw new Exception(""Item doesn't exist"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + [Fact] public async Task AddHasElseBlock_OffersFixer_VB() { @@ -571,7 +691,28 @@ End Class } [Fact] - public async Task AdditionalStatements_ReportsDiagnostic_VB() + public async Task AddWithAdditionalStatements_ReportsDiagnostic_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then + MySet.Add(""Item"") + Console.WriteLine() + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWithAdditionalStatements_ReportsDiagnostic_VB() { string source = @" " + VBUsings + @" @@ -588,7 +729,41 @@ End Sub End Class End Namespace"; - await VerifyVB.VerifyAnalyzerAsync(source); + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task TriviaIsPreserved_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + ' reticulates the splines + If ([|MySet.Contains(""Item"")|]) Then + MySet.Remove(""Item"") + End If + End Sub + End Class +End Namespace"; + + string fixedSource = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + ' reticulates the splines + MySet.Remove(""Item"") + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } [Fact] From 29a46302178d0fd57b9785076472ce4867168658 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 15 Jul 2023 00:21:28 +0200 Subject: [PATCH 13/42] Fix ifs with additional statements --- .../CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs | 9 ++++----- .../BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index 3a97d12228..b4e8611c26 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -16,11 +16,10 @@ public sealed class CSharpDoNotGuardSetAddOrRemoveByContainsFixer : DoNotGuardSe { protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax) { - if (conditionalSyntax is ConditionalExpressionSyntax conditionalExpressionSyntax) - return conditionalExpressionSyntax.WhenTrue.ChildNodes().Count() == 1; - - if (conditionalSyntax is IfStatementSyntax) - return true; + if (conditionalSyntax is IfStatementSyntax ifStatementSyntax) + { + return ifStatementSyntax.Statement.ChildNodes().Count() == 1; + } return false; } diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb index b8fe6fd10a..e3f3a56ced 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -19,7 +19,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance End If If TypeOf conditionalSyntax Is MultiLineIfBlockSyntax Then - Return CType(conditionalSyntax, MultiLineIfBlockSyntax).IfStatement.ChildNodes().Count() = 1 + Return CType(conditionalSyntax, MultiLineIfBlockSyntax).Statements.Count() = 1 End If Return False From 468274e522d7b0d002584b441a9658f74e37945d Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 15 Jul 2023 00:22:06 +0200 Subject: [PATCH 14/42] Fix single line ifs in VB --- .../BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb index e3f3a56ced..c2dd7f6519 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -14,15 +14,11 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance Inherits DoNotGuardSetAddOrRemoveByContainsFixer Protected Overrides Function SyntaxSupportedByFixer(conditionalSyntax As SyntaxNode) As Boolean - If TypeOf conditionalSyntax Is IfStatementSyntax Then - Return True - End If - If TypeOf conditionalSyntax Is MultiLineIfBlockSyntax Then Return CType(conditionalSyntax, MultiLineIfBlockSyntax).Statements.Count() = 1 End If - Return False + Return TypeOf conditionalSyntax Is SingleLineIfStatementSyntax End Function Protected Overrides Function ReplaceConditionWithChild(document As Document, root As SyntaxNode, conditionalOperationNode As SyntaxNode, childOperationNode As SyntaxNode) As Document From 326481cd6541cfa05e0a6171b7dabaa0ee0ff78e Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 15 Jul 2023 00:22:29 +0200 Subject: [PATCH 15/42] Add fixer case for single line ifs in VB --- ...icDoNotGuardSetAddOrRemoveByContains.Fixer.vb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb index c2dd7f6519..720d839851 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -37,7 +37,21 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance .WithElseBlock(Nothing) _ .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) Else - newConditionNode = newConditionNode.WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) + ' if there's an else statement, negate the condition and replace the single true statement with it + Dim singleLineIfBlockSyntax = TryCast(conditionalOperationNode, SingleLineIfStatementSyntax) + If singleLineIfBlockSyntax?.ElseClause?.ChildNodes().Any() Then + Dim generator = SyntaxGenerator.GetGenerator(document) + Dim negatedExpression = generator.LogicalNotExpression(CType(childOperationNode, ExpressionStatementSyntax).Expression.WithoutTrivia()) + + Dim oldElseBlock = singleLineIfBlockSyntax.ElseClause.Statements + + newConditionNode = singleLineIfBlockSyntax.WithCondition(CType(negatedExpression, ExpressionSyntax)) _ + .WithStatements(oldElseBlock) _ + .WithElseClause(Nothing) _ + .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) + Else + newConditionNode = newConditionNode.WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) + End If End If Dim newRoot = root.ReplaceNode(conditionalOperationNode, newConditionNode) From 533cd7814304bf5d42a33c476d5b19d19b77d236 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 02:01:47 +0200 Subject: [PATCH 16/42] Apply suggestions from code review Co-authored-by: Buyaa Namnan --- .../MicrosoftNetCoreAnalyzersResources.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index c92c308867..5a6e3e2b1a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1510,10 +1510,10 @@ Unnecessary call to 'Dictionary.ContainsKey(key)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' Unnecessary call to 'Set.Contains(item)' From cf4957820e06c8f440c382ac015c590a1b10abfd Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 00:25:18 +0200 Subject: [PATCH 17/42] CA1865: Add tests for other set types --- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 5ca3219c80..649d6c0a0f 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -818,6 +818,55 @@ private void RemoveItem(HashSet setParam) await VerifyCS.VerifyAnalyzerAsync(source); } + [Theory] + [InlineData("System.Collections.Generic.SortedSet", "Add")] + [InlineData("System.Collections.Generic.SortedSet", "Remove")] + [InlineData("System.Collections.Generic.HashSet", "Add")] + [InlineData("System.Collections.Generic.HashSet", "Remove")] + [InlineData("System.Collections.Immutable.ImmutableHashSet.Builder", "Add")] + [InlineData("System.Collections.Immutable.ImmutableHashSet.Builder", "Remove")] + [InlineData("System.Collections.Immutable.ImmutableSortedSet.Builder", "Add")] + [InlineData("System.Collections.Immutable.ImmutableSortedSet.Builder", "Remove")] + public async Task SupportsSetsWithContainsReturningBool_OffersFixer_CS(string setType, string method) + { + string source = CSUsings + CSNamespaceAndClassStart + $@" + {SetFieldDeclaration(setType)} + + public MyClass() + {{ + if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) + MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + $@" + {SetFieldDeclaration(setType)} + + public MyClass() + {{ + MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("System.Collections.Immutable.ImmutableHashSet", "Add")] + [InlineData("System.Collections.Immutable.ImmutableHashSet", "Remove")] + [InlineData("System.Collections.Immutable.ImmutableSortedSet", "Add")] + [InlineData("System.Collections.Immutable.ImmutableSortedSet", "Remove")] + public async Task SupportsSetWithContainsReturningGenericType_ReportsDiagnostic_CS(string setType, string method) + { + string source = CSUsings + CSNamespaceAndClassStart + $@" + private {setType} MySet = {setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.Create(); + + public MyClass() + {{ + if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) + MySet = MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } #endregion #region Helpers @@ -836,6 +885,13 @@ public class MyClass private const string VBUsings = @"Imports System Imports System.Collections.Generic"; + + private string SetFieldDeclaration(string setType) + { + return setType.Contains("Builder", StringComparison.Ordinal) + ? $"private readonly {setType} MySet = {setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.CreateBuilder();" + : $"private readonly {setType} MySet = new {setType}();"; + } #endregion } } From d063408eda924e96933f066412bf0e020457d65b Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 00:27:42 +0200 Subject: [PATCH 18/42] CA1865: Check descendants instead of switch --- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 7 +- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 4 +- .../DoNotGuardSetAddOrRemoveByContains.cs | 35 +-------- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 77 +++++++++++++++++++ ...oNotGuardSetAddOrRemoveByContains.Fixer.vb | 6 +- 5 files changed, 94 insertions(+), 35 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index b4e8611c26..2a59ad2f4c 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -14,8 +14,13 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Performance [ExportCodeFixProvider(LanguageNames.CSharp), Shared] public sealed class CSharpDoNotGuardSetAddOrRemoveByContainsFixer : DoNotGuardSetAddOrRemoveByContainsFixer { - protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax) + protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax, SyntaxNode childStatementSyntax) { + if (childStatementSyntax is not ExpressionStatementSyntax) + { + return false; + } + if (conditionalSyntax is IfStatementSyntax ifStatementSyntax) { return ifStatementSyntax.Statement.ChildNodes().Count() == 1; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs index 3679835e6f..b4afed3868 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -40,7 +40,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) } // We only offer a fixer if the conditonal true branch has a single statement, either 'Add' or 'Delete' - if (!SyntaxSupportedByFixer(conditionalSyntax)) + if (!SyntaxSupportedByFixer(conditionalSyntax, childStatementSyntax)) { return; } @@ -52,7 +52,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); } - protected abstract bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax); + protected abstract bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax, SyntaxNode childStatementSyntax); protected abstract Document ReplaceConditionWithChild(Document document, SyntaxNode root, SyntaxNode conditionalOperationNode, diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 126b5b2865..5951dd5047 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -139,42 +139,15 @@ private static bool TryExtractAddOrRemoveInvocation( bool containsNegated, [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) { - addOrRemoveInvocation = null; - var firstOperation = operations.FirstOrDefault(); - - if (firstOperation is null) - { - return false; - } - - switch (firstOperation) - { - case IInvocationOperation invocation: - if ((containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, addMethod)) || - (!containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, removeMethod))) - { - addOrRemoveInvocation = invocation; - return true; - } - - break; - case IExpressionStatementOperation expressionStatement: - var nestedAddOrRemove = expressionStatement.Children + addOrRemoveInvocation = operations + .FirstOrDefault() + ?.DescendantsAndSelf() .OfType() .FirstOrDefault(i => containsNegated ? DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); - if (nestedAddOrRemove != null) - { - addOrRemoveInvocation = nestedAddOrRemove; - return true; - } - - break; - } - - return false; + return addOrRemoveInvocation is not null; } private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 649d6c0a0f..6ea73bd735 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -1,5 +1,6 @@ // 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.Threading.Tasks; using Test.Utilities; using Xunit; @@ -278,6 +279,42 @@ public MyClass() await VerifyCS.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddWithVariableAssignment_ReportsDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if (![|MySet.Contains(""Item"")|]) + { + bool result = MySet.Add(""Item""); + } + } + " + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWithVariableAssignment_ReportsDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + if ([|MySet.Contains(""Item"")|]) + { + bool result = MySet.Remove(""Item""); + } + } + " + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task AddWithNonNegatedContains_NoDiagnostics_CS() { @@ -690,6 +727,46 @@ End Class await VerifyVB.VerifyAnalyzerAsync(source); } + [Fact] + public async Task AddWithVariableAssignment_ReportsDiagnostic_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If Not [|MySet.Contains(""Item"")|] Then + Dim result = MySet.Add(""Item"") + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWithVariableAssignment_ReportsDiagnostic_VB() + { + string source = @" +" + VBUsings + @" +Namespace Testopolis + Public Class SomeClass + Public MySet As New HashSet(Of String)() + + Public Sub New() + If [|MySet.Contains(""Item"")|] Then + Dim result = MySet.Remove(""Item"") + End If + End Sub + End Class +End Namespace"; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task AddWithAdditionalStatements_ReportsDiagnostic_VB() { diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb index 720d839851..e17a95c04b 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -13,7 +13,11 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance Public NotInheritable Class BasicDoNotGuardSetAddOrRemoveByContainsFixer Inherits DoNotGuardSetAddOrRemoveByContainsFixer - Protected Overrides Function SyntaxSupportedByFixer(conditionalSyntax As SyntaxNode) As Boolean + Protected Overrides Function SyntaxSupportedByFixer(conditionalSyntax As SyntaxNode, childStatementSyntax As SyntaxNode) As Boolean + If TypeOf childStatementSyntax IsNot ExpressionStatementSyntax Then + Return False + End If + If TypeOf conditionalSyntax Is MultiLineIfBlockSyntax Then Return CType(conditionalSyntax, MultiLineIfBlockSyntax).Statements.Count() = 1 End If From d7c7c54e1c1ee25170fe606efbe2cab11d83209f Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 01:39:33 +0200 Subject: [PATCH 19/42] CA1865: Do not fire when arguments are different --- .../DoNotGuardSetAddOrRemoveByContains.cs | 43 ++++++- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 112 ++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 5951dd5047..eca88ded25 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -70,7 +70,8 @@ private static void OnConditional(OperationAnalysisContext context, IMethodSymbo return; } - if (!AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation)) + if (!AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation) || + !AreInvocationArgumentsEqual(containsInvocation, addOrRemoveInvocation)) { return; } @@ -162,6 +163,46 @@ private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation }; } + // Checks if invocation argument values are equal + // 1. Not equal: Contains(item) != Add(otherItem), Contains("const") != Add("other const") + // 2. Identical: Contains(item) == Add(item), Contains("const") == Add("const") + private static bool AreInvocationArgumentsEqual(IInvocationOperation invocation1, IInvocationOperation invocation2) + { + return invocation1.Arguments + .Zip(invocation2.Arguments, (a1, a2) => IsArgumentValueEqual(a1.Value, a2.Value)) + .All(argumentsEqual => argumentsEqual); + } + + private static bool IsArgumentValueEqual(IOperation targetArg, IOperation valueArg) + { + // Check if arguments are identical constant/local/parameter/field reference operations. + if (targetArg.Kind != valueArg.Kind) + { + return false; + } + + if (targetArg.ConstantValue.HasValue != valueArg.ConstantValue.HasValue) + { + return false; + } + + if (targetArg.ConstantValue.HasValue) + { + return Equals(targetArg.ConstantValue.Value, valueArg.ConstantValue.Value); + } + + return targetArg switch + { + ILocalReferenceOperation targetLocalReference => + SymbolEqualityComparer.Default.Equals(targetLocalReference.Local, ((ILocalReferenceOperation)valueArg).Local), + IParameterReferenceOperation targetParameterReference => + SymbolEqualityComparer.Default.Equals(targetParameterReference.Parameter, ((IParameterReferenceOperation)valueArg).Parameter), + IFieldReferenceOperation fieldParameterReference => + SymbolEqualityComparer.Default.Equals(fieldParameterReference.Member, ((IFieldReferenceOperation)valueArg).Member), + _ => false, + }; + } + private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSymbol interfaceMethod) { return method.IsImplementationOfInterfaceMethod(method.Parameters[0].Type, interfaceMethod.ContainingType, interfaceMethod.Name); diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 6ea73bd735..f53e1ec690 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -895,6 +895,118 @@ private void RemoveItem(HashSet setParam) await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task ContainsAndAddCalledWithDifferentArguments_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + private const string OtherItemField = ""Other Item""; + + public string OtherItemProperty { get; } = ""Other Item""; + + public MyClass(string otherItemParameter) + { + if (!MySet.Contains(""Item"")) + MySet.Add(""Other Item""); + + if (!MySet.Contains(""Item"")) + MySet.Add(otherItemParameter); + + if (!MySet.Contains(""Item"")) + MySet.Add(OtherItemField); + + if (!MySet.Contains(""Item"")) + MySet.Add(OtherItemProperty); + + string otherItemLocal = ""Other Item""; + if (!MySet.Contains(""Item"")) + MySet.Add(otherItemLocal); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task ContainsAndAddCalledWithSameArgumentsFields_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + private const string FieldItem = ""Item""; + + public MyClass() + { + if (![|MySet.Contains(FieldItem)|]) + { + MySet.Add(FieldItem); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + private const string FieldItem = ""Item""; + + public MyClass() + { + MySet.Add(FieldItem); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ContainsAndAddCalledWithSameArgumentsLocals_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + const string LocalItem = ""Item""; + + if (![|MySet.Contains(LocalItem)|]) + { + MySet.Add(LocalItem); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + const string LocalItem = ""Item""; + + MySet.Add(LocalItem); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ContainsAndAddCalledWithSameArgumentsParameters_OffersFixer_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass(string parameterItem) + { + if (![|MySet.Contains(parameterItem)|]) + { + MySet.Add(parameterItem); + } + }" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass(string parameterItem) + { + MySet.Add(parameterItem); + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + [Theory] [InlineData("System.Collections.Generic.SortedSet", "Add")] [InlineData("System.Collections.Generic.SortedSet", "Remove")] From 4c71d4c00f57a5d473df8920a9e87f862b95520d Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 02:05:30 +0200 Subject: [PATCH 20/42] CA1865: Change fixer title --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 5a6e3e2b1a..e559ab43cd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1516,7 +1516,7 @@ Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call Remove unnecessary call From ed09398579fe2dfa77f2278bbce4de57f2e5e7e9 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 02:07:51 +0200 Subject: [PATCH 21/42] CA1865: Update resources --- .../xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.de.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.es.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.it.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 ++++++------ .../xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 ++++++------ .../MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 ++++++------ .../MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 ++++++------ .../Microsoft.CodeAnalysis.NetAnalyzers.md | 4 ++-- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 4 ++-- 15 files changed, 82 insertions(+), 82 deletions(-) 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 9e8758a4d1..4eb038070a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -764,18 +764,18 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 069cab97d1..127ed05163 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -764,18 +764,18 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 407b5850f3..0c6c21fc40 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -764,18 +764,18 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 04e33621e9..1a3b71b02a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -764,18 +764,18 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 27ffaf6701..031bc02ca1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -764,18 +764,18 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 6afc7d7d30..3a5a532ee8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -764,18 +764,18 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 d24529f789..6c78a5d61c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -764,18 +764,18 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 d62e1b03d1..8b3ea95b3c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -764,18 +764,18 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 72414df0f2..fd6ffd941b 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 @@ -764,18 +764,18 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 ac8ec6ab40..649a2ae97e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -764,18 +764,18 @@ Widening and user defined conversions are not supported with generic types. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 8c8f60121d..f10ef8c0b7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -764,18 +764,18 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 d2022c75d0..93802f7803 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 @@ -764,18 +764,18 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call 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 efb44dd723..69f78b4353 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 @@ -764,18 +764,18 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. + Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' - Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)' + Do not guard '{0}' with '{1}' + Do not guard '{0}' with '{1}' - Unnecessary call to 'Set.Contains(item)' - Unnecessary call to 'Set.Contains(item)' + Remove unnecessary call + Remove unnecessary call diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 32adc1c000..3ee7c2baf7 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1740,9 +1740,9 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try |CodeFix|True| --- -## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Unnecessary call to 'Set.Contains(item)' +## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Remove unnecessary call -Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed. +Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. |Item|Value| |-|-| diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index e610157ed1..0a65dee967 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3217,8 +3217,8 @@ }, "CA1865": { "id": "CA1865", - "shortDescription": "Unnecessary call to 'Set.Contains(item)'", - "fullDescription": "Do not guard 'Set.Add(item)' or 'Set.Remove(item)' with 'Set.Contains(item)'. The former two already check whether the item exists and will return if it was added or removed.", + "shortDescription": "Remove unnecessary call", + "fullDescription": "Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed.", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", "properties": { From 4f62beb069a4f85b95ac9d80e67828f8a7e98b31 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 03:04:44 +0200 Subject: [PATCH 22/42] WIP: Try to handle IImmutableSet Add and Remove --- .../DoNotGuardSetAddOrRemoveByContains.cs | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index eca88ded25..0cb0b44a12 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -48,15 +48,21 @@ public override void Initialize(AnalysisContext context) private void OnCompilationStart(CompilationStartAnalysisContext context) { - if (!TryGetRequiredMethods(context.Compilation, out var containsMethod, out var addMethod, out var removeMethod)) + if (!TryGetRequiredMethods(context.Compilation, out var containsMethod, out var addMethod, out var removeMethod, out var addMethodImmutableSet, out var removeMethodImmutableSet)) { return; } - context.RegisterOperationAction(context => OnConditional(context, containsMethod, addMethod, removeMethod), OperationKind.Conditional); + context.RegisterOperationAction(context => OnConditional(context, containsMethod, addMethod, removeMethod, addMethodImmutableSet, removeMethodImmutableSet), OperationKind.Conditional); } - private static void OnConditional(OperationAnalysisContext context, IMethodSymbol containsMethod, IMethodSymbol addMethod, IMethodSymbol removeMethod) + private static void OnConditional( + OperationAnalysisContext context, + IMethodSymbol containsMethod, + IMethodSymbol addMethod, + IMethodSymbol removeMethod, + IMethodSymbol? addMethodImmutableSet, + IMethodSymbol? removeMethodImmutableSet) { var conditional = (IConditionalOperation)context.Operation; @@ -67,7 +73,12 @@ private static void OnConditional(OperationAnalysisContext context, IMethodSymbo if (!TryExtractAddOrRemoveInvocation(conditional.WhenTrue.Children, addMethod, removeMethod, containsNegated, out var addOrRemoveInvocation)) { - return; + if (addMethodImmutableSet is null || + removeMethodImmutableSet is null || + !TryExtractAddOrRemoveInvocation(conditional.WhenTrue.Children, addMethodImmutableSet, removeMethodImmutableSet, containsNegated, out addOrRemoveInvocation)) + { + return; + } } if (!AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation) || @@ -87,8 +98,13 @@ private static bool TryGetRequiredMethods( Compilation compilation, [NotNullWhen(true)] out IMethodSymbol? containsMethod, [NotNullWhen(true)] out IMethodSymbol? addMethod, - [NotNullWhen(true)] out IMethodSymbol? removeMethod) + [NotNullWhen(true)] out IMethodSymbol? removeMethod, + out IMethodSymbol? addMethodImmutableSet, + out IMethodSymbol? removeMethodImmutableSet) { + addMethodImmutableSet = null; + removeMethodImmutableSet = null; + var iSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericISet1); var iCollectionType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1); @@ -105,6 +121,15 @@ private static bool TryGetRequiredMethods( containsMethod = iCollectionType.GetMembers(Contains).OfType().FirstOrDefault(); removeMethod = iCollectionType.GetMembers(Remove).OfType().FirstOrDefault(); + // Check for Add and Remove from IImmutableSet. This will not lead to a code fix. + var iImmutableSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsImmutableIImmutableSet1); + + if (iImmutableSetType is not null) + { + addMethodImmutableSet = iImmutableSetType.GetMembers(Add).OfType().FirstOrDefault(); + removeMethodImmutableSet = iImmutableSetType.GetMembers(Remove).OfType().FirstOrDefault(); + } + return containsMethod is not null && addMethod is not null && removeMethod is not null; } @@ -143,10 +168,10 @@ private static bool TryExtractAddOrRemoveInvocation( addOrRemoveInvocation = operations .FirstOrDefault() ?.DescendantsAndSelf() - .OfType() - .FirstOrDefault(i => containsNegated ? - DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : - DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); + .OfType() + .FirstOrDefault(i => containsNegated ? + DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : + DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); return addOrRemoveInvocation is not null; } From 315e2c56ac699b6708104aafec487fa614626661 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 19 Jul 2023 23:17:23 +0200 Subject: [PATCH 23/42] Apply suggestions from code review --- .../DoNotGuardSetAddOrRemoveByContains.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 0cb0b44a12..547a189a7b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -104,30 +104,41 @@ private static bool TryGetRequiredMethods( { addMethodImmutableSet = null; removeMethodImmutableSet = null; + containsMethod = null; + addMethod = null; + removeMethod = null; var iSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericISet1); var iCollectionType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1); if (iSetType is null || iCollectionType is null) { - containsMethod = null; - addMethod = null; - removeMethod = null; - return false; } addMethod = iSetType.GetMembers(Add).OfType().FirstOrDefault(); - containsMethod = iCollectionType.GetMembers(Contains).OfType().FirstOrDefault(); - removeMethod = iCollectionType.GetMembers(Remove).OfType().FirstOrDefault(); + foreach (var method in iCollectionType.GetMembers().OfType()) + { + switch (method.Name) + { + case Remove: removeMethod = method; break; + case Contains: containsMethod = method; break; + } + } // Check for Add and Remove from IImmutableSet. This will not lead to a code fix. var iImmutableSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsImmutableIImmutableSet1); if (iImmutableSetType is not null) { - addMethodImmutableSet = iImmutableSetType.GetMembers(Add).OfType().FirstOrDefault(); - removeMethodImmutableSet = iImmutableSetType.GetMembers(Remove).OfType().FirstOrDefault(); + foreach (var method in iImmutableSetType.GetMembers().OfType()) + { + switch (method.Name) + { + case Add: addMethodImmutableSet = method; break; + case Remove: removeMethodImmutableSet = method; break; + } + } } return containsMethod is not null && addMethod is not null && removeMethod is not null; From a0f8d26d441afb805277101c72fc2dfe26122acc Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 20 Jul 2023 03:09:55 +0200 Subject: [PATCH 24/42] CA1865: Use original definitions to filter methods --- .../Performance/DoNotGuardSetAddOrRemoveByContains.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 547a189a7b..4836e967bc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -241,7 +241,11 @@ private static bool IsArgumentValueEqual(IOperation targetArg, IOperation valueA private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSymbol interfaceMethod) { - return method.IsImplementationOfInterfaceMethod(method.Parameters[0].Type, interfaceMethod.ContainingType, interfaceMethod.Name); + var typedInterface = interfaceMethod.ContainingType.Construct(method.Parameters[0].Type); + var typedInterfaceMethod = typedInterface.GetMembers(interfaceMethod.Name).FirstOrDefault(); + + // Check against all original definitions to also cover external interface implementations + return method.GetOriginalDefinitions().Any(definition => SymbolEqualityComparer.Default.Equals(definition, typedInterfaceMethod)); } } } From f22e2dc55fe7b4b7b596c3fb1e815c5ae41a34bb Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 21:22:27 +0200 Subject: [PATCH 25/42] Check parameter length before using them --- .../Performance/DoNotGuardSetAddOrRemoveByContains.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 4836e967bc..ea5b66ceaa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -241,6 +241,11 @@ private static bool IsArgumentValueEqual(IOperation targetArg, IOperation valueA private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSymbol interfaceMethod) { + if (method.Parameters.Length != 1) + { + return false; + } + var typedInterface = interfaceMethod.ContainingType.Construct(method.Parameters[0].Type); var typedInterfaceMethod = typedInterface.GetMembers(interfaceMethod.Name).FirstOrDefault(); From efe299362a536940b5078246e046941fb741a891 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 21:26:03 +0200 Subject: [PATCH 26/42] Only check child operations instead of descendants This ensures that nested Add or Remove calls are not flagged. --- .../DoNotGuardSetAddOrRemoveByContains.cs | 52 +++++++++++++++++-- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 21 ++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index ea5b66ceaa..f7c652f26b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -176,15 +176,59 @@ private static bool TryExtractAddOrRemoveInvocation( bool containsNegated, [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) { - addOrRemoveInvocation = operations - .FirstOrDefault() - ?.DescendantsAndSelf() + addOrRemoveInvocation = null; + var firstOperation = operations.FirstOrDefault(); + + if (firstOperation is null) + { + return false; + } + + switch (firstOperation) + { + case IInvocationOperation invocation: + if ((containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, addMethod)) || + (!containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, removeMethod))) + { + addOrRemoveInvocation = invocation; + return true; + } + + break; + + case ISimpleAssignmentOperation: + case IExpressionStatementOperation: + var firstChildAddOrRemove = firstOperation.Children + .OfType() + .FirstOrDefault(i => containsNegated ? + DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : + DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); + + if (firstChildAddOrRemove != null) + { + addOrRemoveInvocation = firstChildAddOrRemove; + return true; + } + + break; + + case IVariableDeclarationGroupOperation variableDeclarationGroup: + var firstDescendantAddOrRemove = firstOperation.Descendants() .OfType() .FirstOrDefault(i => containsNegated ? DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); - return addOrRemoveInvocation is not null; + if (firstDescendantAddOrRemove != null) + { + addOrRemoveInvocation = firstDescendantAddOrRemove; + return true; + } + + break; + } + + return false; } private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index f53e1ec690..4feef0ba93 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -408,6 +408,27 @@ public MyClass() await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task NestedConditional_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + private readonly HashSet OtherSet = new HashSet(); + + public MyClass() + { + if (MySet.Contains(""Item"")) + { + if (OtherSet.Contains(""Item"")) + { + MySet.Remove(""Item""); + } + } + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + [Fact] public async Task TriviaIsPreserved_CS() { From 7c68257b5c822c7bb5722f3dba65b761b80c5767 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 21:26:59 +0200 Subject: [PATCH 27/42] Fix diagnostic message --- .../DoNotGuardSetAddOrRemoveByContains.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index f7c652f26b..73d1204d8d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -91,7 +91,18 @@ removeMethodImmutableSet is null || locations.Add(conditional.Syntax.GetLocation()); locations.Add(addOrRemoveInvocation.Syntax.Parent!.GetLocation()); - context.ReportDiagnostic(containsInvocation.CreateDiagnostic(Rule, additionalLocations: locations.ToImmutable(), null)); + var symbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat + .WithParameterOptions(SymbolDisplayParameterOptions.IncludeType) + .WithGenericsOptions(SymbolDisplayGenericsOptions.None) + .WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType) + .WithKindOptions(SymbolDisplayKindOptions.None); + + context.ReportDiagnostic(containsInvocation.CreateDiagnostic( + Rule, + additionalLocations: locations.ToImmutable(), + properties: null, + addOrRemoveInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat), + containsInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat))); } private static bool TryGetRequiredMethods( From 860f21706663fce70aa358e50254b302b7e1990a Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 21:28:41 +0200 Subject: [PATCH 28/42] Also flag when interface types are used --- .../DoNotGuardSetAddOrRemoveByContains.cs | 13 +-- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 94 ++++++++++++++----- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 73d1204d8d..a6f642aa4a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -225,10 +225,10 @@ private static bool TryExtractAddOrRemoveInvocation( case IVariableDeclarationGroupOperation variableDeclarationGroup: var firstDescendantAddOrRemove = firstOperation.Descendants() - .OfType() - .FirstOrDefault(i => containsNegated ? - DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : - DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); + .OfType() + .FirstOrDefault(i => containsNegated ? + DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : + DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); if (firstDescendantAddOrRemove != null) { @@ -304,8 +304,9 @@ private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSy var typedInterface = interfaceMethod.ContainingType.Construct(method.Parameters[0].Type); var typedInterfaceMethod = typedInterface.GetMembers(interfaceMethod.Name).FirstOrDefault(); - // Check against all original definitions to also cover external interface implementations - return method.GetOriginalDefinitions().Any(definition => SymbolEqualityComparer.Default.Equals(definition, typedInterfaceMethod)); + // Also check against all original definitions to also cover external interface implementations + return SymbolEqualityComparer.Default.Equals(method, typedInterfaceMethod) || + method.GetOriginalDefinitions().Any(definition => SymbolEqualityComparer.Default.Equals(definition, typedInterfaceMethod)); } } } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 4feef0ba93..33e75840db 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -1029,18 +1029,18 @@ public MyClass(string parameterItem) } [Theory] - [InlineData("System.Collections.Generic.SortedSet", "Add")] - [InlineData("System.Collections.Generic.SortedSet", "Remove")] - [InlineData("System.Collections.Generic.HashSet", "Add")] - [InlineData("System.Collections.Generic.HashSet", "Remove")] - [InlineData("System.Collections.Immutable.ImmutableHashSet.Builder", "Add")] - [InlineData("System.Collections.Immutable.ImmutableHashSet.Builder", "Remove")] - [InlineData("System.Collections.Immutable.ImmutableSortedSet.Builder", "Add")] - [InlineData("System.Collections.Immutable.ImmutableSortedSet.Builder", "Remove")] - public async Task SupportsSetsWithContainsReturningBool_OffersFixer_CS(string setType, string method) + [InlineData("SortedSet", "Add")] + [InlineData("SortedSet", "Remove")] + [InlineData("HashSet", "Add")] + [InlineData("HashSet", "Remove")] + [InlineData("ImmutableHashSet.Builder", "Add")] + [InlineData("ImmutableHashSet.Builder", "Remove")] + [InlineData("ImmutableSortedSet.Builder", "Add")] + [InlineData("ImmutableSortedSet.Builder", "Remove")] + public async Task SupportsSetsWithAddOrRemoveReturningBool_OffersFixer_CS(string setType, string method) { string source = CSUsings + CSNamespaceAndClassStart + $@" - {SetFieldDeclaration(setType)} + private readonly {setType} MySet = {SetCreationExpression(setType)} public MyClass() {{ @@ -1049,7 +1049,7 @@ public MyClass() }}" + CSNamespaceAndClassEnd; string fixedSource = CSUsings + CSNamespaceAndClassStart + $@" - {SetFieldDeclaration(setType)} + private readonly {setType} MySet = {SetCreationExpression(setType)} public MyClass() {{ @@ -1060,15 +1060,65 @@ public MyClass() } [Theory] - [InlineData("System.Collections.Immutable.ImmutableHashSet", "Add")] - [InlineData("System.Collections.Immutable.ImmutableHashSet", "Remove")] - [InlineData("System.Collections.Immutable.ImmutableSortedSet", "Add")] - [InlineData("System.Collections.Immutable.ImmutableSortedSet", "Remove")] - public async Task SupportsSetWithContainsReturningGenericType_ReportsDiagnostic_CS(string setType, string method) + [InlineData("ISet", "SortedSet", "Add")] + [InlineData("ISet", "SortedSet", "Remove")] + [InlineData("ISet", "HashSet", "Add")] + [InlineData("ISet", "HashSet", "Remove")] + [InlineData("ISet", "ImmutableHashSet.Builder", "Add")] + [InlineData("ISet", "ImmutableHashSet.Builder", "Remove")] + [InlineData("ISet", "ImmutableSortedSet.Builder", "Add")] + [InlineData("ISet", "ImmutableSortedSet.Builder", "Remove")] + public async Task SupportsSetsWithAddOrRemoveReturningBoolWithInterfaceType_OffersFixer_CS(string interfaceType, string concreteType, string method) + { + string source = CSUsings + CSNamespaceAndClassStart + $@" + private readonly {interfaceType} MySet = {SetCreationExpression(concreteType)} + + public MyClass() + {{ + if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) + MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + string fixedSource = CSUsings + CSNamespaceAndClassStart + $@" + private readonly {interfaceType} MySet = {SetCreationExpression(concreteType)} + + public MyClass() + {{ + MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("ImmutableHashSet", "Add")] + [InlineData("ImmutableHashSet", "Remove")] + [InlineData("ImmutableSortedSet", "Add")] + [InlineData("ImmutableSortedSet", "Remove")] + public async Task SupportsSetWithAddOrRemoveReturningGenericType_ReportsDiagnostic_CS(string setType, string method) { string source = CSUsings + CSNamespaceAndClassStart + $@" private {setType} MySet = {setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.Create(); + public MyClass() + {{ + if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) + MySet = MySet.{method}(""Item""); + }}" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("IImmutableSet", "ImmutableHashSet", "Add")] + [InlineData("IImmutableSet", "ImmutableHashSet", "Remove")] + [InlineData("IImmutableSet", "ImmutableSortedSet", "Add")] + [InlineData("IImmutableSet", "ImmutableSortedSet", "Remove")] + public async Task SupportsSetWithAddOrRemoveReturningGenericTypeWithInterfaceType_ReportsDiagnostic_CS(string interfaceType, string concreteType, string method) + { + string source = CSUsings + CSNamespaceAndClassStart + $@" + private {interfaceType} MySet = {concreteType[..concreteType.IndexOf('<', StringComparison.Ordinal)]}.Create(); + public MyClass() {{ if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) @@ -1081,7 +1131,8 @@ public MyClass() #region Helpers private const string CSUsings = @"using System; -using System.Collections.Generic;"; +using System.Collections.Generic; +using System.Collections.Immutable;"; private const string CSNamespaceAndClassStart = @"namespace Testopolis { @@ -1094,13 +1145,14 @@ public class MyClass }"; private const string VBUsings = @"Imports System -Imports System.Collections.Generic"; +Imports System.Collections.Generic +Imports System.Collections.Immutable"; - private string SetFieldDeclaration(string setType) + private string SetCreationExpression(string setType) { return setType.Contains("Builder", StringComparison.Ordinal) - ? $"private readonly {setType} MySet = {setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.CreateBuilder();" - : $"private readonly {setType} MySet = new {setType}();"; + ? $"{setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.CreateBuilder();" + : $"new {setType}();"; } #endregion } From 8b7a007e5076c57c75d8edb43e5e27fd826fd48d Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 21:35:29 +0200 Subject: [PATCH 29/42] Use helper class for required symbols --- .../DoNotGuardSetAddOrRemoveByContains.cs | 399 ++++++++++-------- 1 file changed, 217 insertions(+), 182 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index a6f642aa4a..ce78efc0dd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -48,198 +48,43 @@ public override void Initialize(AnalysisContext context) private void OnCompilationStart(CompilationStartAnalysisContext context) { - if (!TryGetRequiredMethods(context.Compilation, out var containsMethod, out var addMethod, out var removeMethod, out var addMethodImmutableSet, out var removeMethodImmutableSet)) + if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) { return; } - context.RegisterOperationAction(context => OnConditional(context, containsMethod, addMethod, removeMethod, addMethodImmutableSet, removeMethodImmutableSet), OperationKind.Conditional); - } - - private static void OnConditional( - OperationAnalysisContext context, - IMethodSymbol containsMethod, - IMethodSymbol addMethod, - IMethodSymbol removeMethod, - IMethodSymbol? addMethodImmutableSet, - IMethodSymbol? removeMethodImmutableSet) - { - var conditional = (IConditionalOperation)context.Operation; + context.RegisterOperationAction(OnConditional, OperationKind.Conditional); - if (!TryExtractContainsInvocation(conditional.Condition, containsMethod, out var containsInvocation, out var containsNegated)) + void OnConditional(OperationAnalysisContext context) { - return; - } + var conditional = (IConditionalOperation)context.Operation; - if (!TryExtractAddOrRemoveInvocation(conditional.WhenTrue.Children, addMethod, removeMethod, containsNegated, out var addOrRemoveInvocation)) - { - if (addMethodImmutableSet is null || - removeMethodImmutableSet is null || - !TryExtractAddOrRemoveInvocation(conditional.WhenTrue.Children, addMethodImmutableSet, removeMethodImmutableSet, containsNegated, out addOrRemoveInvocation)) + if (!symbols.HasApplicableContainsMethod(conditional.Condition, out var containsInvocation, out bool containsNegated) || + !symbols.HasApplicableAddOrRemoveMethod(conditional.WhenTrue.Children, containsNegated, out var addOrRemoveInvocation) || + !AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation) || + !AreInvocationArgumentsEqual(containsInvocation, addOrRemoveInvocation)) { return; } - } - - if (!AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation) || - !AreInvocationArgumentsEqual(containsInvocation, addOrRemoveInvocation)) - { - return; - } - - using var locations = ArrayBuilder.GetInstance(2); - locations.Add(conditional.Syntax.GetLocation()); - locations.Add(addOrRemoveInvocation.Syntax.Parent!.GetLocation()); - - var symbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat - .WithParameterOptions(SymbolDisplayParameterOptions.IncludeType) - .WithGenericsOptions(SymbolDisplayGenericsOptions.None) - .WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType) - .WithKindOptions(SymbolDisplayKindOptions.None); - - context.ReportDiagnostic(containsInvocation.CreateDiagnostic( - Rule, - additionalLocations: locations.ToImmutable(), - properties: null, - addOrRemoveInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat), - containsInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat))); - } - - private static bool TryGetRequiredMethods( - Compilation compilation, - [NotNullWhen(true)] out IMethodSymbol? containsMethod, - [NotNullWhen(true)] out IMethodSymbol? addMethod, - [NotNullWhen(true)] out IMethodSymbol? removeMethod, - out IMethodSymbol? addMethodImmutableSet, - out IMethodSymbol? removeMethodImmutableSet) - { - addMethodImmutableSet = null; - removeMethodImmutableSet = null; - containsMethod = null; - addMethod = null; - removeMethod = null; - - var iSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericISet1); - var iCollectionType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1); - - if (iSetType is null || iCollectionType is null) - { - return false; - } - - addMethod = iSetType.GetMembers(Add).OfType().FirstOrDefault(); - foreach (var method in iCollectionType.GetMembers().OfType()) - { - switch (method.Name) - { - case Remove: removeMethod = method; break; - case Contains: containsMethod = method; break; - } - } - - // Check for Add and Remove from IImmutableSet. This will not lead to a code fix. - var iImmutableSetType = WellKnownTypeProvider.GetOrCreate(compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsImmutableIImmutableSet1); - - if (iImmutableSetType is not null) - { - foreach (var method in iImmutableSetType.GetMembers().OfType()) - { - switch (method.Name) - { - case Add: addMethodImmutableSet = method; break; - case Remove: removeMethodImmutableSet = method; break; - } - } - } - - return containsMethod is not null && addMethod is not null && removeMethod is not null; - } - - private static bool TryExtractContainsInvocation( - IOperation condition, - IMethodSymbol containsMethod, - [NotNullWhen(true)] out IInvocationOperation? containsInvocation, - out bool containsNegated) - { - containsNegated = false; - containsInvocation = null; - - switch (condition.WalkDownParentheses()) - { - case IInvocationOperation invocation: - containsInvocation = invocation; - break; - case IUnaryOperation unaryOperation when unaryOperation.OperatorKind == UnaryOperatorKind.Not && unaryOperation.Operand is IInvocationOperation operand: - containsNegated = true; - containsInvocation = operand; - break; - default: - return false; - } - - return DoesImplementInterfaceMethod(containsInvocation.TargetMethod, containsMethod); - } - - private static bool TryExtractAddOrRemoveInvocation( - IEnumerable operations, - IMethodSymbol addMethod, - IMethodSymbol removeMethod, - bool containsNegated, - [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) - { - addOrRemoveInvocation = null; - var firstOperation = operations.FirstOrDefault(); - - if (firstOperation is null) - { - return false; - } - - switch (firstOperation) - { - case IInvocationOperation invocation: - if ((containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, addMethod)) || - (!containsNegated && DoesImplementInterfaceMethod(invocation.TargetMethod, removeMethod))) - { - addOrRemoveInvocation = invocation; - return true; - } - - break; - - case ISimpleAssignmentOperation: - case IExpressionStatementOperation: - var firstChildAddOrRemove = firstOperation.Children - .OfType() - .FirstOrDefault(i => containsNegated ? - DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : - DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); - - if (firstChildAddOrRemove != null) - { - addOrRemoveInvocation = firstChildAddOrRemove; - return true; - } - - break; - - case IVariableDeclarationGroupOperation variableDeclarationGroup: - var firstDescendantAddOrRemove = firstOperation.Descendants() - .OfType() - .FirstOrDefault(i => containsNegated ? - DoesImplementInterfaceMethod(i.TargetMethod, addMethod) : - DoesImplementInterfaceMethod(i.TargetMethod, removeMethod)); - - if (firstDescendantAddOrRemove != null) - { - addOrRemoveInvocation = firstDescendantAddOrRemove; - return true; - } - break; + using var locations = ArrayBuilder.GetInstance(2); + locations.Add(conditional.Syntax.GetLocation()); + locations.Add(addOrRemoveInvocation.Syntax.Parent!.GetLocation()); + + // Build custom format instead of CSharpShortErrorMessageFormat/VisualBasicShortErrorMessageFormat to prevent unhelpful messages for VB. + var symbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat + .WithParameterOptions(SymbolDisplayParameterOptions.IncludeType) + .WithGenericsOptions(SymbolDisplayGenericsOptions.None) + .WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType) + .WithKindOptions(SymbolDisplayKindOptions.None); + + context.ReportDiagnostic(containsInvocation.CreateDiagnostic( + Rule, + additionalLocations: locations.ToImmutable(), + properties: null, + addOrRemoveInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat), + containsInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat))); } - - return false; } private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) @@ -294,9 +139,9 @@ private static bool IsArgumentValueEqual(IOperation targetArg, IOperation valueA }; } - private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSymbol interfaceMethod) + private static bool DoesImplementInterfaceMethod(IMethodSymbol? method, IMethodSymbol? interfaceMethod) { - if (method.Parameters.Length != 1) + if (method is null || interfaceMethod is null || method.Parameters.Length != 1) { return false; } @@ -308,5 +153,195 @@ private static bool DoesImplementInterfaceMethod(IMethodSymbol method, IMethodSy return SymbolEqualityComparer.Default.Equals(method, typedInterfaceMethod) || method.GetOriginalDefinitions().Any(definition => SymbolEqualityComparer.Default.Equals(definition, typedInterfaceMethod)); } + + internal sealed class RequiredSymbols + { + private RequiredSymbols(IMethodSymbol addMethod, IMethodSymbol removeMethod, IMethodSymbol containsMethod, IMethodSymbol? addMethodImmutableSet, IMethodSymbol? removeMethodImmutableSet, IMethodSymbol? containsMethodImmutableSet) + { + AddMethod = addMethod; + RemoveMethod = removeMethod; + ContainsMethod = containsMethod; + AddMethodImmutableSet = addMethodImmutableSet; + RemoveMethodImmutableSet = removeMethodImmutableSet; + ContainsMethodImmutableSet = containsMethodImmutableSet; + } + + public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols) + { + symbols = default; + + var typeProvider = WellKnownTypeProvider.GetOrCreate(compilation); + var iSetType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericISet1); + var iCollectionType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericICollection1); + + if (iSetType is null || iCollectionType is null) + { + return false; + } + + IMethodSymbol? addMethod = iSetType.GetMembers(Add).OfType().FirstOrDefault(); + IMethodSymbol? removeMethod = null; + IMethodSymbol? containsMethod = null; + + foreach (var method in iCollectionType.GetMembers().OfType()) + { + switch (method.Name) + { + case Remove: removeMethod = method; break; + case Contains: containsMethod = method; break; + } + } + + if (addMethod is null || removeMethod is null || containsMethod is null) + { + return false; + } + + IMethodSymbol? addMethodImmutableSet = null; + IMethodSymbol? removeMethodImmutableSet = null; + IMethodSymbol? containsMethodImmutableSet = null; + + // The methods from IImmutableSet are optional and will not lead to a code fix. + var iImmutableSetType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsImmutableIImmutableSet1); + + if (iImmutableSetType is not null) + { + foreach (var method in iImmutableSetType.GetMembers().OfType()) + { + switch (method.Name) + { + case Add: addMethodImmutableSet = method; break; + case Remove: removeMethodImmutableSet = method; break; + case Contains: containsMethodImmutableSet = method; break; + } + } + } + + symbols = new RequiredSymbols( + addMethod, removeMethod, containsMethod, + addMethodImmutableSet, removeMethodImmutableSet, containsMethodImmutableSet); + + return true; + } + + // A condition contains an applicable 'Contains' method in the following cases: + // 1. The condition contains only the 'Contains' invocation. + // 2. The condition contains a unary not operation where the operand is a 'Contains' invocation. + // + // In all cases, the invocation must implement either 'ICollection.Contains' or 'IImmutableSet.Contains'. + public bool HasApplicableContainsMethod( + IOperation condition, + [NotNullWhen(true)] out IInvocationOperation? containsInvocation, + out bool containsNegated) + { + containsNegated = false; + containsInvocation = null; + + switch (condition.WalkDownParentheses()) + { + case IInvocationOperation invocation: + containsInvocation = invocation; + break; + case IUnaryOperation unaryOperation when unaryOperation.OperatorKind == UnaryOperatorKind.Not && unaryOperation.Operand is IInvocationOperation operand: + containsNegated = true; + containsInvocation = operand; + break; + default: + return false; + } + + return DoesImplementInterfaceMethod(containsInvocation.TargetMethod, ContainsMethod) || + DoesImplementInterfaceMethod(containsInvocation.TargetMethod, ContainsMethodImmutableSet); + } + + // A true conditional block contains an applicable 'Add' or 'Remove' method if the first operation satisfies one of the following cases: + // 1. The operation is an invocation of 'Add' or 'Remove'. + // 2. The operation is either a simple assignment or an expression statement. + // In this case the child statements are checked if they contain an invocation of 'Add' or 'Remove'. + // 3. The operation is a variable group declaration. + // In this case the descendants are checked if they contain an invocation of 'Add' or 'Remove'. + // + // In all cases, the invocation must implement either + // 1. 'ISet.Add' or 'IImmutableSet.Add' if the call to 'Contains' is negated. + // 2. 'ICollection.Remove' or 'IImmutableSet.Remove' otherwise. + public bool HasApplicableAddOrRemoveMethod( + IEnumerable operations, + bool containsNegated, + [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) + { + addOrRemoveInvocation = null; + var firstOperation = operations.FirstOrDefault(); + + if (firstOperation is null) + { + return false; + } + + switch (firstOperation) + { + case IInvocationOperation invocation: + if ((containsNegated && IsAnyAddMethod(invocation.TargetMethod)) || + (!containsNegated && IsAnyRemoveMethod(invocation.TargetMethod))) + { + addOrRemoveInvocation = invocation; + return true; + } + + break; + + case ISimpleAssignmentOperation: + case IExpressionStatementOperation: + var firstChildAddOrRemove = firstOperation.Children + .OfType() + .FirstOrDefault(i => containsNegated ? + IsAnyAddMethod(i.TargetMethod) : + IsAnyRemoveMethod(i.TargetMethod)); + + if (firstChildAddOrRemove != null) + { + addOrRemoveInvocation = firstChildAddOrRemove; + return true; + } + + break; + + case IVariableDeclarationGroupOperation variableDeclarationGroup: + var firstDescendantAddOrRemove = firstOperation.Descendants() + .OfType() + .FirstOrDefault(i => containsNegated ? + IsAnyAddMethod(i.TargetMethod) : + IsAnyRemoveMethod(i.TargetMethod)); + + if (firstDescendantAddOrRemove != null) + { + addOrRemoveInvocation = firstDescendantAddOrRemove; + return true; + } + + break; + } + + return false; + } + + private bool IsAnyAddMethod(IMethodSymbol method) + { + return DoesImplementInterfaceMethod(method, AddMethod) || + DoesImplementInterfaceMethod(method, AddMethodImmutableSet); + } + + private bool IsAnyRemoveMethod(IMethodSymbol method) + { + return DoesImplementInterfaceMethod(method, RemoveMethod) || + DoesImplementInterfaceMethod(method, RemoveMethodImmutableSet); + } + + public IMethodSymbol AddMethod { get; } + public IMethodSymbol RemoveMethod { get; } + public IMethodSymbol ContainsMethod { get; } + public IMethodSymbol? AddMethodImmutableSet { get; } + public IMethodSymbol? RemoveMethodImmutableSet { get; } + public IMethodSymbol? ContainsMethodImmutableSet { get; } + } } } From ba59f851699f52125d57dec4c2bd3e233af9e354 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sat, 22 Jul 2023 22:56:45 +0200 Subject: [PATCH 30/42] Add changes from msbuild pack --- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index f773496d0f..0d6a8e09c7 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -10,6 +10,6 @@ CA1856 | | A constant is expected for the parameter | CA1862 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | -CA1865 | | Unnecessary call to 'Set.Contains(item)' | +CA1865 | | Remove unnecessary call | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | From 9513d9dfbbe3fc4a22a43a01c96ac48c0a0f9048 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 20:58:07 +0200 Subject: [PATCH 31/42] Only compare first argument value This is sufficient as each method involved only has one argument. Co-authored-by: Buyaa Namnan --- .../Performance/DoNotGuardSetAddOrRemoveByContains.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index ce78efc0dd..c3aed79ffc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -104,9 +104,7 @@ private static bool AreInvocationsOnSameInstance(IInvocationOperation invocation // 2. Identical: Contains(item) == Add(item), Contains("const") == Add("const") private static bool AreInvocationArgumentsEqual(IInvocationOperation invocation1, IInvocationOperation invocation2) { - return invocation1.Arguments - .Zip(invocation2.Arguments, (a1, a2) => IsArgumentValueEqual(a1.Value, a2.Value)) - .All(argumentsEqual => argumentsEqual); + return IsArgumentValueEqual(invocation1.Arguments[0].Value, invocation2.Arguments[0].Value); } private static bool IsArgumentValueEqual(IOperation targetArg, IOperation valueArg) From 8a3f6ef034caf207bb31272a511b58d877985744 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 21:17:47 +0200 Subject: [PATCH 32/42] Make symbol display format static --- .../DoNotGuardSetAddOrRemoveByContains.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index c3aed79ffc..db989c4ae4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -27,6 +27,13 @@ public sealed class DoNotGuardSetAddOrRemoveByContains : DiagnosticAnalyzer private const string Add = nameof(Add); private const string Remove = nameof(Remove); + // Build custom format instead of CSharpShortErrorMessageFormat/VisualBasicShortErrorMessageFormat to prevent unhelpful messages for VB. + private static readonly SymbolDisplayFormat s_symbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat + .WithParameterOptions(SymbolDisplayParameterOptions.IncludeType) + .WithGenericsOptions(SymbolDisplayGenericsOptions.None) + .WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType) + .WithKindOptions(SymbolDisplayKindOptions.None); + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( RuleId, CreateLocalizableResourceString(nameof(DoNotGuardSetAddOrRemoveByContainsTitle)), @@ -71,19 +78,12 @@ void OnConditional(OperationAnalysisContext context) locations.Add(conditional.Syntax.GetLocation()); locations.Add(addOrRemoveInvocation.Syntax.Parent!.GetLocation()); - // Build custom format instead of CSharpShortErrorMessageFormat/VisualBasicShortErrorMessageFormat to prevent unhelpful messages for VB. - var symbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat - .WithParameterOptions(SymbolDisplayParameterOptions.IncludeType) - .WithGenericsOptions(SymbolDisplayGenericsOptions.None) - .WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingType) - .WithKindOptions(SymbolDisplayKindOptions.None); - context.ReportDiagnostic(containsInvocation.CreateDiagnostic( Rule, additionalLocations: locations.ToImmutable(), properties: null, - addOrRemoveInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat), - containsInvocation.TargetMethod.ToDisplayString(symbolDisplayFormat))); + addOrRemoveInvocation.TargetMethod.ToDisplayString(s_symbolDisplayFormat), + containsInvocation.TargetMethod.ToDisplayString(s_symbolDisplayFormat))); } } From be9d96e6e1ce8866103fa425f1adc7081480a3a8 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 21:18:05 +0200 Subject: [PATCH 33/42] Typo --- .../Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs index b4afed3868..f4220d7f2c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -39,13 +39,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - // We only offer a fixer if the conditonal true branch has a single statement, either 'Add' or 'Delete' + // We only offer a fixer if the conditional true branch has a single statement, either 'Add' or 'Delete' if (!SyntaxSupportedByFixer(conditionalSyntax, childStatementSyntax)) { return; } - var codeAction = CodeAction.Create(MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle, + var codeAction = CodeAction.Create(MicrosoftNetCoreAnalyzersResources.RemoveRedundantGuardCallCodeFixTitle, ct => Task.FromResult(ReplaceConditionWithChild(context.Document, root, conditionalSyntax, childStatementSyntax)), nameof(MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle)); From 5fef8d0d3ee9160e27254938c22a086ccf5f979c Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 21:51:58 +0200 Subject: [PATCH 34/42] Update DoNotGuardSetAddOrRemoveByContainsTitle --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- .../xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.de.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.es.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.it.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf | 4 ++-- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 2 +- 16 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index e559ab43cd..8ad09d7fde 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1516,7 +1516,7 @@ Do not guard '{0}' with '{1}' - Remove unnecessary call + Unnecessary call to 'Contains(item)' Remove unnecessary call 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 4eb038070a..c9ba111883 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -774,8 +774,8 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 127ed05163..7a9de800b3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -774,8 +774,8 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 0c6c21fc40..4ce6de92dc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -774,8 +774,8 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 1a3b71b02a..4d338c95e5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -774,8 +774,8 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 031bc02ca1..12bb8676d7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -774,8 +774,8 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 3a5a532ee8..1b88f2bd08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -774,8 +774,8 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 6c78a5d61c..9c49d9d292 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -774,8 +774,8 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 8b3ea95b3c..e00e5cac28 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -774,8 +774,8 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 fd6ffd941b..f16fa63dc1 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 @@ -774,8 +774,8 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 649a2ae97e..2beff556a9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -774,8 +774,8 @@ Widening and user defined conversions are not supported with generic types. - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 f10ef8c0b7..5defcba908 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -774,8 +774,8 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 93802f7803..848a54a148 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 @@ -774,8 +774,8 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' 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 69f78b4353..58edaa8e12 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 @@ -774,8 +774,8 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi - Remove unnecessary call - Remove unnecessary call + Unnecessary call to 'Contains(item)' + Unnecessary call to 'Contains(item)' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 3ee7c2baf7..e9fd691e59 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1740,7 +1740,7 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try |CodeFix|True| --- -## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Remove unnecessary call +## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Unnecessary call to 'Contains(item)' Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 599351da5e..e7871ff9e6 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3218,7 +3218,7 @@ }, "CA1865": { "id": "CA1865", - "shortDescription": "Remove unnecessary call", + "shortDescription": "Unnecessary call to 'Contains(item)'", "fullDescription": "Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed.", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", From 1d220c628eb5df54b05b7e5c416e79f0076ed97e Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 23:00:06 +0200 Subject: [PATCH 35/42] Change condition to pattern matching Co-authored-by: Sam Harwell --- .../CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index 2a59ad2f4c..6cc49b4e2c 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -46,7 +46,7 @@ protected override Document ReplaceConditionWithChild(Document document, SyntaxN newRoot = root.ReplaceNode(conditionalOperationNode, newConditionalOperationNode); } - else if (conditionalOperationNode is IfStatementSyntax ifStatementSyntax && ifStatementSyntax.Else != null) + else if (conditionalOperationNode is IfStatementSyntax { Else: not null } ifStatementSyntax) { var expression = GetNegatedExpression(document, childOperationNode); From c5376f3dc560eddd4c42bacddc068c7990609eac Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 25 Jul 2023 23:02:55 +0200 Subject: [PATCH 36/42] Add changes from msbuild pack --- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 0d6a8e09c7..770955ff1b 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -10,6 +10,6 @@ CA1856 | | A constant is expected for the parameter | CA1862 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | -CA1865 | | Remove unnecessary call | +CA1865 | | Unnecessary call to 'Contains(item)' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | From 7a9fa600ed53771bb24e5b3864e3bfeeb7d591bb Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 26 Jul 2023 16:09:27 +0200 Subject: [PATCH 37/42] Add tests for ternary operators --- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 33e75840db..5521f0d108 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -429,6 +429,40 @@ public MyClass() await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task TernaryOperator_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + bool added = MySet.Contains(""Item"") ? false : MySet.Add(""Item""); + bool removed = MySet.Contains(""Item"") ? MySet.Remove(""Item""): false; + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NestedTernaryOperator_NoDiagnostic_CS() + { + string source = CSUsings + CSNamespaceAndClassStart + @" + private readonly HashSet MySet = new HashSet(); + + public MyClass() + { + bool nestedAdded = MySet.Contains(""Item"") + ? false + : MySet.Add(""Item"") ? true : false; + bool nestedRemoved = MySet.Contains(""Item"") + ? MySet.Remove(""Item"") ? true : false + : false; + }" + CSNamespaceAndClassEnd; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task TriviaIsPreserved_CS() { From 6f6970341a2f77e921f92ec32e6d050fa7bf780c Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 26 Jul 2023 21:19:19 +0200 Subject: [PATCH 38/42] Use raw strings and inline class and usings --- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 1736 ++++++++++------- 1 file changed, 997 insertions(+), 739 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 5521f0d108..022115d6cf 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -20,11 +20,15 @@ public class DoNotGuardSetAddOrRemoveByContainsTests [Fact] public async Task NonInvocationConditionDoesNotThrow_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - public MyClass() - { - if (!true) { } - }" + CSNamespaceAndClassEnd; + string source = """ + class C + { + void M() + { + if (!true) { } + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -32,22 +36,34 @@ public MyClass() [Fact] public async Task AddIsTheOnlyStatement_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - MySet.Add(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + void M() + { + if (![|MySet.Contains("Item")|]) + MySet.Add("Item"); + } + } + """; - public MyClass() - { - MySet.Add(""Item""); - }" + CSNamespaceAndClassEnd; + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + MySet.Add("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -55,22 +71,34 @@ public MyClass() [Fact] public async Task RemoveIsTheOnlyStatement_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + void M() + { + if ([|MySet.Contains("Item")|]) + MySet.Remove("Item"); + } + } + """; - public MyClass() - { - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -78,24 +106,36 @@ public MyClass() [Fact] public async Task AddIsTheOnlyStatementInABlock_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - { - MySet.Add(""Item""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + MySet.Add("Item"); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - MySet.Add(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + MySet.Add("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -103,24 +143,36 @@ public MyClass() [Fact] public async Task RemoveIsTheOnlyStatementInABlock_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) - { - MySet.Remove(""Item""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + MySet.Remove("Item"); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -128,25 +180,37 @@ public MyClass() [Fact] public async Task AddHasElseStatement_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - MySet.Add(""Item""); - else - throw new Exception(""Item already exists""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + MySet.Add("Item"); + else + throw new System.Exception("Item already exists"); + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - if (!MySet.Add(""Item"")) - throw new Exception(""Item already exists""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Add("Item")) + throw new System.Exception("Item already exists"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -154,25 +218,37 @@ public MyClass() [Fact] public async Task RemoveHasElseStatement_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) - MySet.Remove(""Item""); - else - throw new Exception(""Item doesn't exist""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + MySet.Remove("Item"); + else + throw new System.Exception("Item doesn't exist"); + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - if (!MySet.Remove(""Item"")) - throw new Exception(""Item doesn't exist""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Remove("Item")) + throw new System.Exception("Item doesn't exist"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -180,31 +256,43 @@ public MyClass() [Fact] public async Task AddHasElseBlock_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - { - MySet.Add(""Item""); - } - else - { - throw new Exception(""Item already exists""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + MySet.Add("Item"); + } + else + { + throw new System.Exception("Item already exists"); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - if (!MySet.Add(""Item"")) - { - throw new Exception(""Item already exists""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Add("Item")) + { + throw new System.Exception("Item already exists"); + } + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -212,31 +300,43 @@ public MyClass() [Fact] public async Task RemoveHasElseBlock_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) - { - MySet.Remove(""Item""); - } - else - { - throw new Exception(""Item doesn't exist""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + MySet.Remove("Item"); + } + else + { + throw new System.Exception("Item doesn't exist"); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - if (!MySet.Remove(""Item"")) - { - throw new Exception(""Item doesn't exist""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Remove("Item")) + { + throw new System.Exception("Item doesn't exist"); + } + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -244,18 +344,23 @@ public MyClass() [Fact] public async Task AddWithAdditionalStatements_ReportsDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - { - MySet.Add(""Item""); - Console.WriteLine(); - } - } - " + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + MySet.Add("Item"); + System.Console.WriteLine(); + } + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -263,18 +368,23 @@ public MyClass() [Fact] public async Task RemoveWithAdditionalStatements_ReportsDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) - { - MySet.Remove(""Item""); - Console.WriteLine(); - } - } - " + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + MySet.Remove("Item"); + System.Console.WriteLine(); + } + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -282,17 +392,22 @@ public MyClass() [Fact] public async Task AddWithVariableAssignment_ReportsDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(""Item"")|]) - { - bool result = MySet.Add(""Item""); - } - } - " + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + bool result = MySet.Add("Item"); + } + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -300,17 +415,22 @@ public MyClass() [Fact] public async Task RemoveWithVariableAssignment_ReportsDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if ([|MySet.Contains(""Item"")|]) + class C { - bool result = MySet.Remove(""Item""); + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + bool result = MySet.Remove("Item"); + } + } } - } - " + CSNamespaceAndClassEnd; + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -318,14 +438,20 @@ public MyClass() [Fact] public async Task AddWithNonNegatedContains_NoDiagnostics_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (MySet.Contains(""Item"")) - MySet.Add(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (MySet.Contains("Item")) + MySet.Add("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -333,14 +459,20 @@ public MyClass() [Fact] public async Task RemoveWithNegatedContains_NoDiagnostics_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (!MySet.Contains(""Item"")) - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Contains("Item")) + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -348,14 +480,20 @@ public MyClass() [Fact] public async Task AdditionalCondition_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (MySet.Contains(""Item"") && MySet.Count > 2) - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (MySet.Contains("Item") && MySet.Count > 2) + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -363,15 +501,21 @@ public MyClass() [Fact] public async Task ConditionInVariable_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - var result = MySet.Contains(""Item""); - if (result) - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + var result = MySet.Contains("Item"); + if (result) + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -379,15 +523,21 @@ public MyClass() [Fact] public async Task RemoveInSeparateLine_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (MySet.Contains(""Item"")) - _ = MySet.Count; - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (MySet.Contains("Item")) + _ = MySet.Count; + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -395,15 +545,21 @@ public MyClass() [Fact] public async Task NotSetRemove_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); - private bool Remove(string item) => false; + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (MySet.Contains(""Item"")) - Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + private bool Remove(string item) => false; + + void M() + { + if (MySet.Contains("Item")) + Remove("Item"); + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -411,20 +567,26 @@ public MyClass() [Fact] public async Task NestedConditional_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); - private readonly HashSet OtherSet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (MySet.Contains(""Item"")) - { - if (OtherSet.Contains(""Item"")) + class C { - MySet.Remove(""Item""); + private readonly HashSet MySet = new HashSet(); + private readonly HashSet OtherSet = new HashSet(); + + void M() + { + if (MySet.Contains("Item")) + { + if (OtherSet.Contains("Item")) + { + MySet.Remove("Item"); + } + } + } } - } - }" + CSNamespaceAndClassEnd; + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -432,14 +594,20 @@ public MyClass() [Fact] public async Task TernaryOperator_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - bool added = MySet.Contains(""Item"") ? false : MySet.Add(""Item""); - bool removed = MySet.Contains(""Item"") ? MySet.Remove(""Item""): false; - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool added = MySet.Contains("Item") ? false : MySet.Add("Item"); + bool removed = MySet.Contains("Item") ? MySet.Remove("Item"): false; + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -447,18 +615,24 @@ public MyClass() [Fact] public async Task NestedTernaryOperator_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - bool nestedAdded = MySet.Contains(""Item"") - ? false - : MySet.Add(""Item"") ? true : false; - bool nestedRemoved = MySet.Contains(""Item"") - ? MySet.Remove(""Item"") ? true : false - : false; - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool nestedAdded = MySet.Contains("Item") + ? false + : MySet.Add("Item") ? true : false; + bool nestedRemoved = MySet.Contains("Item") + ? MySet.Remove("Item") ? true : false + : false; + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -466,26 +640,38 @@ public MyClass() [Fact] public async Task TriviaIsPreserved_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - // reticulates the splines - if ([|MySet.Contains(""Item"")|]) - { - MySet.Remove(""Item""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + // reticulates the splines + if ([|MySet.Contains("Item")|]) + { + MySet.Remove("Item"); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - // reticulates the splines - MySet.Remove(""Item""); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + // reticulates the splines + MySet.Remove("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -493,29 +679,29 @@ public MyClass() [Fact] public async Task AddIsTheOnlyStatement_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then MySet.Add(""Item"") - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - MySet.Add(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then MySet.Add("Item") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + MySet.Add("Item") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -523,29 +709,29 @@ End Class [Fact] public async Task RemoveIsTheOnlyStatement_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If ([|MySet.Contains(""Item"")|]) Then MySet.Remove(""Item"") - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - MySet.Remove(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If ([|MySet.Contains("Item")|]) Then MySet.Remove("Item") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + MySet.Remove("Item") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -553,31 +739,31 @@ End Class [Fact] public async Task AddIsTheOnlyStatementInBlock_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then - MySet.Add(""Item"") - End If - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - MySet.Add(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + MySet.Add("Item") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + MySet.Add("Item") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -585,31 +771,31 @@ End Class [Fact] public async Task RemoveIsTheOnlyStatementInBlock_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If ([|MySet.Contains(""Item"")|]) Then - MySet.Remove(""Item"") - End If - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - MySet.Remove(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If ([|MySet.Contains("Item")|]) Then + MySet.Remove("Item") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + MySet.Remove("Item") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -617,29 +803,29 @@ End Class [Fact] public async Task AddHasElseStatement_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then MySet.Add(""Item"") Else Throw new Exception(""Item already exists"") - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not MySet.Add(""Item"") Then Throw new Exception(""Item already exists"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then MySet.Add("Item") Else Throw new System.Exception("Item already exists") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Add("Item") Then Throw new System.Exception("Item already exists") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -647,29 +833,29 @@ End Class [Fact] public async Task RemoveHasElseStatement_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If [|MySet.Contains(""Item"")|] Then MySet.Remove(""Item"") Else Throw new Exception(""Item doesn't exist"") - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not MySet.Remove(""Item"") Then Throw new Exception(""Item doesn't exist"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then MySet.Remove("Item") Else Throw new System.Exception("Item doesn't exist") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Remove("Item") Then Throw new System.Exception("Item doesn't exist") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -677,35 +863,35 @@ End Class [Fact] public async Task AddHasElseBlock_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then - MySet.Add(""Item"") - Else - Throw new Exception(""Item already exists"") - End If - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not MySet.Add(""Item"") Then - Throw new Exception(""Item already exists"") - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + MySet.Add("Item") + Else + Throw new System.Exception("Item already exists") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Add("Item") Then + Throw new System.Exception("Item already exists") + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -713,35 +899,35 @@ End Class [Fact] public async Task RemoveHasElseBlock_OffersFixer_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If [|MySet.Contains(""Item"")|] Then - MySet.Remove(""Item"") - Else - Throw new Exception(""Item doesn't exist"") - End If - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not MySet.Remove(""Item"") Then - Throw new Exception(""Item doesn't exist"") - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + MySet.Remove("Item") + Else + Throw new System.Exception("Item doesn't exist") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Remove("Item") Then + Throw new System.Exception("Item doesn't exist") + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -749,17 +935,17 @@ End Class [Fact] public async Task AddWithNonNegatedContains_NoDiagnostics_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If MySet.Contains(""Item"") Then MySet.Add(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If MySet.Contains("Item") Then MySet.Add("Item") + End Sub + End Class + """; await VerifyVB.VerifyAnalyzerAsync(source); } @@ -767,17 +953,17 @@ End Class [Fact] public async Task RemoveWithNegatedContains_NoDiagnostics_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not MySet.Contains(""Item"") Then MySet.Remove(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Contains("Item") Then MySet.Remove("Item") + End Sub + End Class + """; await VerifyVB.VerifyAnalyzerAsync(source); } @@ -785,19 +971,19 @@ End Class [Fact] public async Task AddWithVariableAssignment_ReportsDiagnostic_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then - Dim result = MySet.Add(""Item"") - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + Dim result = MySet.Add("Item") + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, source); } @@ -805,19 +991,19 @@ End Class [Fact] public async Task RemoveWithVariableAssignment_ReportsDiagnostic_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If [|MySet.Contains(""Item"")|] Then - Dim result = MySet.Remove(""Item"") - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + Dim result = MySet.Remove("Item") + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, source); } @@ -825,20 +1011,20 @@ End Class [Fact] public async Task AddWithAdditionalStatements_ReportsDiagnostic_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If Not [|MySet.Contains(""Item"")|] Then - MySet.Add(""Item"") - Console.WriteLine() - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + MySet.Add("Item") + System.Console.WriteLine() + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, source); } @@ -846,20 +1032,20 @@ End Class [Fact] public async Task RemoveWithAdditionalStatements_ReportsDiagnostic_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - If [|MySet.Contains(""Item"")|] Then - MySet.Remove(""Item"") - Console.WriteLine() - End If - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + MySet.Remove("Item") + System.Console.WriteLine() + End If + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, source); } @@ -867,33 +1053,33 @@ End Class [Fact] public async Task TriviaIsPreserved_VB() { - string source = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - ' reticulates the splines - If ([|MySet.Contains(""Item"")|]) Then - MySet.Remove(""Item"") - End If - End Sub - End Class -End Namespace"; - - string fixedSource = @" -" + VBUsings + @" -Namespace Testopolis - Public Class SomeClass - Public MySet As New HashSet(Of String)() - - Public Sub New() - ' reticulates the splines - MySet.Remove(""Item"") - End Sub - End Class -End Namespace"; + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + ' reticulates the splines + If ([|MySet.Contains("Item")|]) Then + MySet.Remove("Item") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + ' reticulates the splines + MySet.Remove("Item") + End Sub + End Class + """; await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } @@ -902,50 +1088,56 @@ End Class [WorkItem(6377, "https://github.com/dotnet/roslyn-analyzers/issues/6377")] public async Task ContainsAndRemoveCalledOnDifferentInstances_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet SetField1 = new HashSet(); - private readonly HashSet SetField2 = new HashSet(); - - public HashSet SetProperty1 { get; } = new HashSet(); - - public MyClass() - { - if (SetField2.Contains(""Item"")) - SetField1.Remove(""Item""); - - if (!SetField1.Contains(""Item"")) - { - SetField2.Remove(""Item""); - } - - if (SetProperty1.Contains(""Item"")) - SetField1.Remove(""Item""); - - if (!SetField1.Contains(""Item"")) - { - SetProperty1.Remove(""Item""); - } - - var mySetLocal4 = new HashSet(); - if (mySetLocal4.Contains(""Item"")) - SetField1.Remove(""Item""); - - if (!SetField1.Contains(""Item"")) - { - mySetLocal4.Remove(""Item""); - } - } - - private void RemoveItem(HashSet setParam) - { - if (setParam.Contains(""Item"")) - SetField1.Remove(""Item""); + string source = """ + using System.Collections.Generic; - if (!SetField1.Contains(""Item"")) - { - setParam.Remove(""Item""); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet SetField1 = new HashSet(); + private readonly HashSet SetField2 = new HashSet(); + + public HashSet SetProperty1 { get; } = new HashSet(); + + void M() + { + if (SetField2.Contains("Item")) + SetField1.Remove("Item"); + + if (!SetField1.Contains("Item")) + { + SetField2.Remove("Item"); + } + + if (SetProperty1.Contains("Item")) + SetField1.Remove("Item"); + + if (!SetField1.Contains("Item")) + { + SetProperty1.Remove("Item"); + } + + var mySetLocal4 = new HashSet(); + if (mySetLocal4.Contains("Item")) + SetField1.Remove("Item"); + + if (!SetField1.Contains("Item")) + { + mySetLocal4.Remove("Item"); + } + } + + private void RemoveItem(HashSet setParam) + { + if (setParam.Contains("Item")) + SetField1.Remove("Item"); + + if (!SetField1.Contains("Item")) + { + setParam.Remove("Item"); + } + } + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } @@ -953,30 +1145,36 @@ private void RemoveItem(HashSet setParam) [Fact] public async Task ContainsAndAddCalledWithDifferentArguments_NoDiagnostic_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); - private const string OtherItemField = ""Other Item""; + string source = """ + using System.Collections.Generic; - public string OtherItemProperty { get; } = ""Other Item""; + class C + { + private readonly HashSet MySet = new HashSet(); + private const string OtherItemField = "Other Item"; - public MyClass(string otherItemParameter) - { - if (!MySet.Contains(""Item"")) - MySet.Add(""Other Item""); + public string OtherItemProperty { get; } = "Other Item"; + + void M(string otherItemParameter) + { + if (!MySet.Contains("Item")) + MySet.Add("Other Item"); - if (!MySet.Contains(""Item"")) - MySet.Add(otherItemParameter); + if (!MySet.Contains("Item")) + MySet.Add(otherItemParameter); - if (!MySet.Contains(""Item"")) - MySet.Add(OtherItemField); + if (!MySet.Contains("Item")) + MySet.Add(OtherItemField); - if (!MySet.Contains(""Item"")) - MySet.Add(OtherItemProperty); + if (!MySet.Contains("Item")) + MySet.Add(OtherItemProperty); - string otherItemLocal = ""Other Item""; - if (!MySet.Contains(""Item"")) - MySet.Add(otherItemLocal); - }" + CSNamespaceAndClassEnd; + string otherItemLocal = "Other Item"; + if (!MySet.Contains("Item")) + MySet.Add(otherItemLocal); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -984,26 +1182,38 @@ public MyClass(string otherItemParameter) [Fact] public async Task ContainsAndAddCalledWithSameArgumentsFields_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); - private const string FieldItem = ""Item""; + string source = """ + using System.Collections.Generic; - public MyClass() - { - if (![|MySet.Contains(FieldItem)|]) - { - MySet.Add(FieldItem); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + private const string FieldItem = "Item"; + + void M() + { + if (![|MySet.Contains(FieldItem)|]) + { + MySet.Add(FieldItem); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); - private const string FieldItem = ""Item""; + string fixedSource = """ + using System.Collections.Generic; - public MyClass() - { - MySet.Add(FieldItem); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + private const string FieldItem = "Item"; + + void M() + { + MySet.Add(FieldItem); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -1011,28 +1221,40 @@ public MyClass() [Fact] public async Task ContainsAndAddCalledWithSameArgumentsLocals_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass() - { - const string LocalItem = ""Item""; + class C + { + private readonly HashSet MySet = new HashSet(); - if (![|MySet.Contains(LocalItem)|]) - { - MySet.Add(LocalItem); - } - }" + CSNamespaceAndClassEnd; + void M() + { + const string LocalItem = "Item"; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + if (![|MySet.Contains(LocalItem)|]) + { + MySet.Add(LocalItem); + } + } + } + """; - public MyClass() - { - const string LocalItem = ""Item""; + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + const string LocalItem = "Item"; - MySet.Add(LocalItem); - }" + CSNamespaceAndClassEnd; + MySet.Add(LocalItem); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -1040,24 +1262,36 @@ public MyClass() [Fact] public async Task ContainsAndAddCalledWithSameArgumentsParameters_OffersFixer_CS() { - string source = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string source = """ + using System.Collections.Generic; - public MyClass(string parameterItem) - { - if (![|MySet.Contains(parameterItem)|]) - { - MySet.Add(parameterItem); - } - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M(string parameterItem) + { + if (![|MySet.Contains(parameterItem)|]) + { + MySet.Add(parameterItem); + } + } + } + """; - string fixedSource = CSUsings + CSNamespaceAndClassStart + @" - private readonly HashSet MySet = new HashSet(); + string fixedSource = """ + using System.Collections.Generic; - public MyClass(string parameterItem) - { - MySet.Add(parameterItem); - }" + CSNamespaceAndClassEnd; + class C + { + private readonly HashSet MySet = new HashSet(); + + void M(string parameterItem) + { + MySet.Add(parameterItem); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -1073,22 +1307,36 @@ public MyClass(string parameterItem) [InlineData("ImmutableSortedSet.Builder", "Remove")] public async Task SupportsSetsWithAddOrRemoveReturningBool_OffersFixer_CS(string setType, string method) { - string source = CSUsings + CSNamespaceAndClassStart + $@" - private readonly {setType} MySet = {SetCreationExpression(setType)} + string source = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; + + class C + { + private readonly {{setType}} MySet = {{SetCreationExpression(setType)}} + + void M() + { + if ({{(method == "Add" ? "!" : string.Empty)}}[|MySet.Contains("Item")|]) + MySet.{{method}}("Item"); + } + } + """; - public MyClass() - {{ - if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) - MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + string fixedSource = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; - string fixedSource = CSUsings + CSNamespaceAndClassStart + $@" - private readonly {setType} MySet = {SetCreationExpression(setType)} + class C + { + private readonly {{setType}} MySet = {{SetCreationExpression(setType)}} - public MyClass() - {{ - MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + void M() + { + MySet.{{method}}("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -1104,22 +1352,36 @@ public MyClass() [InlineData("ISet", "ImmutableSortedSet.Builder", "Remove")] public async Task SupportsSetsWithAddOrRemoveReturningBoolWithInterfaceType_OffersFixer_CS(string interfaceType, string concreteType, string method) { - string source = CSUsings + CSNamespaceAndClassStart + $@" - private readonly {interfaceType} MySet = {SetCreationExpression(concreteType)} + string source = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; - public MyClass() - {{ - if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) - MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + class C + { + private readonly {{interfaceType}} MySet = {{SetCreationExpression(concreteType)}} - string fixedSource = CSUsings + CSNamespaceAndClassStart + $@" - private readonly {interfaceType} MySet = {SetCreationExpression(concreteType)} + void M() + { + if ({{(method == "Add" ? "!" : string.Empty)}}[|MySet.Contains("Item")|]) + MySet.{{method}}("Item"); + } + } + """; + + string fixedSource = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; + + class C + { + private readonly {{interfaceType}} MySet = {{SetCreationExpression(concreteType)}} - public MyClass() - {{ - MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + void M() + { + MySet.{{method}}("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -1131,14 +1393,21 @@ public MyClass() [InlineData("ImmutableSortedSet", "Remove")] public async Task SupportsSetWithAddOrRemoveReturningGenericType_ReportsDiagnostic_CS(string setType, string method) { - string source = CSUsings + CSNamespaceAndClassStart + $@" - private {setType} MySet = {setType[..setType.IndexOf('<', StringComparison.Ordinal)]}.Create(); + string source = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; + + class C + { + private {{setType}} MySet = {{setType[..setType.IndexOf('<', StringComparison.Ordinal)]}}.Create(); - public MyClass() - {{ - if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) - MySet = MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + void M() + { + if ({{(method == "Add" ? "!" : string.Empty)}}[|MySet.Contains("Item")|]) + MySet = MySet.{{method}}("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } @@ -1150,38 +1419,27 @@ public MyClass() [InlineData("IImmutableSet", "ImmutableSortedSet", "Remove")] public async Task SupportsSetWithAddOrRemoveReturningGenericTypeWithInterfaceType_ReportsDiagnostic_CS(string interfaceType, string concreteType, string method) { - string source = CSUsings + CSNamespaceAndClassStart + $@" - private {interfaceType} MySet = {concreteType[..concreteType.IndexOf('<', StringComparison.Ordinal)]}.Create(); + string source = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; - public MyClass() - {{ - if ({(method == "Add" ? "!" : string.Empty)}[|MySet.Contains(""Item"")|]) - MySet = MySet.{method}(""Item""); - }}" + CSNamespaceAndClassEnd; + class C + { + private {{interfaceType}} MySet = {{concreteType[..concreteType.IndexOf('<', StringComparison.Ordinal)]}}.Create(); + + void M() + { + if ({{(method == "Add" ? "!" : string.Empty)}}[|MySet.Contains("Item")|]) + MySet = MySet.{{method}}("Item"); + } + } + """; await VerifyCS.VerifyCodeFixAsync(source, source); } #endregion #region Helpers - private const string CSUsings = @"using System; -using System.Collections.Generic; -using System.Collections.Immutable;"; - - private const string CSNamespaceAndClassStart = @"namespace Testopolis -{ - public class MyClass - { -"; - - private const string CSNamespaceAndClassEnd = @" - } -}"; - - private const string VBUsings = @"Imports System -Imports System.Collections.Generic -Imports System.Collections.Immutable"; - private string SetCreationExpression(string setType) { return setType.Contains("Builder", StringComparison.Ordinal) From ad8298285efd463cd5ac557387ff2ddc35267225 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Wed, 26 Jul 2023 23:06:39 +0200 Subject: [PATCH 39/42] Support Add or Remove in else block --- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 9 +- .../DoNotGuardSetAddOrRemoveByContains.cs | 53 +- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 596 +++++++++++++++++- ...oNotGuardSetAddOrRemoveByContains.Fixer.vb | 18 +- 4 files changed, 634 insertions(+), 42 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index 6cc49b4e2c..d22bf8c65a 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -23,7 +23,11 @@ protected override bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax, Syn if (conditionalSyntax is IfStatementSyntax ifStatementSyntax) { - return ifStatementSyntax.Statement.ChildNodes().Count() == 1; + var addOrRemoveInElse = childStatementSyntax.Parent is ElseClauseSyntax || childStatementSyntax.Parent?.Parent is ElseClauseSyntax; + + return addOrRemoveInElse + ? ifStatementSyntax.Else?.Statement.ChildNodes().Count() == 1 + : ifStatementSyntax.Statement.ChildNodes().Count() == 1; } return false; @@ -49,10 +53,11 @@ protected override Document ReplaceConditionWithChild(Document document, SyntaxN else if (conditionalOperationNode is IfStatementSyntax { Else: not null } ifStatementSyntax) { var expression = GetNegatedExpression(document, childOperationNode); + var addOrRemoveInElse = childOperationNode.Parent is ElseClauseSyntax || childOperationNode.Parent?.Parent is ElseClauseSyntax; SyntaxNode newConditionalOperationNode = ifStatementSyntax .WithCondition((ExpressionSyntax)expression) - .WithStatement(ifStatementSyntax.Else.Statement) + .WithStatement(addOrRemoveInElse ? ifStatementSyntax.Statement : ifStatementSyntax.Else.Statement) .WithElse(null) .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index db989c4ae4..a8feaa8bed 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -67,7 +66,7 @@ void OnConditional(OperationAnalysisContext context) var conditional = (IConditionalOperation)context.Operation; if (!symbols.HasApplicableContainsMethod(conditional.Condition, out var containsInvocation, out bool containsNegated) || - !symbols.HasApplicableAddOrRemoveMethod(conditional.WhenTrue.Children, containsNegated, out var addOrRemoveInvocation) || + !symbols.HasApplicableAddOrRemoveMethod(conditional, containsNegated, out var addOrRemoveInvocation) || !AreInvocationsOnSameInstance(containsInvocation, addOrRemoveInvocation) || !AreInvocationArgumentsEqual(containsInvocation, addOrRemoveInvocation)) { @@ -252,7 +251,7 @@ public bool HasApplicableContainsMethod( DoesImplementInterfaceMethod(containsInvocation.TargetMethod, ContainsMethodImmutableSet); } - // A true conditional block contains an applicable 'Add' or 'Remove' method if the first operation satisfies one of the following cases: + // A conditional contains an applicable 'Add' or 'Remove' method if the first operation of WhenTrue or WhenFalse satisfies one of the following: // 1. The operation is an invocation of 'Add' or 'Remove'. // 2. The operation is either a simple assignment or an expression statement. // In this case the child statements are checked if they contain an invocation of 'Add' or 'Remove'. @@ -260,66 +259,72 @@ public bool HasApplicableContainsMethod( // In this case the descendants are checked if they contain an invocation of 'Add' or 'Remove'. // // In all cases, the invocation must implement either - // 1. 'ISet.Add' or 'IImmutableSet.Add' if the call to 'Contains' is negated. - // 2. 'ICollection.Remove' or 'IImmutableSet.Remove' otherwise. + // 1. 'ISet.Add' or 'IImmutableSet.Add' + // 2. 'ICollection.Remove' or 'IImmutableSet.Remove' public bool HasApplicableAddOrRemoveMethod( - IEnumerable operations, + IConditionalOperation conditional, bool containsNegated, [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) { - addOrRemoveInvocation = null; - var firstOperation = operations.FirstOrDefault(); + addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenTrue.Children.FirstOrDefault(), extractAdd: containsNegated); - if (firstOperation is null) + if (addOrRemoveInvocation is null) { - return false; + addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenFalse?.Children.FirstOrDefault(), extractAdd: !containsNegated); + } + + return addOrRemoveInvocation is not null; + } + + private IInvocationOperation? GetApplicableAddOrRemove(IOperation? operation, bool extractAdd) + { + if (operation is null) + { + return null; } - switch (firstOperation) + switch (operation) { case IInvocationOperation invocation: - if ((containsNegated && IsAnyAddMethod(invocation.TargetMethod)) || - (!containsNegated && IsAnyRemoveMethod(invocation.TargetMethod))) + if ((extractAdd && IsAnyAddMethod(invocation.TargetMethod)) || + (!extractAdd && IsAnyRemoveMethod(invocation.TargetMethod))) { - addOrRemoveInvocation = invocation; - return true; + return invocation; } break; case ISimpleAssignmentOperation: case IExpressionStatementOperation: - var firstChildAddOrRemove = firstOperation.Children + var firstChildAddOrRemove = operation.Children .OfType() - .FirstOrDefault(i => containsNegated ? + .FirstOrDefault(i => extractAdd ? IsAnyAddMethod(i.TargetMethod) : IsAnyRemoveMethod(i.TargetMethod)); if (firstChildAddOrRemove != null) { - addOrRemoveInvocation = firstChildAddOrRemove; - return true; + return firstChildAddOrRemove; } break; case IVariableDeclarationGroupOperation variableDeclarationGroup: - var firstDescendantAddOrRemove = firstOperation.Descendants() + var firstDescendantAddOrRemove = operation.Descendants() .OfType() - .FirstOrDefault(i => containsNegated ? + .FirstOrDefault(i => extractAdd ? IsAnyAddMethod(i.TargetMethod) : IsAnyRemoveMethod(i.TargetMethod)); if (firstDescendantAddOrRemove != null) { - addOrRemoveInvocation = firstDescendantAddOrRemove; - return true; + return firstDescendantAddOrRemove; } break; } - return false; + return null; } private bool IsAnyAddMethod(IMethodSymbol method) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 022115d6cf..64118b1585 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -104,7 +104,7 @@ void M() } [Fact] - public async Task AddIsTheOnlyStatementInABlock_OffersFixer_CS() + public async Task AddIsTheOnlyStatementInBlock_OffersFixer_CS() { string source = """ using System.Collections.Generic; @@ -141,7 +141,7 @@ void M() } [Fact] - public async Task RemoveIsTheOnlyStatementInABlock_OffersFixer_CS() + public async Task RemoveIsTheOnlyStatementInBlock_OffersFixer_CS() { string source = """ using System.Collections.Generic; @@ -253,6 +253,82 @@ void M() await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } + [Fact] + public async Task AddWhenFalseHasElseStatement_OffersFixer_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + throw new System.Exception("Item already exists"); + else + MySet.Add("Item"); + } + } + """; + + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Add("Item")) + throw new System.Exception("Item already exists"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveWhenFalseHasElseStatement_OffersFixer_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + throw new System.Exception("Item doesn't exist"); + else + MySet.Remove("Item"); + } + } + """; + + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Remove("Item")) + throw new System.Exception("Item doesn't exist"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + [Fact] public async Task AddHasElseBlock_OffersFixer_CS() { @@ -341,6 +417,94 @@ void M() await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } + [Fact] + public async Task AddWhenFalseHasElseBlock_OffersFixer_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + throw new System.Exception("Item already exists"); + } + else + { + MySet.Add("Item"); + } + } + } + """; + + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Add("Item")) + { + throw new System.Exception("Item already exists"); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveWhenFalseHasElseBlock_OffersFixer_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + throw new System.Exception("Item doesn't exist"); + } + else + { + MySet.Remove("Item"); + } + } + } + """; + + string fixedSource = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Remove("Item")) + { + throw new System.Exception("Item doesn't exist"); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + [Fact] public async Task AddWithAdditionalStatements_ReportsDiagnostic_CS() { @@ -389,6 +553,62 @@ void M() await VerifyCS.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddWhenFalseWithAdditionalStatements_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + throw new System.Exception("Item already exists"); + } + else + { + MySet.Add("Item"); + System.Console.WriteLine(); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWhenFalseWithAdditionalStatements_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + throw new System.Exception("Item doesn't exist"); + } + else + { + MySet.Remove("Item"); + System.Console.WriteLine(); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task AddWithVariableAssignment_ReportsDiagnostic_CS() { @@ -429,14 +649,110 @@ void M() bool result = MySet.Remove("Item"); } } - } - """; + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddWhenFalseWithVariableAssignment_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if ([|MySet.Contains("Item")|]) + { + throw new System.Exception("Item already exists"); + } + else + { + bool result = MySet.Add("Item"); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWhenFalseWithVariableAssignment_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (![|MySet.Contains("Item")|]) + { + throw new System.Exception("Item doesn't exist"); + } + else + { + bool result = MySet.Remove("Item"); + } + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddWithNonNegatedContains_NoDiagnostics_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (MySet.Contains("Item")) + MySet.Add("Item"); + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveWithNegatedContains_NoDiagnostics_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + if (!MySet.Contains("Item")) + MySet.Remove("Item"); + } + } + """; - await VerifyCS.VerifyCodeFixAsync(source, source); + await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] - public async Task AddWithNonNegatedContains_NoDiagnostics_CS() + public async Task AddWhenFalseWithNegatedContains_NoDiagnostics_CS() { string source = """ using System.Collections.Generic; @@ -447,7 +763,9 @@ class C void M() { - if (MySet.Contains("Item")) + if (!MySet.Contains("Item")) + throw new System.Exception("Item doesn't exist"); + else MySet.Add("Item"); } } @@ -457,7 +775,7 @@ void M() } [Fact] - public async Task RemoveWithNegatedContains_NoDiagnostics_CS() + public async Task RemoveWhenFalseWithNonNegatedContains_NoDiagnostics_CS() { string source = """ using System.Collections.Generic; @@ -468,7 +786,9 @@ class C void M() { - if (!MySet.Contains("Item")) + if (MySet.Contains("Item")) + throw new System.Exception("Item already exists"); + else MySet.Remove("Item"); } } @@ -860,6 +1180,66 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } + [Fact] + public async Task AddWhenFalseHasElseStatement_OffersFixer_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then Throw new System.Exception("Item already exists") Else MySet.Add("Item") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Add("Item") Then Throw new System.Exception("Item already exists") + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveWhenFalseHasElseStatement_OffersFixer_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then Throw new System.Exception("Item doesn't exist") Else MySet.Remove("Item") + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Remove("Item") Then Throw new System.Exception("Item doesn't exist") + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + [Fact] public async Task AddHasElseBlock_OffersFixer_VB() { @@ -932,6 +1312,78 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, fixedSource); } + [Fact] + public async Task AddWhenFalseHasElseBlock_OffersFixer_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item already exists") + Else + MySet.Add("Item") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Add("Item") Then + Throw new System.Exception("Item already exists") + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task RemoveWhenFalseHasElseBlock_OffersFixer_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item doesn't exist") + Else + MySet.Remove("Item") + End If + End Sub + End Class + """; + + string fixedSource = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Remove("Item") Then + Throw new System.Exception("Item doesn't exist") + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + [Fact] public async Task AddWithNonNegatedContains_NoDiagnostics_VB() { @@ -968,6 +1420,42 @@ End Class await VerifyVB.VerifyAnalyzerAsync(source); } + [Fact] + public async Task AddWhenFalseWithNegatedContains_NoDiagnostics_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not MySet.Contains("Item") Then Throw new System.Exception("Item doesn't exist") Else MySet.Add("Item") + End Sub + End Class + """; + + await VerifyVB.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task RemoveWhenFalseWithNonNegatedContains_NoDiagnostics_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If MySet.Contains("Item") Then Throw new System.Exception("Item already exists") Else MySet.Remove("Item") + End Sub + End Class + """; + + await VerifyVB.VerifyAnalyzerAsync(source); + } + [Fact] public async Task AddWithVariableAssignment_ReportsDiagnostic_VB() { @@ -1008,6 +1496,50 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddWhenFalseWithVariableAssignment_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item already exists") + Else + Dim result = MySet.Add("Item") + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWhenFalseWithVariableAssignment_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item doesn't exist") + Else + Dim result = MySet.Remove("Item") + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task AddWithAdditionalStatements_ReportsDiagnostic_VB() { @@ -1050,6 +1582,52 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddWhenFalseWithAdditionalStatements_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item already exists") + Else + MySet.Add("Item") + System.Console.WriteLine() + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveWhenFalseWithAdditionalStatements_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + If Not [|MySet.Contains("Item")|] Then + Throw new System.Exception("Item doesn't exist") + Else + MySet.Remove("Item") + System.Console.WriteLine() + End If + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task TriviaIsPreserved_VB() { diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb index e17a95c04b..bdfbcbdae0 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotGuardSetAddOrRemoveByContains.Fixer.vb @@ -19,7 +19,13 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance End If If TypeOf conditionalSyntax Is MultiLineIfBlockSyntax Then - Return CType(conditionalSyntax, MultiLineIfBlockSyntax).Statements.Count() = 1 + Dim addOrRemoveInElse = TypeOf childStatementSyntax.Parent Is ElseBlockSyntax + + If addOrRemoveInElse Then + Return CType(conditionalSyntax, MultiLineIfBlockSyntax).ElseBlock.Statements.Count() = 1 + Else + Return CType(conditionalSyntax, MultiLineIfBlockSyntax).Statements.Count() = 1 + End If End If Return TypeOf conditionalSyntax Is SingleLineIfStatementSyntax @@ -33,11 +39,10 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance If multiLineIfBlockSyntax?.ElseBlock?.ChildNodes().Any() Then Dim generator = SyntaxGenerator.GetGenerator(document) Dim negatedExpression = generator.LogicalNotExpression(CType(childOperationNode, ExpressionStatementSyntax).Expression.WithoutTrivia()) - - Dim oldElseBlock = multiLineIfBlockSyntax.ElseBlock.Statements + Dim addOrRemoveInElse = TypeOf childOperationNode.Parent Is ElseBlockSyntax newConditionNode = multiLineIfBlockSyntax.WithIfStatement(multiLineIfBlockSyntax.IfStatement.WithCondition(CType(negatedExpression, ExpressionSyntax))) _ - .WithStatements(oldElseBlock) _ + .WithStatements(If(addOrRemoveInElse, multiLineIfBlockSyntax.Statements, multiLineIfBlockSyntax.ElseBlock.Statements)) _ .WithElseBlock(Nothing) _ .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) Else @@ -46,11 +51,10 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance If singleLineIfBlockSyntax?.ElseClause?.ChildNodes().Any() Then Dim generator = SyntaxGenerator.GetGenerator(document) Dim negatedExpression = generator.LogicalNotExpression(CType(childOperationNode, ExpressionStatementSyntax).Expression.WithoutTrivia()) - - Dim oldElseBlock = singleLineIfBlockSyntax.ElseClause.Statements + Dim addOrRemoveInElse = TypeOf childOperationNode.Parent Is SingleLineElseClauseSyntax newConditionNode = singleLineIfBlockSyntax.WithCondition(CType(negatedExpression, ExpressionSyntax)) _ - .WithStatements(oldElseBlock) _ + .WithStatements(If(addOrRemoveInElse, singleLineIfBlockSyntax.Statements, singleLineIfBlockSyntax.ElseClause.Statements)) _ .WithElseClause(Nothing) _ .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode) Else From f723ac580879390b3ef4762bee690c4594555caf Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 27 Jul 2023 00:37:09 +0200 Subject: [PATCH 40/42] Support ternary operator (diagnostic only) --- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 15 +- ...oNotGuardSetAddOrRemoveByContains.Fixer.cs | 1 - .../DoNotGuardSetAddOrRemoveByContains.cs | 21 +- ...DoNotGuardSetAddOrRemoveByContainsTests.cs | 350 +++++++++++++++++- 4 files changed, 359 insertions(+), 28 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs index d22bf8c65a..9a7e7a3bd1 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpDoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -37,20 +37,7 @@ protected override Document ReplaceConditionWithChild(Document document, SyntaxN { SyntaxNode newRoot; - if (conditionalOperationNode is ConditionalExpressionSyntax conditionalExpressionSyntax && - conditionalExpressionSyntax.WhenFalse.ChildNodes().Any()) - { - var expression = GetNegatedExpression(document, childOperationNode); - - SyntaxNode newConditionalOperationNode = conditionalExpressionSyntax - .WithCondition((ExpressionSyntax)expression) - .WithWhenTrue(conditionalExpressionSyntax.WhenFalse) - .WithWhenFalse(null!) - .WithAdditionalAnnotations(Formatter.Annotation).WithTriviaFrom(conditionalOperationNode); - - newRoot = root.ReplaceNode(conditionalOperationNode, newConditionalOperationNode); - } - else if (conditionalOperationNode is IfStatementSyntax { Else: not null } ifStatementSyntax) + if (conditionalOperationNode is IfStatementSyntax { Else: not null } ifStatementSyntax) { var expression = GetNegatedExpression(document, childOperationNode); var addOrRemoveInElse = childOperationNode.Parent is ElseClauseSyntax || childOperationNode.Parent?.Parent is ElseClauseSyntax; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs index f4220d7f2c..27e6dbd01d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.Fixer.cs @@ -39,7 +39,6 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - // We only offer a fixer if the conditional true branch has a single statement, either 'Add' or 'Delete' if (!SyntaxSupportedByFixer(conditionalSyntax, childStatementSyntax)) { return; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index a8feaa8bed..1712fe96fe 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -257,6 +257,7 @@ public bool HasApplicableContainsMethod( // In this case the child statements are checked if they contain an invocation of 'Add' or 'Remove'. // 3. The operation is a variable group declaration. // In this case the descendants are checked if they contain an invocation of 'Add' or 'Remove'. + // OR when the WhenTrue or WhenFalse is a InvocationOperation (in the case of a ternary operator). // // In all cases, the invocation must implement either // 1. 'ISet.Add' or 'IImmutableSet.Add' @@ -266,11 +267,11 @@ public bool HasApplicableAddOrRemoveMethod( bool containsNegated, [NotNullWhen(true)] out IInvocationOperation? addOrRemoveInvocation) { - addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenTrue.Children.FirstOrDefault(), extractAdd: containsNegated); + addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenTrue, extractAdd: containsNegated); if (addOrRemoveInvocation is null) { - addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenFalse?.Children.FirstOrDefault(), extractAdd: !containsNegated); + addOrRemoveInvocation = GetApplicableAddOrRemove(conditional.WhenFalse, extractAdd: !containsNegated); } return addOrRemoveInvocation is not null; @@ -278,12 +279,18 @@ public bool HasApplicableAddOrRemoveMethod( private IInvocationOperation? GetApplicableAddOrRemove(IOperation? operation, bool extractAdd) { - if (operation is null) + if (operation is IInvocationOperation ternaryInvocation) { - return null; + if ((extractAdd && IsAnyAddMethod(ternaryInvocation.TargetMethod)) || + (!extractAdd && IsAnyRemoveMethod(ternaryInvocation.TargetMethod))) + { + return ternaryInvocation; + } } - switch (operation) + var firstChildOperation = operation?.Children.FirstOrDefault(); + + switch (firstChildOperation) { case IInvocationOperation invocation: if ((extractAdd && IsAnyAddMethod(invocation.TargetMethod)) || @@ -296,7 +303,7 @@ public bool HasApplicableAddOrRemoveMethod( case ISimpleAssignmentOperation: case IExpressionStatementOperation: - var firstChildAddOrRemove = operation.Children + var firstChildAddOrRemove = firstChildOperation.Children .OfType() .FirstOrDefault(i => extractAdd ? IsAnyAddMethod(i.TargetMethod) : @@ -310,7 +317,7 @@ public bool HasApplicableAddOrRemoveMethod( break; case IVariableDeclarationGroupOperation variableDeclarationGroup: - var firstDescendantAddOrRemove = operation.Descendants() + var firstDescendantAddOrRemove = firstChildOperation.Descendants() .OfType() .FirstOrDefault(i => extractAdd ? IsAnyAddMethod(i.TargetMethod) : diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index 64118b1585..c6001d88cd 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -912,7 +912,7 @@ void M() } [Fact] - public async Task TernaryOperator_NoDiagnostic_CS() + public async Task AddInTernaryWhenTrue_ReportsDiagnostic_CS() { string source = """ using System.Collections.Generic; @@ -923,8 +923,7 @@ class C void M() { - bool added = MySet.Contains("Item") ? false : MySet.Add("Item"); - bool removed = MySet.Contains("Item") ? MySet.Remove("Item"): false; + bool added = ![|MySet.Contains("Item")|] ? MySet.Add("Item") : false; } } """; @@ -933,7 +932,7 @@ void M() } [Fact] - public async Task NestedTernaryOperator_NoDiagnostic_CS() + public async Task AddInTernaryWhenFalse_ReportsDiagnostic_CS() { string source = """ using System.Collections.Generic; @@ -944,10 +943,89 @@ class C void M() { - bool nestedAdded = MySet.Contains("Item") + bool added = [|MySet.Contains("Item")|] ? false : MySet.Add("Item"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrue_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool removed = [|MySet.Contains("Item")|] ? MySet.Remove("Item") : false; + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenFalse_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool removed = ![|MySet.Contains("Item")|] ? false : MySet.Remove("Item"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenFalseNested_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool nestedAdded = [|MySet.Contains("Item")|] ? false : MySet.Add("Item") ? true : false; - bool nestedRemoved = MySet.Contains("Item") + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrueNested_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool nestedRemoved = [|MySet.Contains("Item")|] ? MySet.Remove("Item") ? true : false : false; } @@ -957,6 +1035,86 @@ void M() await VerifyCS.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddInTernaryWhenTrueWithNonNegatedContains_NoDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool added = MySet.Contains("Item") ? MySet.Add("Item") : false; + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenFalseWithNegatedContains_NoDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool added = !MySet.Contains("Item") ? false : MySet.Add("Item"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrueWithNegatedContains_NoDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool removed = !MySet.Contains("Item") ? MySet.Remove("Item") : false; + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenFalseWithNonNegatedContains_ReportsDiagnostic_CS() + { + string source = """ + using System.Collections.Generic; + + class C + { + private readonly HashSet MySet = new HashSet(); + + void M() + { + bool removed = MySet.Contains("Item") ? false : MySet.Remove("Item"); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task TriviaIsPreserved_CS() { @@ -1628,6 +1786,186 @@ End Class await VerifyVB.VerifyCodeFixAsync(source, source); } + [Fact] + public async Task AddInTernaryWhenTrue_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(Not [|MySet.Contains("Item")|], MySet.Add("Item"), false) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenFalse_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If([|MySet.Contains("Item")|], false, MySet.Add("Item")) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrue_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim removed = If([|MySet.Contains("Item")|], MySet.Remove("Item"), false) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenFalse_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(Not [|MySet.Contains("Item")|], false, MySet.Remove("Item")) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenFalseNested_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If([|MySet.Contains("Item")|], false, If(MySet.Add("Item"), true, false)) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrueNested_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If([|MySet.Contains("Item")|], If(MySet.Remove("Item"), true, false), false) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenTrueWithNonNegatedContains_NoDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(MySet.Contains("Item"), MySet.Add("Item"), false) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task AddInTernaryWhenFalseWithNegatedContains_NoDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(Not MySet.Contains("Item"), false, MySet.Add("Item")) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenTrueWithNegatedContains_NoDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(Not MySet.Contains("Item"), MySet.Remove("Item"), false) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task RemoveInTernaryWhenFalseWithNonNegatedContains_ReportsDiagnostic_VB() + { + string source = """ + Imports System.Collections.Generic + + Public Class C + Private ReadOnly MySet As New HashSet(Of String)() + + Public Sub M() + Dim added = If(MySet.Contains("Item"), false, MySet.Remove("Item")) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + [Fact] public async Task TriviaIsPreserved_VB() { From 915a4ad29243226bc65a98c99155ecbe55192249 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Thu, 27 Jul 2023 01:57:06 +0200 Subject: [PATCH 41/42] Move from CA1865 to CA1868 --- src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md | 2 +- .../Performance/DoNotGuardSetAddOrRemoveByContains.cs | 4 ++-- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif | 6 +++--- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 1d33e3dade..aebb84e557 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -4,7 +4,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- -CA1865 | Performance | Info | DoNotGuardSetAddOrRemoveByContains, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865) +CA1868 | Performance | Info | DoNotGuardSetAddOrRemoveByContains, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865) CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250) CA1510 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510) CA1511 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs index 1712fe96fe..c958faf5c3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContains.cs @@ -15,12 +15,12 @@ namespace Microsoft.NetCore.Analyzers.Performance using static MicrosoftNetCoreAnalyzersResources; /// - /// CA1865: + /// CA1868: /// [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DoNotGuardSetAddOrRemoveByContains : DiagnosticAnalyzer { - internal const string RuleId = "CA1865"; + internal const string RuleId = "CA1868"; private const string Contains = nameof(Contains); private const string Add = nameof(Add); diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index e9fd691e59..73bc78975d 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1740,7 +1740,7 @@ Prefer a 'TryAdd' call over an 'Add' call guarded by a 'ContainsKey' check. 'Try |CodeFix|True| --- -## [CA1865](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865): Unnecessary call to 'Contains(item)' +## [CA1868](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868): Unnecessary call to 'Contains(item)' Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index e7871ff9e6..9cea686908 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3216,12 +3216,12 @@ ] } }, - "CA1865": { - "id": "CA1865", + "CA1868": { + "id": "CA1868", "shortDescription": "Unnecessary call to 'Contains(item)'", "fullDescription": "Do not guard 'Add(item)' or 'Remove(item)' with 'Contains(item)' for the set. The former two already check whether the item exists and will return if it was added or removed.", "defaultLevel": "note", - "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868", "properties": { "category": "Performance", "isEnabledByDefault": true, diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 770955ff1b..fd857a9915 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -10,6 +10,6 @@ CA1856 | | A constant is expected for the parameter | CA1862 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | -CA1865 | | Unnecessary call to 'Contains(item)' | +CA1868 | | Unnecessary call to 'Contains(item)' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 67c217b4c2..369fcef7b7 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-CA1865 +Performance: HA, CA1800-CA1868 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2261 Naming: CA1700-CA1727 From 953a0520b92e92202bffa26d1169d4495b7fd823 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 26 Jul 2023 20:40:03 -0700 Subject: [PATCH 42/42] Update src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs --- .../Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs index c6001d88cd..51f28d13e0 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardSetAddOrRemoveByContainsTests.cs @@ -1096,7 +1096,7 @@ void M() } [Fact] - public async Task RemoveInTernaryWhenFalseWithNonNegatedContains_ReportsDiagnostic_CS() + public async Task RemoveInTernaryWhenFalseWithNonNegatedContains_NoDiagnostic_CS() { string source = """ using System.Collections.Generic;