未验证 提交 b289d608 编写于 作者: J Julien Couvreur 提交者: GitHub

GenerateDeconstructMethod code fixer (#28286)

上级 789d523e
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.CodeFixes.GenerateDeconstructMethod;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.GenerateDeconstructMethod
{
public class GenerateDeconstructMethodTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (null, new GenerateDeconstructMethodCodeFixProvider());
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_Simple()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
(int x, int y) = [|this|];
}
}",
@"using System;
class Class
{
private void Deconstruct(out int x, out int y)
{
throw new NotImplementedException();
}
void Method()
{
(int x, int y) = this;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_TypeParamters()
{
await TestInRegularAndScriptAsync(
@"class Class<T>
{
void Method<U>()
{
(T x, U y) = [|this|];
}
}",
@"using System;
class Class<T>
{
private void Deconstruct(out T x, out object y)
{
throw new NotImplementedException();
}
void Method<U>()
{
(T x, U y) = this;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_OtherDeconstructMethods()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
(int x, int y) = [|this|];
}
void Deconstruct(out int x) => throw null;
void Deconstruct(out int x, out int y, out int z) => throw null;
}",
@"using System;
class Class
{
void Method()
{
(int x, int y) = this;
}
void Deconstruct(out int x) => throw null;
void Deconstruct(out int x, out int y, out int z) => throw null;
private void Deconstruct(out int x, out int y)
{
throw new NotImplementedException();
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_AlreadySuccessfull()
{
await TestMissingInRegularAndScriptAsync(
@"class Class
{
void Method()
{
(int x, int y) = [|this|];
}
void Deconstruct(out int x, out int y) => throw null;
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_UndeterminedType()
{
await TestInRegularAndScript1Async(
@"class Class
{
void Method()
{
(var x, var y) = [|this|];
}
}",
@"using System;
class Class
{
private void Deconstruct(out object x, out object y)
{
throw new NotImplementedException();
}
void Method()
{
(var x, var y) = this;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_UndeterminedType2()
{
await TestInRegularAndScript1Async(
@"class Class
{
void Method()
{
var (x, y) = [|this|];
}
}",
@"using System;
class Class
{
private void Deconstruct(out object x, out object y)
{
throw new NotImplementedException();
}
void Method()
{
var (x, y) = this;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionDeclaration_BuiltinType()
{
await TestMissingInRegularAndScriptAsync(
@"class Class
{
void Method()
{
(int x, int y) = [|1|];
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionAssignment()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
int x, y;
(x, y) = [|this|];
}
}",
@"using System;
class Class
{
private void Deconstruct(out int x, out int y)
{
throw new NotImplementedException();
}
void Method()
{
int x, y;
(x, y) = this;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionAssignment_Nested()
{
// We only offer a fix for non-nested deconstruction, at the moment
await TestMissingInRegularAndScriptAsync(
@"class Class
{
void Method()
{
int x, y, z;
((x, y), z) = ([|this|], 0);
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestDeconstructionAssignment_Array()
{
await TestMissingInRegularAndScriptAsync(
@"class Class
{
void Method()
{
int x, y;
(x, y) = [|new[] { this }|];
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestSimpleDeconstructionForeach()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method()
{
foreach ((int x, int y) in new[] { [|this|] }) { }
}
}",
@"using System;
class Class
{
private void Deconstruct(out int x, out int y)
{
throw new NotImplementedException();
}
void Method()
{
foreach ((int x, int y) in new[] { this }) { }
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateMethod)]
public async Task TestSimpleDeconstructionForeach_AnotherType()
{
await TestInRegularAndScriptAsync(
@"class Class
{
void Method(D d)
{
foreach ((int x, int y) in new[] { [|d|] }) { }
}
}
class D
{
}",
@"using System;
class Class
{
void Method(D d)
{
foreach ((int x, int y) in new[] { d }) { }
}
}
class D
{
internal void Deconstruct(out int x, out int y)
{
throw new NotImplementedException();
}
}");
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.GenerateDeconstructMethod
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.GenerateDeconstructMethod), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateEnumMember)]
#pragma warning disable RS1016 // Code fix providers should provide FixAll support. https://github.com/dotnet/roslyn/issues/23528
internal class GenerateDeconstructMethodCodeFixProvider : CodeFixProvider
#pragma warning restore RS1016 // Code fix providers should provide FixAll support.
{
private const string CS8129 = nameof(CS8129); // No suitable Deconstruct instance or extension method was found...
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CS8129);
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
// Not supported in REPL
if (context.Project.IsSubmission)
{
return;
}
var document = context.Document;
var cancellationToken = context.CancellationToken;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var span = context.Span;
var token = root.FindToken(span.Start);
var deconstruction = token.GetAncestors<SyntaxNode>()
.FirstOrDefault(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression, SyntaxKind.ForEachVariableStatement));
if (deconstruction is null)
{
Debug.Fail("The diagnostic can only be produced in context of a deconstruction-assignment or deconstruction-foreach");
return;
}
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
DeconstructionInfo info;
ITypeSymbol type;
ExpressionSyntax target;
switch (deconstruction)
{
case ForEachVariableStatementSyntax @foreach:
info = model.GetDeconstructionInfo(@foreach);
type = model.GetForEachStatementInfo(@foreach).ElementType;
target = @foreach.Variable;
break;
case AssignmentExpressionSyntax assignment:
info = model.GetDeconstructionInfo(assignment);
type = model.GetTypeInfo(assignment.Right).Type;
target = assignment.Left;
break;
default:
throw ExceptionUtilities.Unreachable;
}
if (type.Kind != SymbolKind.NamedType)
{
return;
}
if (info.Method != null || !info.Nested.IsEmpty)
{
// There is already a Deconstruct method, or we have a nesting situation
return;
}
var service = document.GetLanguageService<IGenerateDeconstructMemberService>();
var codeActions = await service.GenerateDeconstructMethodAsync(document, target, (INamedTypeSymbol)type, cancellationToken).ConfigureAwait(false);
Debug.Assert(!codeActions.IsDefault);
context.RegisterFixes(codeActions, context.Diagnostics);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.GenerateMember.GenerateParameterizedMember;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.GenerateMember.GenerateMethod
{
[ExportLanguageService(typeof(IGenerateDeconstructMemberService), LanguageNames.CSharp), Shared]
internal sealed class CSharpGenerateDeconstructMethodService :
AbstractGenerateDeconstructMethodService<CSharpGenerateDeconstructMethodService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>
{
protected override bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType)
=> containingType.ContainingTypesOrSelfHasUnsafeKeyword();
protected override AbstractInvocationInfo CreateInvocationMethodInfo(SemanticDocument document, AbstractGenerateParameterizedMemberService<CSharpGenerateDeconstructMethodService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>.State state)
=> new CSharpGenerateParameterizedMemberService<CSharpGenerateDeconstructMethodService>.InvocationExpressionInfo(document, state);
protected override bool AreSpecialOptionsActive(SemanticModel semanticModel)
=> CSharpCommonGenerationServiceMethods.AreSpecialOptionsActive(semanticModel);
protected override bool IsValidSymbol(ISymbol symbol, SemanticModel semanticModel)
=> CSharpCommonGenerationServiceMethods.IsValidSymbol(symbol, semanticModel);
}
}
......@@ -17,7 +17,7 @@
namespace Microsoft.CodeAnalysis.CSharp.GenerateMember.GenerateMethod
{
[ExportLanguageService(typeof(IGenerateParameterizedMemberService), LanguageNames.CSharp), Shared]
internal partial class CSharpGenerateMethodService :
internal sealed class CSharpGenerateMethodService :
AbstractGenerateMethodService<CSharpGenerateMethodService, SimpleNameSyntax, ExpressionSyntax, InvocationExpressionSyntax>
{
protected override bool IsExplicitInterfaceGeneration(SyntaxNode node)
......
......@@ -30,6 +30,7 @@ internal static class PredefinedCodeFixProviderNames
public const string GenerateVariable = nameof(GenerateVariable);
public const string GenerateMethod = nameof(GenerateMethod);
public const string GenerateConversion = nameof(GenerateConversion);
public const string GenerateDeconstructMethod = nameof(GenerateDeconstructMethod);
public const string GenerateType = nameof(GenerateType);
public const string ImplementAbstractClass = nameof(ImplementAbstractClass);
public const string ImplementInterface = nameof(ImplementInterface);
......
......@@ -47,7 +47,7 @@ protected new class State : AbstractGenerateParameterizedMemberService<TService,
}
}
return TryFinishInitializingState(service, document, cancellationToken);
return TryFinishInitializingStateAsync(service, document, cancellationToken);
}
private bool TryInitializeExplicitConversion(TService service, SemanticDocument document, SyntaxNode node, CancellationToken cancellationToken)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember
{
internal partial class AbstractGenerateDeconstructMethodService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>
{
internal new class State :
AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>.State
{
/// <summary>
/// Make a State instance representing the Deconstruct method we want to generate.
/// The method will be called "Deconstruct". It will be a member of `typeToGenerateIn`.
/// Its arguments will be based on `targetVariables`.
/// </summary>
public static async Task<State> GenerateDeconstructMethodStateAsync(
TService service,
SemanticDocument document,
SyntaxNode targetVariables,
INamedTypeSymbol typeToGenerateIn,
CancellationToken cancellationToken)
{
var state = new State();
if (!await state.TryInitializeMethodAsync(service, document, targetVariables, typeToGenerateIn, cancellationToken).ConfigureAwait(false))
{
return null;
}
return state;
}
private async Task<bool> TryInitializeMethodAsync(
TService service,
SemanticDocument document,
SyntaxNode targetVariables,
INamedTypeSymbol typeToGenerateIn,
CancellationToken cancellationToken)
{
this.TypeToGenerateIn = typeToGenerateIn;
this.IsStatic = false;
var generator = SyntaxGenerator.GetGenerator(document.Document);
this.IdentifierToken = generator.Identifier(WellKnownMemberNames.DeconstructMethodName);
this.MethodGenerationKind = MethodGenerationKind.Member;
MethodKind = MethodKind.Ordinary;
cancellationToken.ThrowIfCancellationRequested();
var semanticModel = document.SemanticModel;
this.ContainingType = semanticModel.GetEnclosingNamedType(targetVariables.SpanStart, cancellationToken);
if (this.ContainingType == null)
{
return false;
}
var parameters = TryMakeParameters(semanticModel, targetVariables, cancellationToken);
if (parameters.IsDefault)
{
return false;
}
var methodSymbol = CodeGenerationSymbolFactory.CreateMethodSymbol(
attributes: default,
accessibility: default,
modifiers: default,
returnType: semanticModel.Compilation.GetSpecialType(SpecialType.System_Void),
refKind: RefKind.None,
explicitInterfaceImplementations: default,
name: null,
typeParameters: default,
parameters);
this.SignatureInfo = new MethodSignatureInfo(document, this, methodSymbol);
return await TryFinishInitializingStateAsync(service, document, cancellationToken).ConfigureAwait(false);
}
private static ImmutableArray<IParameterSymbol> TryMakeParameters(
SemanticModel semanticModel, SyntaxNode target, CancellationToken cancellationToken)
{
var targetType = semanticModel.GetTypeInfo(target).Type;
if (targetType?.IsTupleType != true)
{
return default;
}
var tupleElements = ((INamedTypeSymbol)targetType).TupleElements;
var builder = ArrayBuilder<IParameterSymbol>.GetInstance(tupleElements.Length);
foreach (var element in tupleElements)
{
builder.Add(CodeGenerationSymbolFactory.CreateParameterSymbol(
attributes: default, RefKind.Out, isParams: false, element.Type, element.Name));
}
return builder.ToImmutableAndFree();
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Internal.Log;
namespace Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember
{
internal abstract partial class AbstractGenerateDeconstructMethodService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax> :
AbstractGenerateParameterizedMemberService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>,
IGenerateDeconstructMemberService
where TService : AbstractGenerateDeconstructMethodService<TService, TSimpleNameSyntax, TExpressionSyntax, TInvocationExpressionSyntax>
where TSimpleNameSyntax : TExpressionSyntax
where TExpressionSyntax : SyntaxNode
where TInvocationExpressionSyntax : TExpressionSyntax
{
public async Task<ImmutableArray<CodeAction>> GenerateDeconstructMethodAsync(
Document document,
SyntaxNode leftSide,
INamedTypeSymbol typeToGenerateIn,
CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Refactoring_GenerateMember_GenerateMethod, cancellationToken))
{
var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var state = await State.GenerateDeconstructMethodStateAsync(
(TService)this, semanticDocument, leftSide, typeToGenerateIn, cancellationToken).ConfigureAwait(false);
return state != null ? GetActions(document, state, cancellationToken) : ImmutableArray<CodeAction>.Empty;
}
}
}
}
......@@ -67,7 +67,7 @@ internal new class State : AbstractGenerateParameterizedMemberService<TService,
}
}
return TryFinishInitializingState(service, document, cancellationToken);
return TryFinishInitializingStateAsync(service, document, cancellationToken);
}
private bool TryInitializeExplicitInterface(
......
......@@ -50,7 +50,7 @@ public Location Location
}
}
protected async Task<bool> TryFinishInitializingState(TService service, SemanticDocument document, CancellationToken cancellationToken)
protected async Task<bool> TryFinishInitializingStateAsync(TService service, SemanticDocument document, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
this.TypeToGenerateIn = await SymbolFinder.FindSourceDefinitionAsync(this.TypeToGenerateIn, document.Project.Solution, cancellationToken).ConfigureAwait(false) as INamedTypeSymbol;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember
{
internal interface IGenerateDeconstructMemberService : ILanguageService
{
Task<ImmutableArray<CodeAction>> GenerateDeconstructMethodAsync(
Document document, SyntaxNode targetVariables, INamedTypeSymbol typeToGenerateIn, CancellationToken cancellationToken);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册