File: CodeFixes\CodeFixContext.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CodeFixes;
 
/// <summary>
/// Context for code fixes provided by a <see cref="CodeFixProvider"/>.
/// </summary>
public readonly struct CodeFixContext
{
    private readonly Action<CodeAction, ImmutableArray<Diagnostic>> _registerCodeFix;
 
    /// <summary>
    /// Document corresponding to the <see cref="CodeFixContext.Span"/> to fix.
    /// For code fixes that support non-source documents by providing a non-default value for
    /// <see cref="ExportCodeFixProviderAttribute.DocumentKinds"/>, this property will
    /// throw an <see cref="InvalidOperationException"/>. Such fixers should use the
    /// <see cref="CodeFixContext.TextDocument"/> property instead.
    /// </summary>
    public Document Document
    {
        get
        {
            if (TextDocument is not Document document)
            {
                throw new InvalidOperationException(WorkspacesResources.Use_TextDocument_property_instead_of_Document_property_as_the_provider_supports_non_source_text_documents);
            }
 
            return document;
        }
    }
 
    /// <summary>
    /// TextDocument corresponding to the <see cref="Span"/> to fix.
    /// This property should be used instead of <see cref="Document"/> property by
    /// code fixes that support non-source documents by providing a non-default value for
    /// <see cref="ExportCodeFixProviderAttribute.DocumentKinds"/>
    /// </summary>
    public TextDocument TextDocument { get; }
 
    /// <summary>
    /// Text span within the <see cref="Document"/> or <see cref="TextDocument"/> to fix.
    /// </summary>
    public TextSpan Span { get; }
 
    /// <summary>
    /// Diagnostics to fix.
    /// NOTE: All the diagnostics in this collection have the same <see cref="Span"/>.
    /// </summary>
    public ImmutableArray<Diagnostic> Diagnostics { get; }
 
    /// <summary>
    /// CancellationToken.
    /// </summary>
    public CancellationToken CancellationToken { get; }
 
    /// <summary>
    /// Creates a code fix context to be passed into <see cref="CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext)"/> method.
    /// </summary>
    /// <param name="document">Document to fix.</param>
    /// <param name="span">Text span within the <paramref name="document"/> to fix.</param>
    /// <param name="diagnostics">
    /// Diagnostics to fix.
    /// All the diagnostics must have the same <paramref name="span"/>.
    /// Additionally, the <see cref="Diagnostic.Id"/> of each diagnostic must be in the set of the <see cref="CodeFixProvider.FixableDiagnosticIds"/> of the associated <see cref="CodeFixProvider"/>.
    /// </param>
    /// <param name="registerCodeFix">Delegate to register a <see cref="CodeAction"/> fixing a subset of diagnostics.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <exception cref="ArgumentNullException">Throws this exception if any of the arguments is null.</exception>
    /// <exception cref="ArgumentException">
    /// Throws this exception if the given <paramref name="diagnostics"/> is empty,
    /// has a null element or has an element whose span is not equal to <paramref name="span"/>.
    /// </exception>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public CodeFixContext(
        Document document,
        TextSpan span,
        ImmutableArray<Diagnostic> diagnostics,
        Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
        CancellationToken cancellationToken)
        : this((TextDocument)document,
               span,
               diagnostics,
               registerCodeFix,
               cancellationToken)
    {
    }
 
    /// <summary>
    /// Creates a code fix context to be passed into <see cref="CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext)"/> method.
    /// </summary>
    /// <param name="document">Text document to fix.</param>
    /// <param name="span">Text span within the <paramref name="document"/> to fix.</param>
    /// <param name="diagnostics">
    /// Diagnostics to fix.
    /// All the diagnostics must have the same <paramref name="span"/>.
    /// Additionally, the <see cref="Diagnostic.Id"/> of each diagnostic must be in the set of the <see cref="CodeFixProvider.FixableDiagnosticIds"/> of the associated <see cref="CodeFixProvider"/>.
    /// </param>
    /// <param name="registerCodeFix">Delegate to register a <see cref="CodeAction"/> fixing a subset of diagnostics.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <exception cref="ArgumentNullException">Throws this exception if any of the arguments is null.</exception>
    /// <exception cref="ArgumentException">
    /// Throws this exception if the given <paramref name="diagnostics"/> is empty,
    /// has a null element or has an element whose span is not equal to <paramref name="span"/>.
    /// </exception>
    public CodeFixContext(
        TextDocument document,
        TextSpan span,
        ImmutableArray<Diagnostic> diagnostics,
        Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
        CancellationToken cancellationToken)
    {
        VerifyDiagnosticsArgument(diagnostics, span);
 
        TextDocument = document ?? throw new ArgumentNullException(nameof(document));
        Span = span;
        Diagnostics = diagnostics;
        _registerCodeFix = registerCodeFix ?? throw new ArgumentNullException(nameof(registerCodeFix));
        CancellationToken = cancellationToken;
    }
 
    /// <summary>
    /// Creates a code fix context to be passed into <see cref="CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext)"/> method.
    /// </summary>
    /// <param name="document">Document to fix.</param>
    /// <param name="diagnostic">
    /// Diagnostic to fix.
    /// The <see cref="Diagnostic.Id"/> of this diagnostic must be in the set of the <see cref="CodeFixProvider.FixableDiagnosticIds"/> of the associated <see cref="CodeFixProvider"/>.
    /// </param>
    /// <param name="registerCodeFix">Delegate to register a <see cref="CodeAction"/> fixing a subset of diagnostics.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <exception cref="ArgumentNullException">Throws this exception if any of the arguments is null.</exception>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public CodeFixContext(
        Document document,
        Diagnostic diagnostic,
        Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
        CancellationToken cancellationToken)
        : this(document,
               (diagnostic ?? throw new ArgumentNullException(nameof(diagnostic))).Location.SourceSpan,
               [diagnostic],
               registerCodeFix,
               cancellationToken)
    {
    }
 
    /// <summary>
    /// Creates a code fix context to be passed into <see cref="CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext)"/> method.
    /// </summary>
    /// <param name="document">Text document to fix.</param>
    /// <param name="diagnostic">
    /// Diagnostic to fix.
    /// The <see cref="Diagnostic.Id"/> of this diagnostic must be in the set of the <see cref="CodeFixProvider.FixableDiagnosticIds"/> of the associated <see cref="CodeFixProvider"/>.
    /// </param>
    /// <param name="registerCodeFix">Delegate to register a <see cref="CodeAction"/> fixing a subset of diagnostics.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <exception cref="ArgumentNullException">Throws this exception if any of the arguments is null.</exception>
    public CodeFixContext(
        TextDocument document,
        Diagnostic diagnostic,
        Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
        CancellationToken cancellationToken)
        : this(document,
               (diagnostic ?? throw new ArgumentNullException(nameof(diagnostic))).Location.SourceSpan,
               [diagnostic],
               registerCodeFix,
               cancellationToken)
    {
    }
 
    /// <summary>
    /// Add supplied <paramref name="action"/> to the list of fixes that will be offered to the user.
    /// </summary>
    /// <param name="action">The <see cref="CodeAction"/> that will be invoked to apply the fix.</param>
    /// <param name="diagnostic">The subset of <see cref="Diagnostics"/> being addressed / fixed by the <paramref name="action"/>.</param>
    public void RegisterCodeFix(CodeAction action, Diagnostic diagnostic)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }
 
        if (diagnostic == null)
        {
            throw new ArgumentNullException(nameof(diagnostic));
        }
 
        _registerCodeFix(action, [diagnostic]);
    }
 
    /// <summary>
    /// Add supplied <paramref name="action"/> to the list of fixes that will be offered to the user.
    /// </summary>
    /// <param name="action">The <see cref="CodeAction"/> that will be invoked to apply the fix.</param>
    /// <param name="diagnostics">The subset of <see cref="Diagnostics"/> being addressed / fixed by the <paramref name="action"/>.</param>
    public void RegisterCodeFix(CodeAction action, IEnumerable<Diagnostic> diagnostics)
    {
        if (diagnostics == null)
        {
            throw new ArgumentNullException(nameof(diagnostics));
        }
 
        RegisterCodeFix(action, diagnostics.ToImmutableArray());
    }
 
    /// <summary>
    /// Add supplied <paramref name="action"/> to the list of fixes that will be offered to the user.
    /// </summary>
    /// <param name="action">The <see cref="CodeAction"/> that will be invoked to apply the fix.</param>
    /// <param name="diagnostics">The subset of <see cref="Diagnostics"/> being addressed / fixed by the <paramref name="action"/>.</param>
    public void RegisterCodeFix(CodeAction action, ImmutableArray<Diagnostic> diagnostics)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }
 
        VerifyDiagnosticsArgument(diagnostics, Span);
 
        // TODO: 
        // - Check that all diagnostics are unique (no duplicates).
        // - Check that supplied diagnostics form subset of diagnostics originally
        //   passed to the provider via CodeFixContext.Diagnostics.
 
        _registerCodeFix(action, diagnostics);
    }
 
    private static void VerifyDiagnosticsArgument(ImmutableArray<Diagnostic> diagnostics, TextSpan span)
    {
        if (diagnostics.IsDefaultOrEmpty)
        {
            throw new ArgumentException(WorkspacesResources.At_least_one_diagnostic_must_be_supplied, nameof(diagnostics));
        }
 
        if (diagnostics.Any(static d => d == null))
        {
            throw new ArgumentException(WorkspaceExtensionsResources.Supplied_diagnostic_cannot_be_null, nameof(diagnostics));
        }
 
        if (diagnostics.Any((d, span) => d.Location.SourceSpan != span, span))
        {
            throw new ArgumentException(string.Format(WorkspacesResources.Diagnostic_must_have_span_0, span.ToString()), nameof(diagnostics));
        }
    }
}