Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FixAll support for code refactorings #59809

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d9f5725
FixAll public API + core fix all related types + other workspace rela…
mavasani Feb 28, 2022
7812bec
Features layer changes for FixAll support for refactorings
mavasani Feb 28, 2022
26c35ef
Unified suggestion related changes for FixAll support for refactorings
mavasani Feb 28, 2022
c6cc64f
EditorFeatures layer changes for FixAll support for refactorings
mavasani Feb 28, 2022
4630883
VisualStudio layer changes for FixAll support for refactorings
mavasani Feb 28, 2022
96cd412
Proof of concept - "Invert conditional" code refactoring enhancement …
mavasani Feb 28, 2022
f73b283
Merge remote-tracking branch 'upstream/main' into FixAllCodeRefactoring
mavasani Apr 11, 2022
c1adefd
Remove FixAllScope.Selection
mavasani Apr 11, 2022
b463620
Nullable enable
mavasani Apr 11, 2022
b41f729
Share code
mavasani Apr 11, 2022
7ef03b2
Add code refactoring specific API to IFixAllSpanMappingService and up…
mavasani Apr 12, 2022
80dc735
Cleanup to make the APIs consistent with FixAll APIs for code fixes
mavasani Apr 12, 2022
6e86cd6
Make FixAll APIs for refactorings internal for now. #60703 tracks mak…
mavasani Apr 12, 2022
b27b6ca
Fix function ID
mavasani Apr 12, 2022
09d595c
Fixes
mavasani Apr 12, 2022
b2b7cf8
Add unit tests and integration tests
mavasani Apr 13, 2022
4f20ef8
Add integration test support for FixAll for code refactorings and fix…
mavasani Apr 14, 2022
d97f240
Merge remote-tracking branch 'upstream/main' into FixAllCodeRefactoring
mavasani Apr 14, 2022
bd85c93
Fixes from merge
mavasani Apr 14, 2022
a1e77fe
Fix IDE0044
mavasani Apr 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private static async Task GetPreview(TestWorkspace workspace, CodeRefactoringPro
var suggestedAction = new CodeRefactoringSuggestedAction(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
workspace.ExportProvider.GetExportedValue<SuggestedActionsSourceProvider>(),
workspace, textBuffer, provider, codeActions.First());
workspace, textBuffer, provider, codeActions.First(), fixAllFlavors: null);
await suggestedAction.GetPreviewAsync(CancellationToken.None);
Assert.True(extensionManager.IsDisabled(provider));
Assert.False(extensionManager.IsIgnored(provider));
Expand All @@ -85,7 +85,7 @@ private static void DisplayText(TestWorkspace workspace, CodeRefactoringProvider
var suggestedAction = new CodeRefactoringSuggestedAction(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
workspace.ExportProvider.GetExportedValue<SuggestedActionsSourceProvider>(),
workspace, textBuffer, provider, codeActions.First());
workspace, textBuffer, provider, codeActions.First(), fixAllFlavors: null);
_ = suggestedAction.DisplayText;
Assert.True(extensionManager.IsDisabled(provider));
Assert.False(extensionManager.IsIgnored(provider));
Expand All @@ -98,7 +98,7 @@ private static async Task ActionSets(TestWorkspace workspace, CodeRefactoringPro
var suggestedAction = new CodeRefactoringSuggestedAction(
workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
workspace.ExportProvider.GetExportedValue<SuggestedActionsSourceProvider>(),
workspace, textBuffer, provider, codeActions.First());
workspace, textBuffer, provider, codeActions.First(), fixAllFlavors: null);
_ = await suggestedAction.GetActionSetsAsync(CancellationToken.None);
Assert.True(extensionManager.IsDisabled(provider));
Assert.False(extensionManager.IsIgnored(provider));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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.

#nullable disable

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
[ExportWorkspaceServiceFactory(typeof(IFixAllCodeFixGetFixesService), ServiceLayer.Host), Shared]
internal class FixAllCodeFixGetFixesService : IFixAllCodeFixGetFixesService, IWorkspaceServiceFactory
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an existing type that was renamed from FixAllGetFixesService to FixAllCodeFixGetFixesService and some code split into a helper type FixAllGetFixesServiceHelper which is shared between FixAllCodeFixGetFixesService and FixAllCodeRefactoringGetFixesService.

