File: CodeFixes\CodeFixContext.cs
Web Access
Project: ..\..\..\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.Diagnostics;
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>
#pragma warning disable CS0612 // Type or member is obsolete
    public readonly struct CodeFixContext : ITypeScriptCodeFixContext
#pragma warning restore
    {
        private readonly TextDocument _document;
        private readonly TextSpan _span;
        private readonly ImmutableArray<Diagnostic> _diagnostics;
        private readonly CancellationToken _cancellationToken;
        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 => _document;
 
        /// <summary>
        /// Text span within the <see cref="Document"/> or <see cref="TextDocument"/> to fix.
        /// </summary>
        public TextSpan Span => _span;
 
        /// <summary>
        /// Diagnostics to fix.
        /// NOTE: All the diagnostics in this collection have the same <see cref="Span"/>.
        /// </summary>
        public ImmutableArray<Diagnostic> Diagnostics => _diagnostics;
 
        /// <summary>
        /// CancellationToken.
        /// </summary>
        public CancellationToken CancellationToken => _cancellationToken;
 
        /// <summary>
        /// IDE supplied options to use for settings not specified in the corresponding editorconfig file.
        /// These are not available in Code Style layer. Use <see cref="CodeActionOptionsProviders.GetOptionsProvider(CodeFixContext)"/> extension method 
        /// to access these options in code shared with Code Style layer.
        /// </summary>
        /// <remarks>
        /// This is a <see cref="CodeActionOptionsProvider"/> (rather than <see cref="CodeActionOptions"/> directly)
        /// to allow code fix to update documents across multiple projects that differ in language (and hence language specific options).
        /// </remarks>
        internal readonly CodeActionOptionsProvider Options;
 
        /// <summary>
        /// TypeScript specific. https://github.com/dotnet/roslyn/issues/61122
        /// </summary>
        private readonly bool _isBlocking;
 
        [Obsolete]
        bool ITypeScriptCodeFixContext.IsBlocking
            => _isBlocking;
 
        /// <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(document,
                   span,
                   diagnostics,
                   registerCodeFix,
                   CodeActionOptions.DefaultProvider,
                   isBlocking: false,
                   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)
            : this(document,
                   span,
                   diagnostics,
                   registerCodeFix,
                   CodeActionOptions.DefaultProvider,
                   isBlocking: false,
                   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,
                   ImmutableArray.Create(diagnostic),
                   registerCodeFix,
                   CodeActionOptions.DefaultProvider,
                   isBlocking: false,
                   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,
                   ImmutableArray.Create(diagnostic),
                   registerCodeFix,
                   CodeActionOptions.DefaultProvider,
                   isBlocking: false,
                   cancellationToken)
        {
        }
 
        internal CodeFixContext(
            TextDocument document,
            TextSpan span,
            ImmutableArray<Diagnostic> diagnostics,
            Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
            CodeActionOptionsProvider options,
            bool isBlocking,
            CancellationToken cancellationToken)
        {
            VerifyDiagnosticsArgument(diagnostics, span);
 
            _document = document ?? throw new ArgumentNullException(nameof(document));
            _span = span;
            _diagnostics = diagnostics;
            _registerCodeFix = registerCodeFix ?? throw new ArgumentNullException(nameof(registerCodeFix));
            Options = options;
            _isBlocking = isBlocking;
            _cancellationToken = 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, ImmutableArray.Create(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));
            }
        }
    }
 
    [Obsolete]
    internal interface ITypeScriptCodeFixContext
    {
        bool IsBlocking { get; }
    }
}