提交 6924c39f 编写于 作者: Š Šimon Koníček

Adding C# & VB MergeNestedIfStatementsCodeRefactoringProvider

上级 f48c0bd7
// 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.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.SplitIntoNestedIfStatements;
namespace Microsoft.CodeAnalysis.CSharp.SplitIntoNestedIfStatements
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.MergeNestedIfStatements), Shared]
internal sealed class CSharpMergeNestedIfStatementsCodeRefactoringProvider
: AbstractMergeNestedIfStatementsCodeRefactoringProvider<IfStatementSyntax>
{
protected override string IfKeywordText => SyntaxFacts.GetText(SyntaxKind.IfKeyword);
protected override bool IsTokenOfIfStatement(SyntaxToken token, out IfStatementSyntax ifStatement)
{
if (token.Parent is IfStatementSyntax s)
{
ifStatement = s;
return true;
}
ifStatement = null;
return false;
}
protected override bool IsFirstStatementOfIfStatement(SyntaxNode statement, out IfStatementSyntax ifStatement)
{
if (statement.Parent is IfStatementSyntax s1 && s1.Statement == statement)
{
ifStatement = s1;
return true;
}
if (statement.Parent is BlockSyntax block && block.Statements.FirstOrDefault() == statement &&
block.Parent is IfStatementSyntax s2 && s2.Statement == block)
{
ifStatement = s2;
return true;
}
ifStatement = null;
return false;
}
protected override ImmutableArray<SyntaxNode> GetElseClauses(IfStatementSyntax ifStatement)
{
return ImmutableArray.Create<SyntaxNode>(ifStatement.Else);
}
protected override IfStatementSyntax MergeIfStatements(IfStatementSyntax outerIfStatement, IfStatementSyntax innerIfStatement, SyntaxGenerator generator)
{
var newCondition = SyntaxFactory.BinaryExpression(
SyntaxKind.LogicalAndExpression,
(ExpressionSyntax)generator.AddParentheses(outerIfStatement.Condition),
(ExpressionSyntax)generator.AddParentheses(innerIfStatement.Condition));
return outerIfStatement.WithCondition(newCondition).WithStatement(innerIfStatement.Statement);
}
}
}
......@@ -26,6 +26,7 @@ internal static class PredefinedCodeRefactoringProviderNames
public const string MoveDeclarationNearReference = "Move Declaration Near Reference Code Action Provider";
public const string SimplifyLambda = "Simplify Lambda Code Action Provider";
public const string ConvertToInterpolatedString = "Convert To Interpolated String Code Action Provider";
public const string MergeNestedIfStatements = "Merge Nested If Statements Code Action Provider";
public const string MoveTypeToFile = "Move Type To File Code Action Provider";
public const string ReplaceDocCommentTextWithTag = "Replace Documentation Comment Text With Tag Code Action Provider";
public const string SplitIntoNestedIfStatements = "Split Into Nested If Statements Code Action Provider";
......
......@@ -2233,6 +2233,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Merge nested &apos;{0}&apos; statements.
/// </summary>
internal static string Merge_nested_0_statements {
get {
return ResourceManager.GetString("Merge_nested_0_statements", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to method.
/// </summary>
......
......@@ -1472,4 +1472,7 @@ This version used in: {2}</value>
<data name="Split_into_nested_0_statements" xml:space="preserve">
<value>Split into nested '{0}' statements</value>
</data>
<data name="Merge_nested_0_statements" xml:space="preserve">
<value>Merge nested '{0}' statements</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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.SplitIntoNestedIfStatements
{
internal abstract class AbstractMergeNestedIfStatementsCodeRefactoringProvider<
TIfStatementSyntax> : CodeRefactoringProvider
where TIfStatementSyntax : SyntaxNode
{
protected abstract string IfKeywordText { get; }
protected abstract bool IsTokenOfIfStatement(SyntaxToken token, out TIfStatementSyntax ifStatement);
protected abstract bool IsFirstStatementOfIfStatement(SyntaxNode statement, out TIfStatementSyntax ifStatement);
protected abstract ImmutableArray<SyntaxNode> GetElseClauses(TIfStatementSyntax ifStatement);
protected abstract TIfStatementSyntax MergeIfStatements(TIfStatementSyntax outerIfStatement, TIfStatementSyntax innerIfStatement, SyntaxGenerator generator);
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var token = root.FindToken(context.Span.Start);
if (context.Span.Length > 0 &&
context.Span != token.Span)
{
return;
}
if (IsTokenOfIfStatement(token, out var ifStatement) &&
IsFirstStatementOfIfStatement(ifStatement, out var parentIfStatement) &&
await CanBeMergedAsync(context.Document, parentIfStatement, ifStatement, context.CancellationToken))
{
context.RegisterRefactoring(
new MyCodeAction(
c => FixAsync(context.Document, context.Span, c),
IfKeywordText));
}
}
private async Task<Document> FixAsync(Document document, TextSpan span, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindToken(span.Start);
Contract.ThrowIfFalse(IsTokenOfIfStatement(token, out var ifStatement));
Contract.ThrowIfFalse(IsFirstStatementOfIfStatement(ifStatement, out var parentIfStatement));
var newIfStatement = MergeIfStatements(parentIfStatement, ifStatement, document.GetLanguageService<SyntaxGenerator>());
var newRoot = root.ReplaceNode(parentIfStatement, newIfStatement.WithAdditionalAnnotations(Formatter.Annotation));
return document.WithSyntaxRoot(newRoot);
}
private async Task<bool> CanBeMergedAsync(
Document document,
TIfStatementSyntax outerIfStatement,
TIfStatementSyntax innerIfStatement,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
if (!GetElseClauses(outerIfStatement).SequenceEqual(GetElseClauses(innerIfStatement), syntaxFacts.AreEquivalent))
{
return false;
}
var statements = syntaxFacts.GetStatementContainerStatements(innerIfStatement.Parent);
if (statements.Count == 1)
{
// There are no other statements below the inner if statement. Merging is OK.
return true;
}
else
{
// There are statements below the inner if statement. We can merge if
// 1. these statements exist below the outer if as well, and
// 2. control flow can't reach after the end of these statements (otherwise, they would get executed twice).
// This will typically look like a single return, break, continue or a throw statement.
if (!syntaxFacts.IsStatementContainer(outerIfStatement.Parent))
{
// This shouldn't happen, but let's be cautious.
return false;
}
var outerStatements = syntaxFacts.GetStatementContainerStatements(outerIfStatement.Parent);
var outerIfStatementIndex = outerStatements.IndexOf(outerIfStatement);
var remainingStatements = statements.Skip(1);
var remainingOuterStatements = outerStatements.Skip(outerIfStatementIndex + 1);
if (!remainingStatements.SequenceEqual(remainingOuterStatements.Take(statements.Count - 1), syntaxFacts.AreEquivalent))
{
return false;
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var controlFlow = semanticModel.AnalyzeControlFlow(statements.First(), statements.Last());
return !controlFlow.EndPointIsReachable;
}
}
private sealed class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument, string ifKeywordText)
: base(string.Format(FeaturesResources.Merge_nested_0_statements, ifKeywordText), createChangedDocument)
{
}
}
}
}
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</target>
......
......@@ -107,6 +107,11 @@
<target state="new">Introduce query variable</target>
<note />
</trans-unit>
<trans-unit id="Merge_nested_0_statements">
<source>Merge nested '{0}' statements</source>
<target state="new">Merge nested '{0}' statements</target>
<note />
</trans-unit>
<trans-unit id="Private_member_0_can_be_removed_as_the_value_assigned_to_it_is_never_read">
<source>Private member '{0}' can be removed as the value assigned to it is never read.</source>
<target state="new">Private member '{0}' can be removed as the value assigned to it is never read.</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.Collections.Immutable
Imports System.Composition
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.SplitIntoNestedIfStatements
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.SplitIntoNestedIfStatements
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.MergeNestedIfStatements), [Shared]>
Friend NotInheritable Class VisualBasicMergeNestedIfStatementsCodeRefactoringProvider
Inherits AbstractMergeNestedIfStatementsCodeRefactoringProvider(Of MultiLineIfBlockSyntax)
Protected Overrides ReadOnly Property IfKeywordText As String = SyntaxFacts.GetText(SyntaxKind.IfKeyword)
Protected Overrides Function IsTokenOfIfStatement(token As SyntaxToken, ByRef ifStatement As MultiLineIfBlockSyntax) As Boolean
If TypeOf token.Parent Is IfStatementSyntax AndAlso TypeOf token.Parent.Parent Is MultiLineIfBlockSyntax Then
ifStatement = DirectCast(token.Parent.Parent, MultiLineIfBlockSyntax)
Return True
End If
ifStatement = Nothing
Return False
End Function
Protected Overrides Function IsFirstStatementOfIfStatement(statement As SyntaxNode, ByRef ifStatement As MultiLineIfBlockSyntax) As Boolean
If TypeOf statement.Parent Is MultiLineIfBlockSyntax Then
ifStatement = DirectCast(statement.Parent, MultiLineIfBlockSyntax)
Return ifStatement.Statements.FirstOrDefault() Is statement
End If
ifStatement = Nothing
Return False
End Function
Protected Overrides Function GetElseClauses(ifStatement As MultiLineIfBlockSyntax) As ImmutableArray(Of SyntaxNode)
Return ImmutableArray.ToImmutableArray(Of SyntaxNode)(ifStatement.ElseIfBlocks).Add(ifStatement.ElseBlock)
End Function
Protected Overrides Function MergeIfStatements(outerIfBlock As MultiLineIfBlockSyntax, innerIfBlock As MultiLineIfBlockSyntax, generator As SyntaxGenerator) As MultiLineIfBlockSyntax
Dim newCondition = SyntaxFactory.BinaryExpression(SyntaxKind.AndAlsoExpression,
DirectCast(generator.AddParentheses(outerIfBlock.IfStatement.Condition), ExpressionSyntax),
SyntaxFactory.Token(SyntaxKind.AndAlsoKeyword),
DirectCast(generator.AddParentheses(innerIfBlock.IfStatement.Condition), ExpressionSyntax))
Return outerIfBlock.WithIfStatement(outerIfBlock.IfStatement.WithCondition(newCondition)).WithStatements(innerIfBlock.Statements)
End Function
End Class
End Namespace
......@@ -1895,14 +1895,31 @@ public SyntaxNode GetValueOfEqualsValueClause(SyntaxNode node)
=> ((EqualsValueClauseSyntax)node)?.Value;
public bool IsExecutableBlock(SyntaxNode node)
=> node.IsKind(SyntaxKind.Block);
=> node.IsKind(SyntaxKind.Block, SyntaxKind.SwitchSection);
public SyntaxList<SyntaxNode> GetExecutableBlockStatements(SyntaxNode node)
=> ((BlockSyntax)node).Statements;
{
switch (node)
{
case BlockSyntax block:
return block.Statements;
case SwitchSectionSyntax switchSection:
return switchSection.Statements;
default:
throw ExceptionUtilities.UnexpectedValue(node);
}
}
public SyntaxNode FindInnermostCommonExecutableBlock(IEnumerable<SyntaxNode> nodes)
=> nodes.FindInnermostCommonBlock();
public bool IsStatementContainer(SyntaxNode node)
=> IsExecutableBlock(node) || node.IsEmbeddedStatementOwner();
public IReadOnlyList<SyntaxNode> GetStatementContainerStatements(SyntaxNode node) => IsExecutableBlock(node)
? GetExecutableBlockStatements(node)
: (IReadOnlyList<SyntaxNode>)ImmutableArray.Create<SyntaxNode>(node.GetEmbeddedStatement());
public bool IsCastExpression(SyntaxNode node)
=> node is CastExpressionSyntax;
......
......@@ -290,6 +290,9 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxList<SyntaxNode> GetExecutableBlockStatements(SyntaxNode node);
SyntaxNode FindInnermostCommonExecutableBlock(IEnumerable<SyntaxNode> nodes);
bool IsStatementContainer(SyntaxNode node);
IReadOnlyList<SyntaxNode> GetStatementContainerStatements(SyntaxNode node);
bool AreEquivalent(SyntaxToken token1, SyntaxToken token2);
bool AreEquivalent(SyntaxNode node1, SyntaxNode node2);
......
......@@ -20,6 +20,19 @@ public static T Last<T>(this IReadOnlyList<T> list)
return list[list.Count - 1];
}
public static int IndexOf<T>(this IReadOnlyList<T> list, T item)
{
for (int i = 0; i < list.Count; ++i)
{
if (list[i].Equals(item))
{
return i;
}
}
return -1;
}
private class ReadOnlyList<T> : IReadOnlyList<T>
{
private readonly IList<T> _list;
......
......@@ -1824,6 +1824,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return nodes.FindInnermostCommonExecutableBlock()
End Function
Public Function IsStatementContainer(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsStatementContainer
Return IsExecutableBlock(node)
End Function
Public Function GetStatementContainerStatements(node As SyntaxNode) As IReadOnlyList(Of SyntaxNode) Implements ISyntaxFactsService.GetStatementContainerStatements
Return GetExecutableBlockStatements(node)
End Function
Private Function ISyntaxFactsService_GetLeadingBlankLines(node As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) Implements ISyntaxFactsService.GetLeadingBlankLines
Return MyBase.GetLeadingBlankLines(node)
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册