Copy link
Member

Choose a reason for hiding this comment

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

why is it a workspaceservicefactory, instead of just an IWorkspaceService?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is existing code, and I have mimic'ed the same for FixAllCodeRefactoringGetFixesService. I can clean this up.

{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public FixAllCodeFixGetFixesService()
{
}

public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
=> this;
Copy link
Member

Choose a reason for hiding this comment

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

doesn't use workspaceServices, make into IWorkspaceService (no Factory) instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Existing code, but I can cleanup


public async Task<Solution> GetFixAllChangedSolutionAsync(FixAllContext fixAllContext)
{
var codeAction = await GetFixAllCodeActionAsync(fixAllContext).ConfigureAwait(false);
if (codeAction == null)
{
return fixAllContext.Solution;
}
Copy link
Member

Choose a reason for hiding this comment

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

explain?

Copy link
Contributor Author

@mavasani mavasani Apr 20, 2022

Choose a reason for hiding this comment

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

Existing code, but I can doc.

var codeAction = await GetFixAllCodeActionAsync(fixAllContext).ConfigureAwait(false);
if (codeAction == null)
{
return fixAllContext.Solution;
}


fixAllContext.CancellationToken.ThrowIfCancellationRequested();
return await codeAction.GetChangedSolutionInternalAsync(cancellationToken: fixAllContext.CancellationToken).ConfigureAwait(false);
}

public async Task<ImmutableArray<CodeActionOperation>> GetFixAllOperationsAsync(
FixAllContext fixAllContext, bool showPreviewChangesDialog)
{
var codeAction = await GetFixAllCodeActionAsync(fixAllContext).ConfigureAwait(false);
if (codeAction == null)
{
return ImmutableArray<CodeActionOperation>.Empty;
}

return await FixAllGetFixesServiceHelper.GetFixAllOperationsAsync(
codeAction, fixAllContext.State.Project, fixAllContext.State.CorrelationId,
FunctionId.CodeFixes_FixAllOccurrencesPreviewChanges,
showPreviewChangesDialog, fixAllContext.CancellationToken).ConfigureAwait(false);
}

private static async Task<CodeAction> GetFixAllCodeActionAsync(FixAllContext fixAllContext)
{
using (Logger.LogBlock(
FunctionId.CodeFixes_FixAllOccurrencesComputation,
KeyValueLogMessage.Create(LogType.UserAction, m =>
{
m[FixAllLogger.CorrelationId] = fixAllContext.State.CorrelationId;
m[FixAllLogger.FixAllScope] = fixAllContext.State.Scope.ToString();
}),
fixAllContext.CancellationToken))
{
CodeAction action = null;
try
{
action = await fixAllContext.FixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: false);
}
finally
{
if (action != null)
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: true);
}
else
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: false, timedOut: true);
Copy link
Member

Choose a reason for hiding this comment

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

timed out?

Copy link
Member

Choose a reason for hiding this comment

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

do we mean 'canceled'?

Copy link
Contributor Author

@mavasani mavasani Apr 20, 2022

Choose a reason for hiding this comment

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

Again, all of this is existing code. Just renaming the type and splitting out some common code into a shared type led Github to show file as new code.

else
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: false, timedOut: true);
}

}
}

return action;
}
}

internal static Solution PreviewChanges(
Solution currentSolution,
Solution newSolution,
string fixAllPreviewChangesTitle,
string fixAllTopLevelHeader,
string languageOpt,
Workspace workspace,
int? correlationId = null,
CancellationToken cancellationToken = default)
Copy link
Member

Choose a reason for hiding this comment

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

please don't make internal methods have optional cancellation tokens. That just means it's easy for us to forget to pass them along.

Copy link
Contributor Author

@mavasani mavasani Apr 20, 2022

Choose a reason for hiding this comment

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

