未验证 提交 cc43abcf 编写于 作者: J Jinu 提交者: GitHub

Merge pull request #28066 from CyrusNajmabadi/convertAnonymousToNamed

Initial impl of 'convert anonymous type to class' refactoring.
......@@ -5,7 +5,6 @@
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.GenerateEqualsAndGetHashCodeFromMembers;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers;
......@@ -16,12 +15,12 @@
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.GenerateEqualsAndGetHashCodeFromMembers
{
using static AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider;
using static GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider;
public class GenerateEqualsAndGetHashCodeFromMembersTests : AbstractCSharpCodeActionTest
{
protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
=> new CSharpGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider((IPickMembersService)parameters.fixProviderData);
=> new GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider((IPickMembersService)parameters.fixProviderData);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateEqualsAndGetHashCode)]
public async Task TestEqualsSingleField()
......
......@@ -415,7 +415,17 @@ protected Task TestSmartTagTextAsync(string initialMarkup, string displayText, i
var fixedRoot = await document.GetSyntaxRootAsync();
var actualText = fixedRoot.ToFullString();
Assert.Equal(expectedText, actualText);
// To help when a user just writes a test (and supplied no 'expectedText') just print
// out the entire 'actualText' (without any trimming). in the case that we have both,
// call the normal Assert helper which will print out a good trimmed diff.
if (expectedText == "")
{
Assert.Equal((object)expectedText, actualText);
}
else
{
Assert.Equal(expectedText, actualText);
}
TestAnnotations(conflictSpans, ConflictAnnotation.Kind);
TestAnnotations(renameSpans, RenameAnnotation.Kind);
......
......@@ -4,17 +4,16 @@ Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Imports Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
Imports Microsoft.CodeAnalysis.PickMembers
Imports Microsoft.CodeAnalysis.VisualBasic.GenerateEqualsAndGetHashCodeFromMembers
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.GenerateConstructorFromMembers
Public Class GenerateEqualsAndGetHashCodeFromMembersTests
Inherits AbstractVisualBasicCodeActionTest
Private Const GenerateOperatorsId = AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId
Private Const ImplementIEquatableId = AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId
Private Const GenerateOperatorsId = GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.GenerateOperatorsId
Private Const ImplementIEquatableId = GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.ImplementIEquatableId
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider
Return New VisualBasicGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider(
Return New GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider(
DirectCast(parameters.fixProviderData, IPickMembersService))
End Function
......
// 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 System.Linq;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToClass
{
[ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.IntroduceVariable)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToClass)), Shared]
internal class CSharpConvertAnonymousTypeToClassCodeRefactoringProvider :
AbstractConvertAnonymousTypeToClassCodeRefactoringProvider<
ExpressionSyntax,
NameSyntax,
IdentifierNameSyntax,
ObjectCreationExpressionSyntax,
AnonymousObjectCreationExpressionSyntax,
NamespaceDeclarationSyntax>
{
protected override ObjectCreationExpressionSyntax CreateObjectCreationExpression(
NameSyntax nameNode, AnonymousObjectCreationExpressionSyntax anonymousObject)
{
return SyntaxFactory.ObjectCreationExpression(
nameNode, CreateArgumentList(anonymousObject), initializer: default);
}
private ArgumentListSyntax CreateArgumentList(AnonymousObjectCreationExpressionSyntax anonymousObject)
=> SyntaxFactory.ArgumentList(
SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(anonymousObject.OpenBraceToken),
CreateArguments(anonymousObject.Initializers),
SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(anonymousObject.CloseBraceToken));
private SeparatedSyntaxList<ArgumentSyntax> CreateArguments(SeparatedSyntaxList<AnonymousObjectMemberDeclaratorSyntax> initializers)
=> SyntaxFactory.SeparatedList<ArgumentSyntax>(CreateArguments(initializers.GetWithSeparators()));
private SyntaxNodeOrTokenList CreateArguments(SyntaxNodeOrTokenList list)
=> new SyntaxNodeOrTokenList(list.Select(CreateArgumentOrComma));
private SyntaxNodeOrToken CreateArgumentOrComma(SyntaxNodeOrToken declOrComma)
=> declOrComma.IsToken
? declOrComma
: CreateArgument((AnonymousObjectMemberDeclaratorSyntax)declOrComma);
private ArgumentSyntax CreateArgument(AnonymousObjectMemberDeclaratorSyntax decl)
=> SyntaxFactory.Argument(decl.Expression);
}
}
// 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.Threading;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers;
using Microsoft.CodeAnalysis.PickMembers;
namespace Microsoft.CodeAnalysis.CSharp.GenerateEqualsAndGetHashCodeFromMembers
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp,
Name = PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers), Shared]
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
Before = PredefinedCodeRefactoringProviderNames.AddConstructorParametersFromMembers)]
internal class CSharpGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
: AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
{
public CSharpGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider() : this(null)
{
}
public CSharpGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider(IPickMembersService pickMembersService)
: base(pickMembersService)
{
}
protected override ImmutableArray<SyntaxNode> WrapWithUnchecked(ImmutableArray<SyntaxNode> statements)
=> ImmutableArray.Create<SyntaxNode>(SyntaxFactory.CheckedStatement(SyntaxKind.UncheckedStatement,
SyntaxFactory.Block(statements.OfType<StatementSyntax>())));
}
}
// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.GenerateEqualsAndGetHashCodeFromMembers
{
[ExportLanguageService(typeof(IGenerateEqualsAndGetHashCodeService), LanguageNames.CSharp), Shared]
internal class CSharpGenerateEqualsAndGetHashCodeService : AbstractGenerateEqualsAndGetHashCodeService
{
protected override bool TryWrapWithUnchecked(ImmutableArray<SyntaxNode> statements, out ImmutableArray<SyntaxNode> wrappedStatements)
{
wrappedStatements = ImmutableArray.Create<SyntaxNode>(
SyntaxFactory.CheckedStatement(SyntaxKind.UncheckedStatement,
SyntaxFactory.Block(statements.OfType<StatementSyntax>())));
return true;
}
}
}
......@@ -7,6 +7,7 @@
namespace Microsoft.CodeAnalysis.CodeRefactorings.IntroduceVariable
{
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToClass)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic,
Name = PredefinedCodeRefactoringProviderNames.IntroduceVariable), Shared]
internal class IntroduceVariableCodeRefactoringProvider : CodeRefactoringProvider
......
......@@ -7,6 +7,7 @@ internal static class PredefinedCodeRefactoringProviderNames
public const string AddFileBanner = "Add Banner To File Code Action Provider";
public const string AddConstructorParametersFromMembers = "Add Parameters From Members Code Action Provider";
public const string ChangeSignature = "Change Signature Code Action Provider";
public const string ConvertAnonymousTypeToClass = "Convert Anonymous Type to Class Code Action Provider";
public const string EncapsulateField = "Encapsulate Field";
public const string ExtractInterface = "Extract Interface Code Action Provider";
public const string ExtractMethod = "Extract Method Code Action Provider";
......
......@@ -879,6 +879,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Convert to class.
/// </summary>
internal static string Convert_to_class {
get {
return ResourceManager.GetString("Convert_to_class", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Convert to conditional expression.
/// </summary>
......
......@@ -1376,4 +1376,7 @@ This version used in: {2}</value>
<data name="Convert_to_conditional_expression" xml:space="preserve">
<value>Convert to conditional expression</value>
</data>
<data name="Convert_to_class" xml:space="preserve">
<value>Convert to class</value>
</data>
</root>
\ 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal abstract partial class AbstractGenerateEqualsAndGetHashCodeService : IGenerateEqualsAndGetHashCodeService
{
private const string GetHashCodeName = nameof(object.GetHashCode);
private static readonly SyntaxAnnotation s_specializedFormattingAnnotation = new SyntaxAnnotation();
protected abstract bool TryWrapWithUnchecked(
ImmutableArray<SyntaxNode> statements, out ImmutableArray<SyntaxNode> wrappedStatements);
public async Task<Document> FormatDocumentAsync(Document document, CancellationToken cancellationToken)
{
var rules = new List<IFormattingRule> { new FormatLargeBinaryExpressionRule(document.GetLanguageService<ISyntaxFactsService>()) };
rules.AddRange(Formatter.GetDefaultFormattingRules(document));
var formattedDocument = await Formatter.FormatAsync(
document, s_specializedFormattingAnnotation,
options: null, rules: rules, cancellationToken: cancellationToken).ConfigureAwait(false);
return formattedDocument;
}
public async Task<IMethodSymbol> GenerateEqualsMethodAsync(
Document document, INamedTypeSymbol namedType, ImmutableArray<ISymbol> members,
string localNameOpt, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return document.GetLanguageService<SyntaxGenerator>().CreateEqualsMethod(
compilation, namedType, members, localNameOpt,
s_specializedFormattingAnnotation, cancellationToken);
}
public async Task<IMethodSymbol> GenerateIEquatableEqualsMethodAsync(
Document document, INamedTypeSymbol namedType,
ImmutableArray<ISymbol> members, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return document.GetLanguageService<SyntaxGenerator>().CreateIEqutableEqualsMethod(
compilation, namedType, members,
s_specializedFormattingAnnotation, cancellationToken);
}
public async Task<IMethodSymbol> GenerateEqualsMethodThroughIEquatableEqualsAsync(
Document document, INamedTypeSymbol containingType, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var generator = document.GetLanguageService<SyntaxGenerator>();
var expressions = ArrayBuilder<SyntaxNode>.GetInstance();
var objName = generator.IdentifierName("obj");
if (containingType.IsValueType)
{
// return obj is T && this.Equals((T)obj);
expressions.Add(generator.IsTypeExpression(objName, containingType));
expressions.Add(
generator.InvocationExpression(
generator.MemberAccessExpression(
generator.ThisExpression(),
generator.IdentifierName(nameof(Equals))),
generator.CastExpression(containingType, objName)));
}
else
{
// return this.Equals(obj as T);
expressions.Add(
generator.InvocationExpression(
generator.MemberAccessExpression(
generator.ThisExpression(),
generator.IdentifierName(nameof(Equals))),
generator.TryCastExpression(objName, containingType)));
}
var statement = generator.ReturnStatement(
expressions.Aggregate(generator.LogicalAndExpression));
expressions.Free();
return compilation.CreateEqualsMethod(
ImmutableArray.Create(statement));
}
public async Task<IMethodSymbol> GenerateGetHashCodeMethodAsync(
Document document, INamedTypeSymbol namedType,
ImmutableArray<ISymbol> members, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var factory = document.GetLanguageService<SyntaxGenerator>();
return CreateGetHashCodeMethod(
factory, compilation, namedType, members, cancellationToken);
}
private IMethodSymbol CreateGetHashCodeMethod(
SyntaxGenerator factory, Compilation compilation,
INamedTypeSymbol namedType, ImmutableArray<ISymbol> members,
CancellationToken cancellationToken)
{
var statements = CreateGetHashCodeStatements(
factory, compilation, namedType, members, cancellationToken);
return CodeGenerationSymbolFactory.CreateMethodSymbol(
attributes: default,
accessibility: Accessibility.Public,
modifiers: new DeclarationModifiers(isOverride: true),
returnType: compilation.GetSpecialType(SpecialType.System_Int32),
refKind: RefKind.None,
explicitInterfaceImplementations: default,
name: GetHashCodeName,
typeParameters: default,
parameters: default,
statements: statements);
}
private ImmutableArray<SyntaxNode> CreateGetHashCodeStatements(
SyntaxGenerator factory, Compilation compilation,
INamedTypeSymbol namedType, ImmutableArray<ISymbol> members,
CancellationToken cancellationToken)
{
// If we have access to System.HashCode, then just use that.
var hashCodeType = compilation.GetTypeByMetadataName("System.HashCode");
var components = factory.GetGetHashCodeComponents(
compilation, namedType, members,
justMemberReference: true, cancellationToken);
if (components.Length > 0 && hashCodeType != null)
{
return CreateGetHashCodeStatementsUsingSystemHashCode(
factory, compilation, hashCodeType, components, cancellationToken);
}
// Otherwise, try to just spit out a reasonable hash code for these members.
var statements = factory.CreateGetHashCodeMethodStatements(
compilation, namedType, members, useInt64: false, cancellationToken);
// Unfortunately, our 'reasonable' hash code may overflow in checked contexts.
// C# can handle this by adding 'checked{}' around the code, VB has to jump
// through more hoops.
if (!compilation.Options.CheckOverflow)
{
return statements;
}
if (TryWrapWithUnchecked(statements, out var wrappedStatements))
{
return wrappedStatements;
}
// If tuples are available, use (a, b, c).GetHashCode to simply generate the tuple.
var valueTupleType = compilation.GetTypeByMetadataName(typeof(ValueTuple).FullName);
if (components.Length >= 2 && valueTupleType != null)
{
return ImmutableArray.Create(factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(
factory.TupleExpression(components),
GetHashCodeName))));
}
// Otherwise, use 64bit math to compute the hash. Importantly, if we always clamp
// the hash to be 32 bits or less, then the following cannot ever overflow in 64
// bits: hashCode = hashCode * -1521134295 + j.GetHashCode()
//
// So we'll generate lines like: hashCode = (hashCode * -1521134295 + j.GetHashCode()) & 0x7FFFFFFF
//
// This does mean all hashcodes will be positive. But it will avoid the overflow problem.
return factory.CreateGetHashCodeMethodStatements(
compilation, namedType, members, useInt64: true, cancellationToken);
}
private ImmutableArray<SyntaxNode> CreateGetHashCodeStatementsUsingSystemHashCode(
SyntaxGenerator factory, Compilation compilation, INamedTypeSymbol hashCodeType,
ImmutableArray<SyntaxNode> memberReferences, CancellationToken cancellationToken)
{
if (memberReferences.Length <= 8)
{
var statement = factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(factory.TypeExpression(hashCodeType), "Combine"),
memberReferences));
return ImmutableArray.Create(statement);
}
const string hashName = "hash";
var statements = ArrayBuilder<SyntaxNode>.GetInstance();
statements.Add(factory.LocalDeclarationStatement(hashName,
factory.ObjectCreationExpression(hashCodeType)));
var localReference = factory.IdentifierName(hashName);
foreach (var member in memberReferences)
{
statements.Add(factory.ExpressionStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(localReference, "Add"),
member)));
}
statements.Add(factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(localReference, "ToHashCode"))));
return statements.ToImmutableAndFree();
}
}
}
......@@ -8,66 +8,63 @@
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal partial class AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
internal partial class AbstractGenerateEqualsAndGetHashCodeService
{
private partial class GenerateEqualsAndGetHashCodeAction : CodeAction
/// <summary>
/// Specialized formatter for the "return a == obj.a &amp;&amp; b == obj.b &amp;&amp; c == obj.c &amp;&amp; ...
/// code that we spit out.
/// </summary>
private class FormatLargeBinaryExpressionRule : AbstractFormattingRule
{
private ISyntaxFactsService _syntaxFacts;
public FormatLargeBinaryExpressionRule(ISyntaxFactsService syntaxFacts)
{
_syntaxFacts = syntaxFacts;
}
/// <summary>
/// Specialized formatter for the "return a == obj.a &amp;&amp; b == obj.b &amp;&amp; c == obj.c &amp;&amp; ...
/// code that we spit out.
/// Wrap the large &amp;&amp; expression after every &amp;&amp; token.
/// </summary>
private class FormatLargeBinaryExpressionRule : AbstractFormattingRule
public override AdjustNewLinesOperation GetAdjustNewLinesOperation(
SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation<AdjustNewLinesOperation> nextOperation)
{
private ISyntaxFactsService _syntaxFacts;
public FormatLargeBinaryExpressionRule(ISyntaxFactsService syntaxFacts)
if (_syntaxFacts.IsLogicalAndExpression(previousToken.Parent))
{
_syntaxFacts = syntaxFacts;
return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines);
}
/// <summary>
/// Wrap the large &amp;&amp; expression after every &amp;&amp; token.
/// </summary>
public override AdjustNewLinesOperation GetAdjustNewLinesOperation(
SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation<AdjustNewLinesOperation> nextOperation)
{
if (_syntaxFacts.IsLogicalAndExpression(previousToken.Parent))
{
return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines);
}
return nextOperation.Invoke();
}
return nextOperation.Invoke();
}
/// <summary>
/// Align all the wrapped parts of the expression with the token after 'return'.
/// That way we get:
///
/// return a == obj.a &amp;&amp;
/// b == obj.b &amp;&amp;
/// ...
/// </summary>
public override void AddIndentBlockOperations(
List<IndentBlockOperation> list, SyntaxNode node, OptionSet optionSet, NextAction<IndentBlockOperation> nextOperation)
/// <summary>
/// Align all the wrapped parts of the expression with the token after 'return'.
/// That way we get:
///
/// return a == obj.a &amp;&amp;
/// b == obj.b &amp;&amp;
/// ...
/// </summary>
public override void AddIndentBlockOperations(
List<IndentBlockOperation> list, SyntaxNode node, OptionSet optionSet, NextAction<IndentBlockOperation> nextOperation)
{
if (_syntaxFacts.IsReturnStatement(node))
{
if (_syntaxFacts.IsReturnStatement(node))
var expr = _syntaxFacts.GetExpressionOfReturnStatement(node);
if (expr?.ChildNodesAndTokens().Count > 1)
{
var expr = _syntaxFacts.GetExpressionOfReturnStatement(node);
if (expr?.ChildNodesAndTokens().Count > 1)
{
list.Add(FormattingOperations.CreateRelativeIndentBlockOperation(
expr.GetFirstToken(),
expr.GetFirstToken().GetNextToken(),
node.GetLastToken(),
indentationDelta: 0,
option: IndentBlockOption.RelativePosition));
list.Add(FormattingOperations.CreateRelativeIndentBlockOperation(
expr.GetFirstToken(),
expr.GetFirstToken().GetNextToken(),
node.GetLastToken(),
indentationDelta: 0,
option: IndentBlockOption.RelativePosition));
return;
}
return;
}
nextOperation.Invoke(list);
}
nextOperation.Invoke(list);
}
}
}
......
......@@ -9,33 +9,26 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal partial class AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
{
private partial class GenerateEqualsAndGetHashCodeAction : CodeAction
{
private static readonly SyntaxAnnotation s_specializedFormattingAnnotation = new SyntaxAnnotation();
private readonly bool _generateEquals;
private readonly bool _generateGetHashCode;
private readonly bool _implementIEquatable;
private readonly bool _generateOperators;
private readonly AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly INamedTypeSymbol _containingType;
private readonly ImmutableArray<ISymbol> _selectedMembers;
private readonly TextSpan _textSpan;
public GenerateEqualsAndGetHashCodeAction(
AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
......@@ -45,7 +38,6 @@ private partial class GenerateEqualsAndGetHashCodeAction : CodeAction
bool implementIEquatable,
bool generateOperators)
{
_service = service;
_document = document;
_containingType = containingType;
_selectedMembers = selectedMembers;
......@@ -67,7 +59,7 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
if (_implementIEquatable)
{
methods.Add(await CreateIEquatableEqualsMethodAsync((CancellationToken)cancellationToken).ConfigureAwait((bool)false));
methods.Add(await CreateIEquatableEqualsMethodAsync(cancellationToken).ConfigureAwait((bool)false));
}
if (_generateGetHashCode)
......@@ -97,7 +89,8 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
var newDocument = await UpdateDocumentAndAddImportsAsync(
oldType, newType, cancellationToken).ConfigureAwait(false);
var formattedDocument = await FormatDocumentAsync(
var service = _document.GetLanguageService<IGenerateEqualsAndGetHashCodeService>();
var formattedDocument = await service.FormatDocumentAsync(
newDocument, cancellationToken).ConfigureAwait(false);
return formattedDocument;
......@@ -120,17 +113,6 @@ private async Task<Document> UpdateDocumentAndAddImportsAsync(SyntaxNode oldType
return newDocument;
}
private async Task<Document> FormatDocumentAsync(Document newDocument, CancellationToken cancellationToken)
{
var rules = new List<IFormattingRule> { new FormatLargeBinaryExpressionRule(_document.GetLanguageService<ISyntaxFactsService>()) };
rules.AddRange(Formatter.GetDefaultFormattingRules(_document));
var formattedDocument = await Formatter.FormatAsync(
newDocument, s_specializedFormattingAnnotation,
options: null, rules: rules, cancellationToken: cancellationToken).ConfigureAwait(false);
return formattedDocument;
}
private async Task<(SyntaxNode oldType, SyntaxNode newType)> AddMethodsAsync(
IList<IMethodSymbol> methods,
CancellationToken cancellationToken)
......@@ -210,188 +192,25 @@ private IMethodSymbol CreateInequalityOperator(Compilation compilation, SyntaxGe
ImmutableArray.Create(generator.ReturnStatement(expression)));
}
private async Task<IMethodSymbol> CreateGetHashCodeMethodAsync(CancellationToken cancellationToken)
{
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var factory = _document.GetLanguageService<SyntaxGenerator>();
return CreateGetHashCodeMethod(factory, compilation, cancellationToken);
}
private IMethodSymbol CreateGetHashCodeMethod(
SyntaxGenerator factory,
Compilation compilation,
CancellationToken cancellationToken)
private Task<IMethodSymbol> CreateGetHashCodeMethodAsync(CancellationToken cancellationToken)
{
var statements = CreateGetHashCodeStatements(factory, compilation, cancellationToken);
return CodeGenerationSymbolFactory.CreateMethodSymbol(
attributes: default,
accessibility: Accessibility.Public,
modifiers: new DeclarationModifiers(isOverride: true),
returnType: compilation.GetSpecialType(SpecialType.System_Int32),
refKind: RefKind.None,
explicitInterfaceImplementations: default,
name: GetHashCodeName,
typeParameters: default,
parameters: default,
statements: statements);
var service = _document.GetLanguageService<IGenerateEqualsAndGetHashCodeService>();
return service.GenerateGetHashCodeMethodAsync(_document, _containingType, _selectedMembers, cancellationToken);
}
private ImmutableArray<SyntaxNode> CreateGetHashCodeStatements(
SyntaxGenerator factory, Compilation compilation, CancellationToken cancellationToken)
private Task<IMethodSymbol> CreateEqualsMethodAsync(CancellationToken cancellationToken)
{
// If we have access to System.HashCode, then just use that.
var hashCodeType = compilation.GetTypeByMetadataName("System.HashCode");
var components = factory.GetGetHashCodeComponents(
compilation, _containingType, _selectedMembers,
justMemberReference: true, cancellationToken);
if (components.Length > 0 && hashCodeType != null)
{
return CreateGetHashCodeStatementsUsingSystemHashCode(
factory, compilation, hashCodeType, components, cancellationToken);
}
// Otherwise, try to just spit out a reasonable hash code for these members.
var statements = factory.CreateGetHashCodeMethodStatements(
compilation, _containingType, _selectedMembers, useInt64: false, cancellationToken);
// Unfortunately, our 'reasonable' hash code may overflow in checked contexts.
// C# can handle this by adding 'checked{}' around the code, VB has to jump
// through more hoops.
if (!compilation.Options.CheckOverflow)
{
return statements;
}
if (compilation.Language == LanguageNames.CSharp)
{
return _service.WrapWithUnchecked(statements);
}
// If tuples are available, use (a, b, c).GetHashCode to simply generate the tuple.
var valueTupleType = compilation.GetTypeByMetadataName(typeof(ValueTuple).FullName);
if (components.Length >= 2 && valueTupleType != null)
{
return ImmutableArray.Create(factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(
factory.TupleExpression(components),
GetHashCodeName))));
}
// Otherwise, use 64bit math to compute the hash. Importantly, if we always clamp
// the hash to be 32 bits or less, then the following cannot ever overflow in 64
// bits: hashCode = hashCode * -1521134295 + j.GetHashCode()
//
// So we'll generate lines like: hashCode = (hashCode * -1521134295 + j.GetHashCode()) & 0x7FFFFFFF
//
// This does mean all hashcodes will be positive. But it will avoid the overflow problem.
return factory.CreateGetHashCodeMethodStatements(
compilation, _containingType, _selectedMembers, useInt64: true, cancellationToken);
}
private ImmutableArray<SyntaxNode> CreateGetHashCodeStatementsUsingSystemHashCode(
SyntaxGenerator factory, Compilation compilation, INamedTypeSymbol hashCodeType,
ImmutableArray<SyntaxNode> memberReferences, CancellationToken cancellationToken)
{
if (memberReferences.Length <= 8)
{
var statement = factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(factory.TypeExpression(hashCodeType), "Combine"),
memberReferences));
return ImmutableArray.Create(statement);
}
const string hashName = "hash";
var statements = ArrayBuilder<SyntaxNode>.GetInstance();
statements.Add(factory.LocalDeclarationStatement(hashName,
factory.ObjectCreationExpression(hashCodeType)));
var localReference = factory.IdentifierName(hashName);
foreach (var member in memberReferences)
{
statements.Add(factory.ExpressionStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(localReference, "Add"),
member)));
}
statements.Add(factory.ReturnStatement(
factory.InvocationExpression(
factory.MemberAccessExpression(localReference, "ToHashCode"))));
return statements.ToImmutableAndFree();
}
private async Task<IMethodSymbol> CreateEqualsMethodAsync(CancellationToken cancellationToken)
{
if (_implementIEquatable)
{
return await ImplementEqualsThroughIEqutableEqualsAsync(cancellationToken).ConfigureAwait(false);
}
else
{
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return _document.GetLanguageService<SyntaxGenerator>().CreateEqualsMethod(
compilation, _containingType, _selectedMembers,
s_specializedFormattingAnnotation, cancellationToken);
}
var service = _document.GetLanguageService<IGenerateEqualsAndGetHashCodeService>();
return _implementIEquatable
? service.GenerateEqualsMethodThroughIEquatableEqualsAsync(_document, _containingType, cancellationToken)
: service.GenerateEqualsMethodAsync(_document, _containingType, _selectedMembers, cancellationToken);
}
private async Task<IMethodSymbol> CreateIEquatableEqualsMethodAsync(CancellationToken cancellationToken)
{
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return _document.GetLanguageService<SyntaxGenerator>().CreateIEqutableEqualsMethod(
compilation, _containingType, _selectedMembers,
s_specializedFormattingAnnotation, cancellationToken);
}
private async Task<IMethodSymbol> ImplementEqualsThroughIEqutableEqualsAsync(CancellationToken cancellationToken)
{
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var generator = _document.GetLanguageService<SyntaxGenerator>();
var expressions = ArrayBuilder<SyntaxNode>.GetInstance();
var objName = generator.IdentifierName("obj");
if (_containingType.IsValueType)
{
// return obj is T && this.Equals((T)obj);
expressions.Add(generator.IsTypeExpression(objName, _containingType));
expressions.Add(
generator.InvocationExpression(
generator.MemberAccessExpression(
generator.ThisExpression(),
generator.IdentifierName(nameof(Equals))),
generator.CastExpression(_containingType, objName)));
}
else
{
// return this.Equals(obj as T);
expressions.Add(
generator.InvocationExpression(
generator.MemberAccessExpression(
generator.ThisExpression(),
generator.IdentifierName(nameof(Equals))),
generator.TryCastExpression(objName, _containingType)));
}
var statement = generator.ReturnStatement(
expressions.Aggregate(generator.LogicalAndExpression));
expressions.Free();
return compilation.CreateEqualsMethod(
ImmutableArray.Create(statement));
}
private async Task<IMethodSymbol> CreateIEqutableEqualsMethodAsync(CancellationToken cancellationToken)
{
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return _document.GetLanguageService<SyntaxGenerator>().CreateIEqutableEqualsMethod(
compilation, _containingType, _selectedMembers,
s_specializedFormattingAnnotation, cancellationToken);
var service = _document.GetLanguageService<IGenerateEqualsAndGetHashCodeService>();
return await service.GenerateIEquatableEqualsMethodAsync(
_document, _containingType, _selectedMembers, cancellationToken).ConfigureAwait(false);
}
public override string Title
......
......@@ -19,7 +19,11 @@
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal abstract partial class AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider : AbstractGenerateFromMembersCodeRefactoringProvider
[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic,
Name = PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers), Shared]
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
Before = PredefinedCodeRefactoringProviderNames.AddConstructorParametersFromMembers)]
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider : AbstractGenerateFromMembersCodeRefactoringProvider
{
public const string GenerateOperatorsId = nameof(GenerateOperatorsId);
public const string ImplementIEquatableId = nameof(ImplementIEquatableId);
......@@ -29,12 +33,15 @@ internal abstract partial class AbstractGenerateEqualsAndGetHashCodeFromMembersC
private readonly IPickMembersService _pickMembersService_forTestingPurposes;
protected AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider(IPickMembersService pickMembersService)
public GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider()
: this(pickMembersService: null)
{
_pickMembersService_forTestingPurposes = pickMembersService;
}
protected abstract ImmutableArray<SyntaxNode> WrapWithUnchecked(ImmutableArray<SyntaxNode> statements);
public GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider(IPickMembersService pickMembersService)
{
_pickMembersService_forTestingPurposes = pickMembersService;
}
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
......@@ -245,7 +252,7 @@ private void GetExistingMemberInfo(INamedTypeSymbol containingType, out bool has
else
{
return new GenerateEqualsAndGetHashCodeAction(
this, document, textSpan, containingType, members,
document, textSpan, containingType, members,
generateEquals, generateGetHashCode,
implementIEquatable: false, generateOperators: false);
}
......
......@@ -11,13 +11,13 @@
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal partial class AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
{
private class GenerateEqualsAndGetHashCodeWithDialogCodeAction : CodeActionWithOptions
{
private readonly bool _generateEquals;
private readonly bool _generateGetHashCode;
private readonly AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service;
private readonly GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly INamedTypeSymbol _containingType;
private readonly ImmutableArray<ISymbol> _viableMembers;
......@@ -25,7 +25,7 @@ private class GenerateEqualsAndGetHashCodeWithDialogCodeAction : CodeActionWithO
private readonly TextSpan _textSpan;
public GenerateEqualsAndGetHashCodeWithDialogCodeAction(
AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider service,
GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
......@@ -85,7 +85,7 @@ protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperation
var generatorOperators = (generateOperatorsOption?.Value).GetValueOrDefault();
var action = new GenerateEqualsAndGetHashCodeAction(
_service, _document, _textSpan, _containingType, result.Members,
_document, _textSpan, _containingType, result.Members,
_generateEquals, _generateGetHashCode, implementIEquatable, generatorOperators);
return await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
......
// 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;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
/// <summary>
/// Service that can be used to generate <see cref="object.Equals(object)"/> and
/// <see cref="object.GetHashCode"/> overloads for use from other IDE features.
/// </summary>
internal interface IGenerateEqualsAndGetHashCodeService : ILanguageService
{
/// <summary>
/// Formats only the members in the provided document that were generated by this interface.
/// </summary>
Task<Document> FormatDocumentAsync(Document document, CancellationToken cancellationToken);
/// <summary>
/// Generates an override of <see cref="object.Equals(object)"/> that works by comparing the
/// provided <paramref name="members"/>.
/// </summary>
Task<IMethodSymbol> GenerateEqualsMethodAsync(Document document, INamedTypeSymbol namedType, ImmutableArray<ISymbol> members, string localNameOpt, CancellationToken cancellationToken);
/// <summary>
/// Generates an override of <see cref="object.Equals(object)"/> that works by delegating to
/// <see cref="IEquatable{T}.Equals(T)"/>.
/// </summary>
Task<IMethodSymbol> GenerateEqualsMethodThroughIEquatableEqualsAsync(Document document, INamedTypeSymbol namedType, CancellationToken cancellationToken);
/// <summary>
/// Generates an implementation of <see cref="IEquatable{T}.Equals"/> that works by
/// comparing the provided <paramref name="members"/>.
/// </summary>
Task<IMethodSymbol> GenerateIEquatableEqualsMethodAsync(Document document, INamedTypeSymbol namedType, ImmutableArray<ISymbol> members, CancellationToken cancellationToken);
/// <summary>
/// Generates an override of <see cref="object.GetHashCode"/> that computes a reasonable
/// hash based on the provided <paramref name="members"/>. The generated function will
/// defer to HashCode.Combine if it exists. Otherwise, it will determine if it should
/// generate code directly in-line to compute the hash, or defer to something like
/// <see cref="ValueTuple.GetHashCode"/> to provide a reasonable alternative.
/// </summary>
Task<IMethodSymbol> GenerateGetHashCodeMethodAsync(Document document, INamedTypeSymbol namedType, ImmutableArray<ISymbol> members, 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;
namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
{
internal static class IGenerateEqualsAndGetHashCodeServiceExtensions
{
public static Task<IMethodSymbol> GenerateEqualsMethodAsync(
this IGenerateEqualsAndGetHashCodeService service, Document document, INamedTypeSymbol namedType,
ImmutableArray<ISymbol> members, CancellationToken cancellationToken)
=> service.GenerateEqualsMethodAsync(document, namedType, members, localNameOpt: null, cancellationToken);
}
}
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
......@@ -27,6 +27,11 @@
<target state="new">Add to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Convert_to_class">
<source>Convert to class</source>
<target state="new">Convert to class</target>
<note />
</trans-unit>
<trans-unit id="Fix_typo_0">
<source>Fix typo '{0}'</source>
<target state="new">Fix typo '{0}'</target>
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Composition
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToClass
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertAnonymousTypeToClass
<ExtensionOrder(Before:=PredefinedCodeRefactoringProviderNames.IntroduceVariable)>
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToClass), [Shared]>
Friend Class VisualBasicConvertAnonymousTypeToClassCodeRefactoringProvider
Inherits AbstractConvertAnonymousTypeToClassCodeRefactoringProvider(Of
ExpressionSyntax,
NameSyntax,
IdentifierNameSyntax,
ObjectCreationExpressionSyntax,
AnonymousObjectCreationExpressionSyntax,
NamespaceBlockSyntax)
Protected Overrides Function CreateObjectCreationExpression(
nameNode As NameSyntax, anonymousObject As AnonymousObjectCreationExpressionSyntax) As ObjectCreationExpressionSyntax
Return SyntaxFactory.ObjectCreationExpression(
attributeLists:=Nothing, nameNode, CreateArgumentList(anonymousObject.Initializer), initializer:=Nothing)
End Function
Private Function CreateArgumentList(initializer As ObjectMemberInitializerSyntax) As ArgumentListSyntax
Return SyntaxFactory.ArgumentList(
SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(initializer.OpenBraceToken),
CreateArguments(initializer.Initializers),
SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(initializer.CloseBraceToken))
End Function
Private Function CreateArguments(initializers As SeparatedSyntaxList(Of FieldInitializerSyntax)) As SeparatedSyntaxList(Of ArgumentSyntax)
Return SyntaxFactory.SeparatedList(Of ArgumentSyntax)(CreateArguments(initializers.GetWithSeparators()))
End Function
Private Function CreateArguments(list As SyntaxNodeOrTokenList) As SyntaxNodeOrTokenList
Return New SyntaxNodeOrTokenList(list.Select(AddressOf CreateArgumentOrComma))
End Function
Private Function CreateArgumentOrComma(declOrComma As SyntaxNodeOrToken) As SyntaxNodeOrToken
Return If(declOrComma.IsToken,
declOrComma,
CreateArgument(CType(declOrComma, FieldInitializerSyntax)))
End Function
Private Function CreateArgument(initializer As FieldInitializerSyntax) As ArgumentSyntax
Dim expression = If(TryCast(initializer, InferredFieldInitializerSyntax)?.Expression,
TryCast(initializer, NamedFieldInitializerSyntax)?.Expression)
Return SyntaxFactory.SimpleArgument(expression)
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
Imports Microsoft.CodeAnalysis.PickMembers
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateEqualsAndGetHashCodeFromMembers
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic,
Name:=PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers), [Shared]>
<ExtensionOrder(After:=PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
Before:=PredefinedCodeRefactoringProviderNames.AddConstructorParametersFromMembers)>
Friend Class VisualBasicGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
Inherits AbstractGenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider
Public Sub New()
Me.New(Nothing)
End Sub
Public Sub New(pickMembersService As IPickMembersService)
MyBase.New(pickMembersService)
End Sub
Protected Overrides Function WrapWithUnchecked(statements As ImmutableArray(Of SyntaxNode)) As ImmutableArray(Of SyntaxNode)
Return statements
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Composition
Imports Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers
Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.VisualBasic.GenerateEqualsAndGetHashCodeFromMembers
<ExportLanguageService(GetType(IGenerateEqualsAndGetHashCodeService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicGenericEqualsAndGetHashCodeService
Inherits AbstractGenerateEqualsAndGetHashCodeService
Protected Overrides Function TryWrapWithUnchecked(statements As ImmutableArray(Of SyntaxNode), ByRef wrappedStatements As ImmutableArray(Of SyntaxNode)) As Boolean
' VB doesn't support 'unchecked' statements.
Return False
End Function
End Class
End Namespace
......@@ -48,6 +48,7 @@ public static class Features
public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync";
public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable";
public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield";
public const string CodeActionsConvertAnonymousTypeToClass = "CodeActions.ConvertAnonymousTypeToClass";
public const string CodeActionsConvertNumericLiteral = "CodeActions.ConvertNumericLiteral";
public const string CodeActionsConvertToInterpolatedString = "CodeActions.ConvertToInterpolatedString";
public const string CodeActionsConvertToIterator = "CodeActions.ConvertToIterator";
......
......@@ -3927,15 +3927,14 @@ public override SyntaxNode TypedConstantExpression(TypedConstant value)
}
public override SyntaxNode IdentifierName(string identifier)
{
return identifier.ToIdentifierName();
}
=> identifier.ToIdentifierName();
public override SyntaxNode GenericName(string identifier, IEnumerable<SyntaxNode> typeArguments)
{
return SyntaxFactory.GenericName(identifier.ToIdentifierToken(),
=> GenericName(identifier.ToIdentifierToken(), typeArguments);
internal override SyntaxNode GenericName(SyntaxToken identifier, IEnumerable<SyntaxNode> typeArguments)
=> SyntaxFactory.GenericName(identifier,
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(typeArguments.Cast<TypeSyntax>())));
}
public override SyntaxNode WithTypeArguments(SyntaxNode expression, IEnumerable<SyntaxNode> typeArguments)
{
......@@ -4207,14 +4206,10 @@ private static IReadOnlyList<T> AsReadOnlyList<T>(IEnumerable<T> sequence)
}
internal override SyntaxNode IdentifierName(SyntaxToken identifier)
{
return SyntaxFactory.IdentifierName(identifier);
}
=> SyntaxFactory.IdentifierName(identifier);
internal override SyntaxToken Identifier(string identifier)
{
return SyntaxFactory.Identifier(identifier);
}
internal override SyntaxToken Identifier(string identifier)
=> SyntaxFactory.Identifier(identifier);
internal override SyntaxNode NamedAnonymousObjectMemberDeclarator(SyntaxNode identifier, SyntaxNode expression)
{
......
......@@ -1642,6 +1642,8 @@ public SyntaxNode NullLiteralExpression()
/// </summary>
public abstract SyntaxNode GenericName(string identifier, IEnumerable<SyntaxNode> typeArguments);
internal abstract SyntaxNode GenericName(SyntaxToken identifier, IEnumerable<SyntaxNode> typeArguments);
/// <summary>
/// Creates an expression that denotes a generic identifier name.
/// </summary>
......
......@@ -9,6 +9,29 @@ namespace Microsoft.CodeAnalysis.LanguageServices
{
internal static class ISyntaxFactsServiceExtensions
{
public static bool IsLegalIdentifier(this ISyntaxFactsService syntaxFacts, string name)
{
if (name.Length == 0)
{
return false;
}
if (!syntaxFacts.IsIdentifierStartCharacter(name[0]))
{
return false;
}
for (int i = 1; i < name.Length; i++)
{
if (!syntaxFacts.IsIdentifierPartCharacter(name[i]))
{
return false;
}
}
return true;
}
public static bool IsWord(this ISyntaxFactsService syntaxFacts, SyntaxToken token)
{
return syntaxFacts.IsIdentifier(token)
......
......@@ -199,7 +199,7 @@ private static bool TryGetValue(IDictionary<string, ISymbol> dictionary, string
public static ImmutableArray<SyntaxNode> CreateAssignmentStatements(
this SyntaxGenerator factory,
Compilation compilation,
IList<IParameterSymbol> parameters,
ImmutableArray<IParameterSymbol> parameters,
IDictionary<string, ISymbol> parameterToExistingFieldMap,
IDictionary<string, string> parameterToNewFieldMap,
bool addNullChecks,
......
......@@ -19,18 +19,19 @@ internal static partial class ICodeDefinitionFactoryExtensions
private const string EqualsName = "Equals";
private const string DefaultName = "Default";
private const string ObjName = "obj";
private const string OtherName = "other";
public const string OtherName = "other";
public static IMethodSymbol CreateEqualsMethod(
this SyntaxGenerator factory,
Compilation compilation,
INamedTypeSymbol containingType,
ImmutableArray<ISymbol> symbols,
ImmutableArray<ISymbol> symbols,
string localNameOpt,
SyntaxAnnotation statementAnnotation,
CancellationToken cancellationToken)
{
var statements = CreateEqualsMethodStatements(
factory, compilation, containingType, symbols, cancellationToken);
factory, compilation, containingType, symbols, localNameOpt, cancellationToken);
statements = statements.SelectAsArray(s => s.WithAdditionalAnnotations(statementAnnotation));
return CreateEqualsMethod(compilation, statements);
......@@ -91,6 +92,7 @@ public static IMethodSymbol CreateEqualsMethod(this Compilation compilation, Imm
Compilation compilation,
INamedTypeSymbol containingType,
ImmutableArray<ISymbol> members,
string localNameOpt,
CancellationToken cancellationToken)
{
var statements = ArrayBuilder<SyntaxNode>.GetInstance();
......@@ -100,7 +102,7 @@ public static IMethodSymbol CreateEqualsMethod(this Compilation compilation, Imm
//
// var order = obj as CustomerOrder;
var localName = GetLocalName(containingType);
var localName = localNameOpt ?? GetLocalName(containingType);
var localNameExpression = factory.IdentifierName(localName);
......
......@@ -151,8 +151,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
End Function
Public Overloads Overrides Function GenericName(identifier As String, typeArguments As IEnumerable(Of SyntaxNode)) As SyntaxNode
Return GenericName(identifier.ToIdentifierToken(), typeArguments)
End Function
Friend Overrides Function GenericName(identifier As SyntaxToken, typeArguments As IEnumerable(Of SyntaxNode)) As SyntaxNode
Return SyntaxFactory.GenericName(
identifier.ToIdentifierToken,
identifier,
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SeparatedList(typeArguments.Cast(Of TypeSyntax)()))).WithAdditionalAnnotations(Simplifier.Annotation)
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册