提交 c45f242b 编写于 作者: C Cyrus Najmabadi

Extract code into helper method.

上级 49700895
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseDefaultLiteral
......@@ -51,49 +52,12 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
var originalNodes = diagnostics.SelectAsArray(
d => (DefaultExpressionSyntax)originalRoot.FindNode(d.Location.SourceSpan, getInnermostNodeForTie: true));
// This code fix will not make changes that affect the semantics of a statement or declaration. Therefore,
// we can skip the expensive verification step in cases where only one default expression appears within the
// group.
var nodesBySemanticBoundary = originalNodes.GroupBy(node => GetSemanticBoundary(node));
var nodesToVerify = nodesBySemanticBoundary.Where(group => group.Skip(1).Any()).Flatten().ToSet();
// We're going to be continually editing this tree. Track all the nodes we
// care about so we can find them across each edit.
document = document.WithSyntaxRoot(originalRoot.TrackNodes(originalNodes));
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var currentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
foreach (var originalDefaultExpression in originalNodes)
{
var defaultExpression = currentRoot.GetCurrentNode(originalDefaultExpression);
var skipVerification = !nodesToVerify.Contains(originalDefaultExpression);
if (skipVerification || defaultExpression.CanReplaceWithDefaultLiteral(parseOptions, options, semanticModel, cancellationToken))
{
var replacement = SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)
.WithTriviaFrom(defaultExpression);
document = document.WithSyntaxRoot(currentRoot.ReplaceNode(defaultExpression, replacement));
semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
currentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
}
}
editor.ReplaceNode(originalRoot, currentRoot);
}
private static SyntaxNode GetSemanticBoundary(DefaultExpressionSyntax node)
{
// Notes:
// 1. Syntax which doesn't fall into one of the "safe buckets" will get placed into a single group keyed off
// the root of the tree. If more than one such node exists in the document, all will be verified.
// 2. Cannot include ArgumentSyntax because it could affect generic argument inference.
return node.FirstAncestorOrSelf<SyntaxNode>(n =>
n is StatementSyntax
|| n is ParameterSyntax
|| n is VariableDeclaratorSyntax
|| n.Parent == null);
await editor.ApplySemanticEditsAsync(
document, originalNodes,
(semanticModel, defaultExpression) => defaultExpression.CanReplaceWithDefaultLiteral(parseOptions, options, semanticModel, cancellationToken),
defaultExpression => SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)
.WithTriviaFrom(defaultExpression),
cancellationToken).ConfigureAwait(false);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
......
......@@ -257,6 +257,15 @@ public bool IsUsingStatement(SyntaxNode node)
public bool IsReturnStatement(SyntaxNode node)
=> node.Kind() == SyntaxKind.ReturnStatement;
public bool IsStatement(SyntaxNode node)
=> node is StatementSyntax;
public bool IsParameter(SyntaxNode node)
=> node is ParameterSyntax;
public bool IsVariableDeclarator(SyntaxNode node)
=> node is VariableDeclaratorSyntax;
public SyntaxNode GetExpressionOfReturnStatement(SyntaxNode node)
=> (node as ReturnStatementSyntax)?.Expression;
......
......@@ -188,6 +188,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsForEachStatement(SyntaxNode node);
bool IsLockStatement(SyntaxNode node);
bool IsUsingStatement(SyntaxNode node);
bool IsStatement(SyntaxNode node);
bool IsParameter(SyntaxNode node);
bool IsVariableDeclarator(SyntaxNode node);
bool IsReturnStatement(SyntaxNode node);
SyntaxNode GetExpressionOfReturnStatement(SyntaxNode node);
......
// 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.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
internal static class SyntaxEditorExtensions
{
/// <summary>
/// Helper function for fix-all fixes where individual fixes may affect the viability
/// of another. For example, consider the following code:
///
/// if ((double)x == (double)y)
///
/// In this code either cast can be removed, but at least one cast must remain. Even
/// though an analyzer marks both, a fixer must not remove both. One way to accomplish
/// this would be to have the fixer do a semantic check after each application. However
/// This is extremely expensive, especially for hte common cases where one fix does
/// not affect each other.
///
/// To address that, this helper groups fixes at certain boundary points. i.e. at
/// statement boundaries. If there is only one fix within the boundary, it does not
/// do any semantic verification. However, if there are multiple fixes in a boundary
/// it will call into <paramref name="canReplace"/> to validate if the subsequent fix
/// can be made or not.
/// </summary>
public static async Task ApplySemanticEditsAsync<TNode>(
this SyntaxEditor editor, Document document,
ImmutableArray<TNode> originalNodes,
Func<SemanticModel, TNode, bool> canReplace,
Func<TNode, SyntaxNode> createReplacement,
CancellationToken cancellationToken) where TNode : SyntaxNode
{
// This code fix will not make changes that affect the semantics of a statement
// or declaration. Therefore, we can skip the expensive verification step in
// cases where only one expression appears within the group.
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var nodesBySemanticBoundary = originalNodes.GroupBy(node => GetSemanticBoundary(syntaxFacts, node));
var nodesToVerify = nodesBySemanticBoundary.Where(group => group.Skip(1).Any()).Flatten().ToSet();
// We're going to be continually editing this tree. Track all the nodes we
// care about so we can find them across each edit.
var originalRoot = editor.OriginalRoot;
document = document.WithSyntaxRoot(originalRoot.TrackNodes(originalNodes));
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var currentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
foreach (var originalNode in originalNodes)
{
var currentNode = currentRoot.GetCurrentNode(originalNode);
var skipVerification = !nodesToVerify.Contains(originalNode);
if (skipVerification || canReplace(semanticModel, currentNode))
{
var replacement = createReplacement(currentNode);
document = document.WithSyntaxRoot(currentRoot.ReplaceNode(currentNode, replacement));
semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
currentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
}
}
editor.ReplaceNode(originalRoot, currentRoot);
}
private static SyntaxNode GetSemanticBoundary(ISyntaxFactsService syntaxFacts, SyntaxNode node)
{
// Notes:
// 1. Syntax which doesn't fall into one of the "safe buckets" will get placed into a
// single group keyed off the root of the tree. If more than one such node exists
// in the document, all will be verified.
// 2. Cannot include ArgumentSyntax because it could affect generic argument inference.
return node.FirstAncestorOrSelf<SyntaxNode>(
n => syntaxFacts.IsStatement(n) ||
syntaxFacts.IsParameter(n) ||
syntaxFacts.IsVariableDeclarator(n) ||
n.Parent == null);
}
}
}
......@@ -251,6 +251,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return node.Kind() = SyntaxKind.ReturnStatement
End Function
Public Function IsStatement(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsStatement
Return TypeOf node Is StatementSyntax
End Function
Public Function IsParameter(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsParameter
Return TypeOf node Is ParameterSyntax
End Function
Public Function IsVariableDeclarator(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsVariableDeclarator
Return TypeOf node Is VariableDeclaratorSyntax
End Function
Public Function GetExpressionOfReturnStatement(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetExpressionOfReturnStatement
Return TryCast(node, ReturnStatementSyntax)?.Expression
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册