|
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Xunit;
using FixAllScope = Microsoft.CodeAnalysis.CodeFixes.FixAllScope;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions
{
public abstract partial class AbstractCodeActionTest : AbstractCodeActionOrUserDiagnosticTest
{
protected abstract CodeRefactoringProvider CreateCodeRefactoringProvider(
Workspace workspace, TestParameters parameters);
protected override async Task<(ImmutableArray<CodeAction>, CodeAction actionToInvoke)> GetCodeActionsAsync(
TestWorkspace workspace, TestParameters parameters = null)
{
parameters ??= TestParameters.Default;
GetDocumentAndSelectSpanOrAnnotatedSpan(workspace, out var document, out var span, out var annotation);
var refactoring = await GetCodeRefactoringAsync(workspace, parameters);
var actions = refactoring == null
? ImmutableArray<CodeAction>.Empty
: refactoring.CodeActions.Select(n => n.action).AsImmutable();
actions = MassageActions(actions);
var fixAllScope = GetFixAllScope(annotation);
if (fixAllScope is FixAllScope.ContainingMember or FixAllScope.ContainingType &&
document.GetLanguageService<IFixAllSpanMappingService>() is IFixAllSpanMappingService spanMappingService)
{
var documentsAndSpansToFix = await spanMappingService.GetFixAllSpansAsync(
document, span, fixAllScope.Value, CancellationToken.None).ConfigureAwait(false);
if (documentsAndSpansToFix.IsEmpty)
{
return (ImmutableArray<CodeAction>.Empty, null);
}
}
var actionToInvoke = actions.IsDefaultOrEmpty ? null : actions[parameters.index];
if (actionToInvoke == null || fixAllScope == null)
return (actions, actionToInvoke);
var fixAllCodeAction = await GetFixAllFixAsync(actionToInvoke,
refactoring.Provider, refactoring.CodeActionOptionsProvider, document, span, fixAllScope.Value).ConfigureAwait(false);
if (fixAllCodeAction == null)
return (ImmutableArray<CodeAction>.Empty, null);
return (ImmutableArray.Create(fixAllCodeAction), fixAllCodeAction);
}
private static async Task<CodeAction> GetFixAllFixAsync(
CodeAction originalCodeAction,
CodeRefactoringProvider provider,
CodeActionOptionsProvider optionsProvider,
Document document,
TextSpan selectionSpan,
FixAllScope scope)
{
var fixAllProvider = provider.GetFixAllProvider();
if (fixAllProvider == null || !fixAllProvider.GetSupportedFixAllScopes().Contains(scope))
return null;
var fixAllState = new FixAllState(fixAllProvider, document, selectionSpan, provider, optionsProvider, scope, originalCodeAction);
var fixAllContext = new FixAllContext(fixAllState, new ProgressTracker(), CancellationToken.None);
return await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
}
protected override Task<ImmutableArray<Diagnostic>> GetDiagnosticsWorkerAsync(TestWorkspace workspace, TestParameters parameters)
=> SpecializedTasks.EmptyImmutableArray<Diagnostic>();
internal async Task<CodeRefactoring> GetCodeRefactoringAsync(
TestWorkspace workspace, TestParameters parameters)
{
GetDocumentAndSelectSpanOrAnnotatedSpan(workspace, out var document, out var span, out _);
return await GetCodeRefactoringAsync(document, span, workspace, parameters).ConfigureAwait(false);
}
internal async Task<CodeRefactoring> GetCodeRefactoringAsync(
Document document,
TextSpan selectedOrAnnotatedSpan,
TestWorkspace workspace,
TestParameters parameters)
{
var provider = CreateCodeRefactoringProvider(workspace, parameters);
var actions = ArrayBuilder<(CodeAction, TextSpan?)>.GetInstance();
var codeActionOptionsProvider = parameters.globalOptions?.IsEmpty() == false
? CodeActionOptionsStorage.GetCodeActionOptionsProvider(workspace.GlobalOptions)
: CodeActionOptions.DefaultProvider;
var context = new CodeRefactoringContext(document, selectedOrAnnotatedSpan, (a, t) => actions.Add((a, t)), codeActionOptionsProvider, isBlocking: false, CancellationToken.None);
await provider.ComputeRefactoringsAsync(context);
var result = actions.Count > 0 ? new CodeRefactoring(provider, actions.ToImmutable(), FixAllProviderInfo.Create(provider), codeActionOptionsProvider) : null;
actions.Free();
return result;
}
protected async Task TestActionOnLinkedFiles(
TestWorkspace workspace,
string expectedText,
CodeAction action,
string expectedPreviewContents = null)
{
var operations = await VerifyActionAndGetOperationsAsync(workspace, action);
await VerifyPreviewContents(workspace, expectedPreviewContents, operations);
var applyChangesOperation = operations.OfType<ApplyChangesOperation>().First();
await applyChangesOperation.TryApplyAsync(workspace, workspace.CurrentSolution, new ProgressTracker(), CancellationToken.None);
foreach (var document in workspace.Documents)
{
var fixedRoot = await workspace.CurrentSolution.GetDocument(document.Id).GetSyntaxRootAsync();
var actualText = fixedRoot.ToFullString();
Assert.Equal(expectedText, actualText);
}
}
private static async Task VerifyPreviewContents(
TestWorkspace workspace, string expectedPreviewContents,
ImmutableArray<CodeActionOperation> operations)
{
if (expectedPreviewContents != null)
{
var editHandler = workspace.ExportProvider.GetExportedValue<ICodeActionEditHandlerService>();
var previews = await editHandler.GetPreviewsAsync(workspace, operations, CancellationToken.None);
var content = (await previews.GetPreviewsAsync())[0];
var diffView = content as DifferenceViewerPreview;
Assert.NotNull(diffView.Viewer);
var previewContents = diffView.Viewer.RightView.TextBuffer.AsTextContainer().CurrentText.ToString();
diffView.Dispose();
Assert.Equal(expectedPreviewContents, previewContents);
}
}
protected static Document GetDocument(TestWorkspace workspace)
=> workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
internal static void EnableOptions(
ImmutableArray<PickMembersOption> options,
params string[] ids)
{
foreach (var id in ids)
{
EnableOption(options, id);
}
}
internal static void EnableOption(ImmutableArray<PickMembersOption> options, string id)
{
var option = options.FirstOrDefault(o => o.Id == id);
if (option != null)
{
option.Value = true;
}
}
internal Task TestWithPickMembersDialogAsync(
string initialMarkup,
string expectedMarkup,
string[] chosenSymbols,
Action<ImmutableArray<PickMembersOption>> optionsCallback = null,
int index = 0,
TestParameters parameters = null)
{
var ps = parameters ?? TestParameters.Default;
var pickMembersService = new TestPickMembersService(chosenSymbols.AsImmutableOrNull(), optionsCallback);
return TestInRegularAndScript1Async(
initialMarkup, expectedMarkup,
index,
ps.WithFixProviderData(pickMembersService));
}
}
[ExportWorkspaceService(typeof(IPickMembersService), ServiceLayer.Host), Shared, PartNotDiscoverable]
internal class TestPickMembersService : IPickMembersService
{
public ImmutableArray<string> MemberNames;
public Action<ImmutableArray<PickMembersOption>> OptionsCallback;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TestPickMembersService()
{
}
#pragma warning disable RS0034 // Exported parts should be marked with 'ImportingConstructorAttribute'
public TestPickMembersService(
ImmutableArray<string> memberNames,
Action<ImmutableArray<PickMembersOption>> optionsCallback)
{
MemberNames = memberNames;
OptionsCallback = optionsCallback;
}
#pragma warning restore RS0034 // Exported parts should be marked with 'ImportingConstructorAttribute'
public PickMembersResult PickMembers(
string title,
ImmutableArray<ISymbol> members,
ImmutableArray<PickMembersOption> options,
bool selectAll)
{
OptionsCallback?.Invoke(options);
return new PickMembersResult(
MemberNames.IsDefault
? members
: MemberNames.SelectAsArray(n => members.Single(m => m.Name == n)),
options,
selectAll);
}
}
}
|