Existing code, I just refactored it move out common code to a shared type.

internal static Solution PreviewChanges(
Solution currentSolution,
Solution newSolution,
string fixAllPreviewChangesTitle,
string fixAllTopLevelHeader,
string languageOpt,
Workspace workspace,
int? correlationId = null,
CancellationToken cancellationToken = default)
{

=> FixAllGetFixesServiceHelper.PreviewChanges(
currentSolution, newSolution, fixAllPreviewChangesTitle,
fixAllTopLevelHeader, languageOpt, workspace,
FunctionId.CodeFixes_FixAllOccurrencesPreviewChanges,
correlationId, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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.Immutable;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
[ExportWorkspaceServiceFactory(typeof(IFixAllCodeRefactoringGetFixesService), ServiceLayer.Host), Shared]
internal class FixAllCodeRefactoringGetFixesService : IFixAllCodeRefactoringGetFixesService, IWorkspaceServiceFactory
Copy link
Member

Choose a reason for hiding this comment

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

  1. these types need some sort of logic saying that they shoudl be shared with the other.
  2. i feel like a common subclass with generics can help here.
  3. why is this in the wpf layer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i feel like a common subclass with generics can help here.

So the core issue was that similar types like FixAllContext, FixAllProvider, DocumentBasedFixAllProvider are all public types. Adding a common abstract type is not possible, unless we make it public, which would seem odd. One thing we can do is possibly move all the code to shared internal types like CommonFixAllContext, CommonFixAllProvider, etc. and then have conversions between them OR use composition - I'll see which works out better. I think it might be better to just create a separate PR for that approach

{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public FixAllCodeRefactoringGetFixesService()
{
}

public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
=> this;

public async Task<Solution?> GetFixAllChangedSolutionAsync(FixAllContext fixAllContext)
{
var codeAction = await GetFixAllCodeActionAsync(fixAllContext).ConfigureAwait(false);
if (codeAction == null)
{
return fixAllContext.Project.Solution;
Copy link
Member

Choose a reason for hiding this comment

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

why is this returning .Project.Solution. Code in prior class returned just .Solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought I removed Solution property from this context because it was just returning Project.Solution. Let me clean this up using the Generics/Type hierarchy suggestion you provided and make it consistent.

}

fixAllContext.CancellationToken.ThrowIfCancellationRequested();
return await codeAction.GetChangedSolutionInternalAsync(cancellationToken: fixAllContext.CancellationToken).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

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

i hope all the fixAllContext.CancellationTokens are being used properly :)

}

public async Task<ImmutableArray<CodeActionOperation>> GetFixAllOperationsAsync(FixAllContext fixAllContext)
{
var codeAction = await GetFixAllCodeActionAsync(fixAllContext).ConfigureAwait(false);
if (codeAction == null)
{
return ImmutableArray<CodeActionOperation>.Empty;
}

return await FixAllGetFixesServiceHelper.GetFixAllOperationsAsync(
codeAction, fixAllContext.State.Project, fixAllContext.State.CorrelationId,
FunctionId.Refactoring_FixAllOccurrencesPreviewChanges,
showPreviewChangesDialog: true, fixAllContext.CancellationToken).ConfigureAwait(false);
}

private static async Task<CodeAction?> GetFixAllCodeActionAsync(FixAllContext fixAllContext)
{
using (Logger.LogBlock(
FunctionId.CodeFixes_FixAllOccurrencesComputation,
KeyValueLogMessage.Create(LogType.UserAction, m =>
{
m[FixAllLogger.CorrelationId] = fixAllContext.State.CorrelationId;
m[FixAllLogger.FixAllScope] = fixAllContext.State.FixAllScope.ToString();
}),
fixAllContext.CancellationToken))
{
CodeAction? action = null;
try
{
action = await fixAllContext.FixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: false);
}
finally
{
if (action != null)
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: true);
}
else
{
FixAllLogger.LogComputationResult(fixAllContext.State.CorrelationId, completed: false, timedOut: true);
}
}

return action;
}
}
}
}
Loading