File: Microsoft.NetCore.Analyzers\Tasks\DoNotCreateTaskCompletionSourceWithWrongArguments.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.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Tasks
{
    /// <summary>CA2247: Do not create TaskCompletionSource with wrong arguments.</summary>
    [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
    public sealed class DoNotCreateTaskCompletionSourceWithWrongArgumentsFixer : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DoNotCreateTaskCompletionSourceWithWrongArguments.RuleId);

        public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            Document doc = context.Document;
            CancellationToken cancellationToken = context.CancellationToken;
            SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            SemanticModel model = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            // If we're able to make the desired substitution...
            var (targetNode, replacementField) = GetTaskCreationOptionsField(context, root, model, cancellationToken);
            if (replacementField != null)
            {
                // ...then offer it.
                string title = MicrosoftNetCoreAnalyzersResources.DoNotCreateTaskCompletionSourceWithWrongArgumentsFix;
                context.RegisterCodeFix(
                    CodeAction.Create(title,
                        async ct =>
                        {
                            // Replace "TaskContinuationOptions.Value" with "TaskCreationOptions.Value"
                            DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false);
                            editor.ReplaceNode(targetNode,
                                editor.Generator.Argument(
                                    editor.Generator.MemberAccessExpression(
                                        editor.Generator.TypeExpressionForStaticMemberAccess(replacementField.ContainingType), replacementField.Name)));
                            return editor.GetChangedDocument();
                        },
                        equivalenceKey: title),
                    context.Diagnostics);
            }

            static (SyntaxNode Expression, IFieldSymbol? ReplacementField) GetTaskCreationOptionsField(
                CodeFixContext context, SyntaxNode root, SemanticModel model, CancellationToken cancellationToken)
            {
                if (// If we can get all the necessary types,
                    model.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTaskCompletionSource1, out _) &&
                    model.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTaskContinuationOptions, out var taskContinutationOptionsType) &&
                    model.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTaskCreationOptions, out INamedTypeSymbol? taskCreationOptionsType) &&

                    // and the provided expression is an argument,
                    root.FindNode(context.Span) is SyntaxNode expression &&
                    model.GetOperationWalkingUpParentChain(expression, cancellationToken) is IArgumentOperation arg &&

                    // and it wraps a conversion from a TaskContinuationOptions member
                    arg.Value is IConversionOperation convert &&
                    convert.Operand is IFieldReferenceOperation field &&
                    taskContinutationOptionsType.Equals(field.Type) &&
                    taskContinutationOptionsType.Equals(field.Field.ContainingType) &&

                    // and that option also exists on TaskCreationOptions,
                    taskCreationOptionsType.GetMembers(field.Field.Name).FirstOrDefault() is IFieldSymbol taskCreationOptionsField)
                {
                    // then hand back the found SyntaxNode and desired TaskCreationOptions field to be substituted.
                    return (expression, taskCreationOptionsField);
                }

                // Otherwise, nothing to fix.
                return default;
            }
        }
    }
}