提交 0e142f49 编写于 作者: C CyrusNajmabadi

Add back support for generating Equals/GetHashCode.

上级 66507a25
......@@ -129,6 +129,7 @@
<Compile Include="AddPackage\InstallPackageDirectlyCodeAction.cs" />
<Compile Include="AddConstructorParametersFromMembers\State.cs" />
<Compile Include="GenerateConstructorFromMembers\GenerateConstructorWithDialogCodeAction.cs" />
<Compile Include="GenerateEqualsAndGetHashCodeFromMembers\GenerateEqualsAndHashWithDialogCodeAction.cs" />
<Compile Include="PickMembers\IPickMembersService.cs" />
<Compile Include="PickMembers\PickMembersResult.cs" />
<Compile Include="GenerateFromMembers\SelectedMemberInfo.cs" />
......@@ -519,7 +520,7 @@
<Compile Include="GenerateConstructorFromMembers\ConstructorDelegatingCodeAction.cs" />
<Compile Include="GenerateConstructorFromMembers\FieldDelegatingCodeAction.cs" />
<Compile Include="GenerateConstructorFromMembers\State.cs" />
<Compile Include="GenerateEqualsAndGetHashCodeFromMembers\GenerateEqualsAndHashCodeAction.cs" />
<Compile Include="GenerateEqualsAndGetHashCodeFromMembers\GenerateEqualsAndGetHashCodeAction.cs" />
<Compile Include="GenerateMember\AbstractGenerateMemberService.cs" />
<Compile Include="GenerateMember\GenerateConstructor\AbstractGenerateConstructorService.CodeAction.cs" />
<Compile Include="GenerateMember\GenerateConstructor\AbstractGenerateConstructorService.cs" />
......
......@@ -2157,6 +2157,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Pick members to be used in Equals/GetHashCode.
/// </summary>
internal static string Pick_members_to_be_used_in_Equals_GetHashCode {
get {
return ResourceManager.GetString("Pick_members_to_be_used_in_Equals_GetHashCode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to + additional {0} {1}.
/// </summary>
......
......@@ -1184,4 +1184,7 @@ This version used in: {2}</value>
<data name="Pick_members_to_be_used_as_constructor_parameters" xml:space="preserve">
<value>Pick members to be used as constructor parameters</value>
</data>
<data name="Pick_members_to_be_used_in_Equals_GetHashCode" xml:space="preserve">
<value>Pick members to be used in Equals/GetHashCode</value>
</data>
</root>
\ No newline at end of file
......@@ -102,31 +102,6 @@ private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
this, document, textSpan, containingType, viableMembers));
}
/// <summary>
/// Gets the enclosing named type for the specified position. We can't use
/// <see cref="SemanticModel.GetEnclosingSymbol"/> because that doesn't return
/// the type you're current on if you're on the header of a class/interface.
/// </summary>
private INamedTypeSymbol GetEnclosingNamedType(
SemanticModel semanticModel, SyntaxNode root, int start, CancellationToken cancellationToken)
{
var token = root.FindToken(start);
if (token == ((ICompilationUnitSyntax)root).EndOfFileToken)
{
token = token.GetPreviousToken();
}
for (var node = token.Parent; node != null; node = node.Parent)
{
if (semanticModel.GetDeclaredSymbol(node) is INamedTypeSymbol declaration)
{
return declaration;
}
}
return null;
}
public async Task<ImmutableArray<CodeAction>> GenerateConstructorFromMembersAsync(
Document document, TextSpan textSpan, 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.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
......@@ -13,22 +14,22 @@ namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
{
private class GenerateEqualsAndHashCodeAction : CodeAction
private class GenerateEqualsAndGetHashCodeAction : CodeAction
{
private readonly bool _generateEquals;
private readonly bool _generateGetHashCode;
private readonly GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly INamedTypeSymbol _containingType;
private readonly IList<ISymbol> _selectedMembers;
private readonly ImmutableArray<ISymbol> _selectedMembers;
private readonly TextSpan _textSpan;
public GenerateEqualsAndHashCodeAction(
public GenerateEqualsAndGetHashCodeAction(
GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
bool generateEquals = false,
bool generateGetHashCode = false)
{
......@@ -83,16 +84,14 @@ private async Task<IMethodSymbol> CreateEqualsMethodAsync(CancellationToken canc
}
public override string Title
{
get
{
return _generateEquals
? _generateGetHashCode
? FeaturesResources.Generate_Both
: FeaturesResources.Generate_Equals_object
: FeaturesResources.Generate_GetHashCode;
}
}
=> GetTitle(_generateEquals, _generateGetHashCode);
internal static string GetTitle(bool generateEquals, bool generateGetHashCode)
=> generateEquals
? generateGetHashCode
? FeaturesResources.Generate_Both
: FeaturesResources.Generate_Equals_object
: FeaturesResources.Generate_GetHashCode;
}
}
}
\ No newline at end of file
// 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.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -10,15 +10,20 @@
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.GenerateFromMembers;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
// [ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers), Shared]
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
Before = PredefinedCodeRefactoringProviderNames.AddConstructorParametersFromMembers)]
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider : AbstractGenerateFromMembersCodeRefactoringProvider
{
private const string EqualsName = nameof(object.Equals);
private const string GetHashCodeName = nameof(object.GetHashCode);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var document = context.Document;
......@@ -32,11 +37,76 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var actions = await this.GenerateEqualsAndGetHashCodeFromMembersAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
context.RegisterRefactorings(actions);
if (actions.IsDefaultOrEmpty && textSpan.IsEmpty)
{
await HandleNonSelectionAsync(context).ConfigureAwait(false);
}
}
private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
{
var document = context.Document;
var textSpan = context.Span;
var cancellationToken = context.CancellationToken;
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
// We offer the refactoring when the user is either on the header of a class/struct,
// or if they're between any members of a class/struct and are on a blank line.
if (!syntaxFacts.IsOnTypeHeader(root, textSpan.Start) &&
!syntaxFacts.IsBetweenTypeMembers(sourceText, root, textSpan.Start))
{
return;
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// Only supported on classes/structs.
var containingType = GetEnclosingNamedType(semanticModel, root, textSpan.Start, cancellationToken);
if (containingType?.TypeKind != TypeKind.Class && containingType?.TypeKind != TypeKind.Struct)
{
return;
}
// No constructors for static classes.
if (containingType.IsStatic)
{
return;
}
// Find all the possible writable instance fields/properties. If there are any, then
// show a dialog to the user to select the ones they want. Otherwise, if there are none
// just offer to generate the no-param constructor if they don't already have one.
var viableMembers = containingType.GetMembers().WhereAsArray(IsInstanceFieldOrProperty);
if (viableMembers.Length == 0)
{
return;
}
GetExistingMemberInfo(
containingType, out var hasEquals, out var hasGetHashCode);
var actions = CreateActions(
document, textSpan, containingType, viableMembers,
hasEquals, hasGetHashCode,
withDialog: true);
context.RegisterRefactorings(actions);
}
private const string EqualsName = "Equals";
private const string GetHashCodeName = "GetHashCode";
private const string ObjName = nameof(ObjName);
private void GetExistingMemberInfo(INamedTypeSymbol containingType, out bool hasEquals, out bool hasGetHashCode)
{
hasEquals = containingType.GetMembers(EqualsName)
.OfType<IMethodSymbol>()
.Any(m => m.Parameters.Length == 1 && !m.IsStatic);
hasGetHashCode = containingType.GetMembers(GetHashCodeName)
.OfType<IMethodSymbol>()
.Any(m => m.Parameters.Length == 0 && !m.IsStatic);
}
public async Task<ImmutableArray<CodeAction>> GenerateEqualsAndGetHashCodeFromMembersAsync(
Document document,
......@@ -51,21 +121,12 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
{
if (info.ContainingType != null && info.ContainingType.TypeKind != TypeKind.Interface)
{
var hasEquals =
info.ContainingType.GetMembers(EqualsName)
.OfType<IMethodSymbol>()
.Any(m => m.Parameters.Length == 1 && !m.IsStatic);
var hasGetHashCode =
info.ContainingType.GetMembers(GetHashCodeName)
.OfType<IMethodSymbol>()
.Any(m => m.Parameters.Length == 0 && !m.IsStatic);
if (!hasEquals || !hasGetHashCode)
{
return CreateActions(
document, textSpan, info.ContainingType, info.SelectedMembers, hasEquals, hasGetHashCode).AsImmutableOrNull();
}
GetExistingMemberInfo(
info.ContainingType, out var hasEquals, out var hasGetHashCode);
return CreateActions(
document, textSpan, info.ContainingType, info.SelectedMembers,
hasEquals, hasGetHashCode, withDialog: false);
}
}
......@@ -73,27 +134,53 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
}
}
private IEnumerable<CodeAction> CreateActions(
private ImmutableArray<CodeAction> CreateActions(
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
bool hasEquals,
bool hasGetHashCode)
bool hasGetHashCode,
bool withDialog)
{
var result = ArrayBuilder<CodeAction>.GetInstance();
if (!hasEquals)
{
yield return new GenerateEqualsAndHashCodeAction(this, document, textSpan, containingType, selectedMembers, generateEquals: true);
result.Add(CreateCodeAction(document, textSpan, containingType, selectedMembers,
generateEquals: true, generateGetHashCode: false, withDialog: withDialog));
}
if (!hasGetHashCode)
{
yield return new GenerateEqualsAndHashCodeAction(this, document, textSpan, containingType, selectedMembers, generateGetHashCode: true);
result.Add(CreateCodeAction(document, textSpan, containingType, selectedMembers,
generateEquals: false, generateGetHashCode: true, withDialog: withDialog));
}
if (!hasEquals && !hasGetHashCode)
{
yield return new GenerateEqualsAndHashCodeAction(this, document, textSpan, containingType, selectedMembers, generateEquals: true, generateGetHashCode: true);
result.Add(CreateCodeAction(document, textSpan, containingType, selectedMembers,
generateEquals: true, generateGetHashCode: true, withDialog: withDialog));
}
return result.ToImmutableAndFree();
}
private CodeAction CreateCodeAction(
Document document, TextSpan textSpan,
INamedTypeSymbol containingType, ImmutableArray<ISymbol> members,
bool generateEquals, bool generateGetHashCode, bool withDialog)
{
if (withDialog)
{
return new GenerateEqualsAndGetHashCodeWithDialogCodeAction(
this, document, textSpan, containingType, members,
generateEquals, generateGetHashCode);
}
else
{
return new GenerateEqualsAndGetHashCodeAction(
this, document, textSpan, containingType, members,
generateEquals, generateGetHashCode);
}
}
}
......
// 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.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
{
private class GenerateEqualsAndGetHashCodeWithDialogCodeAction : CodeActionWithOptions
{
private readonly bool _generateEquals;
private readonly bool _generateGetHashCode;
private readonly GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly INamedTypeSymbol _containingType;
private readonly ImmutableArray<ISymbol> _viableMembers;
private readonly TextSpan _textSpan;
public GenerateEqualsAndGetHashCodeWithDialogCodeAction(
GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
ImmutableArray<ISymbol> viableMembers,
bool generateEquals = false,
bool generateGetHashCode = false)
{
_service = service;
_document = document;
_containingType = containingType;
_viableMembers = viableMembers;
_textSpan = textSpan;
_generateEquals = generateEquals;
_generateGetHashCode = generateGetHashCode;
}
public override object GetOptions(CancellationToken cancellationToken)
{
var service = _document.Project.Solution.Workspace.Services.GetService<IPickMembersService>();
return service.PickMembers(FeaturesResources.Pick_members_to_be_used_in_Equals_GetHashCode, _viableMembers);
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(object options, CancellationToken cancellationToken)
{
var result = (PickMembersResult)options;
if (result.IsCanceled)
{
return ImmutableArray<CodeActionOperation>.Empty;
}
var action = new GenerateEqualsAndGetHashCodeAction(
_service, _document, _textSpan, _containingType, result.Members, _generateEquals, _generateGetHashCode);
return await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
public override string Title
=> GenerateEqualsAndGetHashCodeAction.GetTitle(_generateEquals, _generateGetHashCode);
}
}
}
\ No newline at end of file
......@@ -21,6 +21,31 @@ protected AbstractGenerateFromMembersCodeRefactoringProvider()
{
}
/// <summary>
/// Gets the enclosing named type for the specified position. We can't use
/// <see cref="SemanticModel.GetEnclosingSymbol"/> because that doesn't return
/// the type you're current on if you're on the header of a class/interface.
/// </summary>
protected static INamedTypeSymbol GetEnclosingNamedType(
SemanticModel semanticModel, SyntaxNode root, int start, CancellationToken cancellationToken)
{
var token = root.FindToken(start);
if (token == ((ICompilationUnitSyntax)root).EndOfFileToken)
{
token = token.GetPreviousToken();
}
for (var node = token.Parent; node != null; node = node.Parent)
{
if (semanticModel.GetDeclaredSymbol(node) is INamedTypeSymbol declaration)
{
return declaration;
}
}
return null;
}
protected async Task<SelectedMemberInfo> GetSelectedMemberInfoAsync(
Document document, TextSpan textSpan, CancellationToken cancellationToken)
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册