From 74c477c83891f2e3b0aa718e05de2ec3016923ba Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Thu, 23 Feb 2017 00:23:24 -0800 Subject: [PATCH] Add specialized formatting for teh equals expressions we create. --- src/Features/Core/Portable/Features.csproj | 1 + .../FormatLargeBinaryExpressionRule.cs | 54 +++++++++++++++++++ .../GenerateEqualsAndGetHashCodeAction.cs | 24 +++++++-- .../CSharpSyntaxFactsService.cs | 10 ++-- .../SyntaxFactsService/ISyntaxFactsService.cs | 2 + ...ionFactoryExtensions_CreateEqualsMethod.cs | 19 ++++--- .../VisualBasicSyntaxFactsService.vb | 10 +++- 7 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/FormatLargeBinaryExpressionRule.cs diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index e9ef0545a9f..01c6d4a803c 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -129,6 +129,7 @@ + diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/FormatLargeBinaryExpressionRule.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/FormatLargeBinaryExpressionRule.cs new file mode 100644 index 00000000000..df818653a86 --- /dev/null +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/FormatLargeBinaryExpressionRule.cs @@ -0,0 +1,54 @@ +// 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 Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers +{ + internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider + { + private partial class GenerateEqualsAndGetHashCodeAction : CodeAction + { + private class FormatLargeBinaryExpressionRule : AbstractFormattingRule + { + private ISyntaxFactsService _syntaxFacts; + + public FormatLargeBinaryExpressionRule(ISyntaxFactsService syntaxFacts) + { + _syntaxFacts = syntaxFacts; + } + + public override AdjustNewLinesOperation GetAdjustNewLinesOperation( + SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation nextOperation) + { + if (_syntaxFacts.IsLogicalAndExpression(previousToken.Parent)) + { + return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); + } + + return nextOperation.Invoke(); + } + + public override void AddIndentBlockOperations( + List list, SyntaxNode node, OptionSet optionSet, NextAction nextOperation) + { + if (_syntaxFacts.IsReturnStatement(node)) + { + list.Add(FormattingOperations.CreateRelativeIndentBlockOperation( + node.GetFirstToken(), + node.GetFirstToken().GetNextToken(), + node.GetLastToken(), + indentationDelta: 1, + option: IndentBlockOption.RelativePosition)); + return; + } + + nextOperation.Invoke(list); + } + } + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeAction.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeAction.cs index c73ef195e96..b562ddbfd3f 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeAction.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeAction.cs @@ -7,6 +7,9 @@ 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.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -14,8 +17,10 @@ namespace Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers { internal partial class GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider { - private class GenerateEqualsAndGetHashCodeAction : CodeAction + private partial class GenerateEqualsAndGetHashCodeAction : CodeAction { + private static readonly SyntaxAnnotation s_specializedFormattingAnnotation = new SyntaxAnnotation(); + private readonly bool _generateEquals; private readonly bool _generateGetHashCode; private readonly GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider _service; @@ -60,13 +65,21 @@ protected override async Task GetChangedDocumentAsync(CancellationToke var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - return await CodeGenerator.AddMemberDeclarationsAsync( + var newDocument = await CodeGenerator.AddMemberDeclarationsAsync( _document.Project.Solution, _containingType, members, new CodeGenerationOptions(contextLocation: syntaxTree.GetLocation(_textSpan)), - cancellationToken) - .ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); + + var rules = new List { new FormatLargeBinaryExpressionRule(_document.GetLanguageService()) }; + 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 CreateGetHashCodeMethodAsync(CancellationToken cancellationToken) @@ -80,7 +93,8 @@ private async Task CreateEqualsMethodAsync(CancellationToken canc { var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); return _document.GetLanguageService().CreateEqualsMethod( - compilation, _containingType, _selectedMembers, cancellationToken); + compilation, _containingType, _selectedMembers, + s_specializedFormattingAnnotation, cancellationToken); } public override string Title diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index caa7b7dfb4e..ce80672788b 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -231,9 +231,10 @@ public bool IsLockStatement(SyntaxNode node) } public bool IsUsingStatement(SyntaxNode node) - { - return node is UsingStatementSyntax; - } + => node.Kind() == SyntaxKind.UsingStatement; + + public bool IsReturnStatement(SyntaxNode node) + => node.Kind() == SyntaxKind.ReturnStatement; public bool IsThisConstructorInitializer(SyntaxToken token) { @@ -1806,6 +1807,9 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond public SyntaxNode WalkDownParentheses(SyntaxNode node) => (node as ExpressionSyntax)?.WalkDownParentheses() ?? node; + public bool IsLogicalAndExpression(SyntaxNode node) + => node.Kind() == SyntaxKind.LogicalAndExpression; + public bool IsLogicalNotExpression(SyntaxNode node) => node.Kind() == SyntaxKind.LogicalNotExpression; diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index 503ea37f367..621e3158eb2 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -75,6 +75,7 @@ internal interface ISyntaxFactsService : ILanguageService bool IsExpressionOfAwaitExpression(SyntaxNode node); SyntaxNode GetExpressionOfAwaitExpression(SyntaxNode node); + bool IsLogicalAndExpression(SyntaxNode node); bool IsLogicalNotExpression(SyntaxNode node); SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node); @@ -164,6 +165,7 @@ internal interface ISyntaxFactsService : ILanguageService bool IsForEachStatement(SyntaxNode node); bool IsLockStatement(SyntaxNode node); bool IsUsingStatement(SyntaxNode node); + bool IsReturnStatement(SyntaxNode node); bool IsLocalDeclarationStatement(SyntaxNode node); bool IsDeclaratorOfLocalDeclarationStatement(SyntaxNode declator, SyntaxNode localDeclarationStatement); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ICodeDefinitionFactoryExtensions_CreateEqualsMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ICodeDefinitionFactoryExtensions_CreateEqualsMethod.cs index f76196cd481..f2a5c8340b0 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ICodeDefinitionFactoryExtensions_CreateEqualsMethod.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ICodeDefinitionFactoryExtensions_CreateEqualsMethod.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; @@ -23,9 +24,12 @@ internal static partial class ICodeDefinitionFactoryExtensions Compilation compilation, INamedTypeSymbol containingType, IList symbols, + SyntaxAnnotation statementAnnotation, CancellationToken cancellationToken) { - var statements = CreateEqualsMethodStatements(factory, compilation, containingType, symbols, cancellationToken); + var statements = CreateEqualsMethodStatements( + factory, compilation, containingType, symbols, cancellationToken); + statements = statements.SelectAsArray(s => s.WithAdditionalAnnotations(statementAnnotation)); return CodeGenerationSymbolFactory.CreateMethodSymbol( attributes: null, @@ -40,7 +44,7 @@ internal static partial class ICodeDefinitionFactoryExtensions statements: statements); } - private static IList CreateEqualsMethodStatements( + private static ImmutableArray CreateEqualsMethodStatements( SyntaxGenerator factory, Compilation compilation, INamedTypeSymbol containingType, @@ -48,11 +52,11 @@ internal static partial class ICodeDefinitionFactoryExtensions CancellationToken cancellationToken) { var iequatableType = compilation.GetTypeByMetadataName("System.IEquatable`1"); - var statements = new List(); + var statements = ArrayBuilder.GetInstance(); var parts = StringBreaker.BreakIntoWordParts(containingType.Name); - string localName = "v"; - for (int i = parts.Count - 1; i >= 0; i--) + var localName = "v"; + for (var i = parts.Count - 1; i >= 0; i--) { var p = parts[i]; if (char.IsLetter(containingType.Name[p.Start])) @@ -120,7 +124,8 @@ internal static partial class ICodeDefinitionFactoryExtensions foreach (var member in members) { var symbolNameExpression = factory.IdentifierName(member.Name); - var thisSymbol = factory.MemberAccessExpression(factory.ThisExpression(), symbolNameExpression).WithAdditionalAnnotations(Simplification.Simplifier.Annotation); + var thisSymbol = factory.MemberAccessExpression(factory.ThisExpression(), symbolNameExpression) + .WithAdditionalAnnotations(Simplification.Simplifier.Annotation); var otherSymbol = factory.MemberAccessExpression(localNameExpression, symbolNameExpression); #if false @@ -161,7 +166,7 @@ internal static partial class ICodeDefinitionFactoryExtensions statements.Add(factory.ReturnStatement( expressions.Aggregate(factory.LogicalAndExpression))); - return statements; + return statements.ToImmutableAndFree(); } private static bool ImplementsIEquatable(ITypeSymbol memberType, INamedTypeSymbol iequatableType) diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index ba986fa86a3..5f87fe3e5b4 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -205,7 +205,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Function Public Function IsUsingStatement(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsUsingStatement - Return TypeOf node Is UsingStatementSyntax + Return node.Kind() = SyntaxKind.UsingStatement + End Function + + Public Function IsReturnStatement(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsReturnStatement + Return node.Kind() = SyntaxKind.ReturnStatement End Function Public Function IsThisConstructorInitializer(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsThisConstructorInitializer @@ -1607,6 +1611,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return If(TryCast(node, ExpressionSyntax)?.WalkDownParentheses(), node) End Function + Public Function IsLogicalAndExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLogicalAndExpression + Return node.IsKind(SyntaxKind.AndAlsoExpression) + End Function + Public Function IsLogicalNotExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLogicalNotExpression Return node.IsKind(SyntaxKind.NotExpression) End Function -- GitLab