| File: Microsoft.NetCore.Analyzers\Performance\DoNotGuardCall.Fixer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.NetAnalyzers; namespace Microsoft.NetCore.Analyzers.Performance { /// <summary> /// CA1853: <inheritdoc cref="MicrosoftNetCoreAnalyzersResources.DoNotGuardDictionaryRemoveByContainsKeyTitle"/> /// CA1868: <inheritdoc cref="MicrosoftNetCoreAnalyzersResources.DoNotGuardSetAddOrRemoveByContainsTitle"/> /// </summary> public abstract class DoNotGuardCallFixer : SyntaxEditorBasedCodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create( DoNotGuardCallAnalyzer.DoNotGuardDictionaryRemoveByContainsKeyRuleId, DoNotGuardCallAnalyzer.DoNotGuardSetAddOrRemoveByContainsRuleId); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); if (TryGetGuardedCallInElse(root, context.Diagnostics[0]) is null) { return; } RegisterCodeFix( context, MicrosoftNetCoreAnalyzersResources.RemoveRedundantGuardCallCodeFixTitle, context.Diagnostics[0].Descriptor.Id); } protected sealed override Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken) { if (TryGetGuardedCallInElse(editor.OriginalRoot, diagnostic) is not bool guardedCallInElse || editor.OriginalRoot.FindNode(diagnostic.AdditionalLocations[0].SourceSpan) is not SyntaxNode conditionalSyntax) { return Task.CompletedTask; } // Both shapes of the fix re-emit statements taken from inside the conditional, so they have to be // read off the conditional as the fixes before this one left it rather than off the original tree. editor.ReplaceNode( conditionalSyntax, (currentConditional, generator) => ReplaceConditionWithChild(currentConditional, guardedCallInElse, generator)); return Task.CompletedTask; } /// <summary> /// Reports whether the guarded call sits in the conditional's <c>else</c> branch, or /// <see langword="null"/> when the shape is not one the fix handles. /// </summary> private bool? TryGetGuardedCallInElse(SyntaxNode root, Diagnostic diagnostic) { if (diagnostic.AdditionalLocations.Count < 2 || root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan) is not SyntaxNode conditionalSyntax || root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan) is not SyntaxNode childStatementSyntax || !SyntaxSupportedByFixer(conditionalSyntax, childStatementSyntax)) { return null; } return IsInElseBranch(childStatementSyntax); } protected abstract bool SyntaxSupportedByFixer(SyntaxNode conditionalSyntax, SyntaxNode childStatementSyntax); protected abstract bool IsInElseBranch(SyntaxNode childStatementSyntax); /// <summary> /// Rewrites <paramref name="currentConditional"/> into the guarded call alone, or - when the conditional /// has an <c>else</c> - into the other branch guarded by the negated call. /// </summary> protected abstract SyntaxNode ReplaceConditionWithChild(SyntaxNode currentConditional, bool guardedCallInElse, SyntaxGenerator generator); } }