Skip to content

Add support for code refactorings and code fixes in non-source files#64364

Merged
mavasani merged 28 commits intodotnet:mainfrom
mavasani:XamlCodeActions
Oct 31, 2022
Merged

Add support for code refactorings and code fixes in non-source files#64364
mavasani merged 28 commits intodotnet:mainfrom
mavasani:XamlCodeActions

Conversation

@mavasani
Copy link
Contributor

@mavasani mavasani commented Sep 29, 2022

Addresses CodeRefactoring API and CodeFix API portion of #62877

TODO:

Public APIs added

Code Refactoring API

namespace Microsoft.CodeAnalysis.CodeRefactorings
{
    public readonly struct CodeRefactoringContext
    {
        /// <summary>
        /// TextDocument corresponding to the <see cref="CodeRefactoringContext.Span"/> to refactor.
        /// This property should be used instead of <see cref="CodeRefactoringContext.Document"/> property by
        /// code refactorings that support non-source documents by providing a non-default value for
        /// <see cref="ExportCodeRefactoringProviderAttribute.DocumentKinds"/>
        /// </summary>
        public TextDocument TextDocument { get; }

        /// <summary>
        /// Creates a code refactoring context to be passed into <see cref="CodeRefactoringProvider.ComputeRefactoringsAsync(CodeRefactoringContext)"/> method.
        /// </summary>
        public CodeRefactoringContext(TextDocument document, TextSpan span, Action<CodeAction> registerRefactoring, CancellationToken cancellationToken);
    }

    public sealed class ExportCodeRefactoringProviderAttribute : ExportAttribute
    {
        /// <summary>
        /// The document kinds for which this provider can provide refactorings. See <see cref="TextDocumentKind"/>.
        /// By default, the provider supports refactorings only for source documents, <see cref="TextDocumentKind.Document"/>.
        /// </summary>
        public TextDocumentKind[] DocumentKinds { get; set; }

        /// <summary>
        /// The document extensions for which this provider can provide refactorings.
        /// By default, this value is null and the document extension is not considered to determine applicability of refactorings.
        /// </summary>
        public string[]? DocumentExtensions { get; set; }
    }
}

Code Fix API

namespace Microsoft.CodeAnalysis.CodeFixes
{
    public readonly struct CodeFixContext
    {
        /// <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>
        /// 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);
    }

    public sealed class ExportCodeFixProviderAttribute : ExportAttribute
    {
        /// <summary>
        /// The document kinds for which this provider can provide code fixes. See <see cref="TextDocumentKind"/>.
        /// By default, the provider supports code fixes only for source documents, <see cref="TextDocumentKind.Document"/>.
        /// </summary>
        public TextDocumentKind[] DocumentKinds { get; set; }

        /// <summary>
        /// The document extensions for which this provider can provide code fixes.
        /// By default, this value is null and the document extension is not considered to determine applicability of code fixes.
        /// </summary>
        public string[]? DocumentExtensions { get; set; }
    }
}

Examples

Example code refactoring

[ExportCodeRefactoringProvider(
    LanguageNames.CSharp,
    DocumentKinds = new[] { TextDocumentKind.AdditionalDocument, TextDocumentKind.AnalyzerConfigDocument },
    DocumentExtensions = new[] { ".xaml", ".txt", ".editorconfig", ".globalconfig" })]
[Shared]
internal sealed class MyRefactoring : CodeRefactoringProvider
{
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public MyRefactoring()
    {
    }

    public override Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        context.RegisterRefactoring(CodeAction.Create("My Refactoring",
            createChangedSolution: async ct =>
            {
                var document = context.TextDocument;
                var text = await document.GetTextAsync(ct).ConfigureAwait(false);
                var newText = SourceText.From(text.ToString() + Environment.NewLine + "# Refactored" + Environment.NewLine);
                if (document.Kind == TextDocumentKind.AdditionalDocument)
                    return document.Project.Solution.WithAdditionalDocumentText(document.Id, newText);
                return document.Project.Solution.WithAnalyzerConfigDocumentText(document.Id, newText);
            }));

        return Task.CompletedTask;
    }
}

Example code fix provider

    [ExportCodeFixProvider(
        LanguageNames.CSharp,
        DocumentKinds = new[] { TextDocumentKind.AdditionalDocument },
        DocumentExtensions = new[] { ".txt", ".xaml" })]
    [Shared]
    internal sealed class AdditionalFileCodeFixer : CodeFixProvider
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public AdditionalFileCodeFixer()
        {
        }

        public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create("My0001");

        public override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            context.RegisterCodeFix(CodeAction.Create("My Code Fix",
                createChangedSolution: async ct =>
                {
                    var document = context.TextDocument;
                    var text = await document.GetTextAsync(ct).ConfigureAwait(false);
                    var newText = SourceText.From(text.ToString() + Environment.NewLine + "# Fixed" + Environment.NewLine);
                    return document.Project.Solution.WithAdditionalDocumentText(document.Id, newText);
                }),
                context.Diagnostics[0]);

            return Task.CompletedTask;
        }
    }

Addresses `CodeRefactoring API` portion of dotnet#62877
@ghost ghost added the Area-Analyzers label Sep 29, 2022
@mavasani
Copy link
Contributor Author

@mgoertz-msft

@jmarolf jmarolf self-assigned this Sep 29, 2022
@mavasani mavasani changed the title Add support for code refactorings in non-source files Add support for code refactorings and code fixes in non-source files Oct 4, 2022
@mavasani mavasani marked this pull request as ready for review October 7, 2022 13:15
@mavasani mavasani requested review from a team as code owners October 7, 2022 13:15
Copy link
Contributor

@mgoertz-msft mgoertz-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Thank you for doing this, @mavasani!

I love that you considered the filtering by document extension too. :)

@mavasani
Copy link
Contributor Author

@jasonmalinowski Thank you for the thorough feedback! I think I have addressed most of it, and create one follow-up work item. I also verified that the added integration test passes after the latest commit if I uncomment out [ContentType("text")] attribute on the provider.

@mavasani
Copy link
Contributor Author

@jasonmalinowski can you take another look when convenient?

Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit: Looks great!

@jasonmalinowski
Copy link
Member

@mavasani Apologies for not re-reviewing earlier; I had forgotten I didn't sign off originally.

…rs `[EditorBrowsable(EditorBrowsableState.Never)]`
@mavasani mavasani modified the milestone: 17.5 P2 Oct 27, 2022
@mavasani mavasani enabled auto-merge October 27, 2022 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

6 participants