From 08f74cf5f875b6cb691e26bd262f4362e083e968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Hou=C5=A1ka?= Date: Wed, 26 Jun 2019 17:05:50 -0700 Subject: [PATCH] Rework NodeExtractor to be language agnostic via ISyntaxFactsService. --- .../CSharpRefactoringHelpersService.cs | 31 ---------- .../AbstractRefactoringHelpersService.cs | 57 +++++++++++++++---- .../IRefactoringHelpersService.cs | 9 +-- .../VisualBasicRefactoringHelpersService.vb | 19 ------- 4 files changed, 51 insertions(+), 65 deletions(-) diff --git a/src/Features/CSharp/Portable/CodeRefactorings/CSharpRefactoringHelpersService.cs b/src/Features/CSharp/Portable/CodeRefactorings/CSharpRefactoringHelpersService.cs index c57a1dd134b..78775b5d8d6 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/CSharpRefactoringHelpersService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/CSharpRefactoringHelpersService.cs @@ -9,36 +9,5 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings [ExportLanguageService(typeof(IRefactoringHelpersService), LanguageNames.CSharp), Shared] internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService { - public override SyntaxNode DefaultNodeExtractor(SyntaxNode node) - { - switch (node) - { - case LocalDeclarationStatementSyntax localDeclaration: - { - if (localDeclaration.Declaration.Variables.Count == 1 && localDeclaration.Declaration.Variables.First().Initializer != null) - { - var initializer = localDeclaration.Declaration.Variables.First().Initializer; - return initializer.Value; - } - - break; - } - - case ExpressionStatementSyntax expressionStatement: - { - if (expressionStatement.Expression is AssignmentExpressionSyntax assignmentExpression) - { - if (assignmentExpression.Right != null) - { - return assignmentExpression.Right; - } - } - - break; - } - } - - return node; - } } } diff --git a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs index 39eb69b098c..f22a4f65a47 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs @@ -1,23 +1,20 @@ // 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.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings { internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersService { - public abstract SyntaxNode DefaultNodeExtractor(SyntaxNode node); - public async Task TryGetSelectedNodeAsync( Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode { - return await TryGetSelectedNodeAsync(document, selection, n => n is TSyntaxNode, DefaultNodeExtractor, cancellationToken).ConfigureAwait(false) as TSyntaxNode; + return await TryGetSelectedNodeAsync(document, selection, n => n is TSyntaxNode, cancellationToken).ConfigureAwait(false) as TSyntaxNode; } public Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, CancellationToken cancellationToken) @@ -25,8 +22,9 @@ public Task TryGetSelectedNodeAsync(Document document, TextSpan sele return TryGetSelectedNodeAsync(document, selection, predicate, DefaultNodeExtractor, cancellationToken); } - public async Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, Func extractNode, CancellationToken cancellationToken) + public async Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, Func extractNode, CancellationToken cancellationToken) { + var syntaxFacts = document.GetLanguageService(); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var selectionStripped = await CodeRefactoringHelpers.GetStrippedTextSpan(document, selection, cancellationToken).ConfigureAwait(false); @@ -38,7 +36,7 @@ public async Task TryGetSelectedNodeAsync(Document document, TextSpa SyntaxNode prevNode; do { - var wantedNode = TryGetAcceptedNodeOrExtracted(node, predicate, extractNode); + var wantedNode = TryGetAcceptedNodeOrExtracted(node, predicate, extractNode, syntaxFacts); if (wantedNode != default) { return wantedNode; @@ -70,7 +68,7 @@ public async Task TryGetSelectedNodeAsync(Document document, TextSpa do { // consider either a Node that is a parent of touched Token (selection can be within) or ancestor Node of such Token whose span starts on selection - var wantedNode = TryGetAcceptedNodeOrExtracted(rightNode, predicate, extractNode); + var wantedNode = TryGetAcceptedNodeOrExtracted(rightNode, predicate, extractNode, syntaxFacts); if (wantedNode != default) { return wantedNode; @@ -102,7 +100,7 @@ public async Task TryGetSelectedNodeAsync(Document document, TextSpa do { // consider either a Node that is a parent of touched Token (selection can be within) or ancestor Node of such Token whose span ends on selection - var wantedNode = TryGetAcceptedNodeOrExtracted(leftNode, predicate, extractNode); + var wantedNode = TryGetAcceptedNodeOrExtracted(leftNode, predicate, extractNode, syntaxFacts); if (wantedNode != default) { return wantedNode; @@ -116,7 +114,7 @@ public async Task TryGetSelectedNodeAsync(Document document, TextSpa // nothing found return default; - static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate predicate, Func extractNode) + static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate predicate, Func extractNode, ISyntaxFactsService syntaxFacts) { if (node == default) { @@ -128,7 +126,7 @@ static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate b + if (syntaxFacts.IsLocalDeclarationStatement(node)) + { + var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(node); + if (variables.Count == 1) + { + var declaredVariable = variables.First(); + var initializer = syntaxFacts.GetInitializerOfVariableDeclarator(declaredVariable); + + if (initializer != default) + { + var value = syntaxFacts.GetValueOfEqualsValueClause(initializer); + if (value != default) + { + return value; + } + } + } + } + + // a = b; + // -> b + if (syntaxFacts.IsSimpleAssignmentStatement(node)) + { + syntaxFacts.GetPartsOfAssignmentExpressionOrStatement(node, out _, out _, out var rightSide); + if (rightSide != default) + { + return rightSide; + } + } + + return node; + } } } diff --git a/src/Features/Core/Portable/CodeRefactorings/IRefactoringHelpersService.cs b/src/Features/Core/Portable/CodeRefactorings/IRefactoringHelpersService.cs index 9a8890bb2d5..64b0e9d6a17 100644 --- a/src/Features/Core/Portable/CodeRefactorings/IRefactoringHelpersService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/IRefactoringHelpersService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CodeRefactorings @@ -27,7 +28,7 @@ internal interface IRefactoringHelpersService : ILanguageService /// that are under those that might be selected / considered (as described above). It is a that /// should always return either given Node or a Node somewhere below it that should be tested with and /// potentially returned instead of current Node. - /// E.g. + /// E.g. /// allows returning right side Expresion node even if whole AssignmentNode is selected. /// /// @@ -36,7 +37,7 @@ internal interface IRefactoringHelpersService : ILanguageService /// of tokens gracefully. /// /// - Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, Func extractNode, CancellationToken cancellationToken); + Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, Func extractNode, CancellationToken cancellationToken); /// /// @@ -87,9 +88,9 @@ internal interface IRefactoringHelpersService : ILanguageService Task TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate predicate, CancellationToken cancellationToken); /// - /// Extractor function for methods that retrieves expressions from + /// Extractor function for methods that retrieves expressions from /// declarations and assignments. Otherwise returns unchanged . /// - SyntaxNode DefaultNodeExtractor(SyntaxNode node); + SyntaxNode DefaultNodeExtractor(SyntaxNode node, ISyntaxFactsService syntaxFacts); } } diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb index 6f1bf68c41c..56c2049109d 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb @@ -9,24 +9,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings Friend Class VisualBasicRefactoringHelpersService Inherits AbstractRefactoringHelpersService - - Public Overrides Function DefaultNodeExtractor(node As SyntaxNode) As SyntaxNode - If TypeOf node Is LocalDeclarationStatementSyntax Then - Dim localDeclaration = CType(node, LocalDeclarationStatementSyntax) - If localDeclaration.Declarators.Count = 1 And localDeclaration.Declarators.First.Initializer IsNot Nothing Then - Dim initilizer = localDeclaration.Declarators.First.Initializer - If initilizer IsNot Nothing Then - Return initilizer - End If - End If - ElseIf TypeOf node Is AssignmentStatementSyntax Then - Dim assignmentStatement = CType(node, AssignmentStatementSyntax) - If assignmentStatement.Right IsNot Nothing Then - Return assignmentStatement.Right - End If - End If - - Return node - End Function End Class End Namespace -- GitLab