提交 f2f61ce8 编写于 作者: P Petr Houska

PR feedback + cleanup and reorg.

上级 9380d197
......@@ -105,7 +105,6 @@ static void Main()
await TestMissingInRegularAndScriptAsync(code);
}
[Fact]
public async Task TestForeachInsideLocalDeclaration()
{
......
// 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.Threading.Tasks;
using ICSharpCode.Decompiler.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editor.UnitTests.RefactoringHelpers;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RefactoringHelpers
......
......@@ -89,9 +89,9 @@ private static string GetSelectionAndResultSpans(string text, out TextSpan selec
private async Task<TNode> GetNodeForSelection<TNode>(string text, TextSpan selection, Func<TNode, bool> predicate) where TNode : SyntaxNode
{
var document = fixture.UpdateDocument(text, SourceCodeKind.Regular);
var resultNode = await document.TryGetSelectedNodeAsync<TNode>(selection, predicate, CancellationToken.None).ConfigureAwait(false);
var relevantNodes = await document.TryGetRelevantNodesAsync<TNode>(selection, CancellationToken.None).ConfigureAwait(false);
return resultNode;
return relevantNodes.FirstOrDefault(predicate);
}
}
}
......@@ -11,7 +11,7 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings
{
[ExportLanguageService(typeof(IRefactoringHelpersService), LanguageNames.CSharp), Shared]
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService<ExpressionSyntax, ArgumentSyntax>
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService<ExpressionSyntax>
{
protected override IEnumerable<SyntaxNode> ExtractNodesSimple(SyntaxNode node, ISyntaxFactsService syntaxFacts)
{
......
......@@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CSharp.NameTupleElement
{
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.IntroduceVariable)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpNameTupleElementCodeRefactoringProvider)), Shared]
internal class CSharpNameTupleElementCodeRefactoringProvider : AbstractNameTupleElementCodeRefactoringProvider<ArgumentSyntax, TupleExpressionSyntax>
internal class CSharpNameTupleElementCodeRefactoringProvider : AbstractNameTupleElementCodeRefactoringProvider<ArgumentSyntax, TupleExpressionSyntax, ExpressionSyntax>
{
[ImportingConstructor]
public CSharpNameTupleElementCodeRefactoringProvider()
......
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseNamedArguments
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpUseNamedArgumentsCodeRefactoringProvider)), Shared]
internal class CSharpUseNamedArgumentsCodeRefactoringProvider : AbstractUseNamedArgumentsCodeRefactoringProvider
{
private abstract class BaseAnalyzer<TSyntax, TSyntaxList> : Analyzer<TSyntax, TSyntax, TSyntaxList>
private abstract class BaseAnalyzer<TSyntax, TSyntaxList> : Analyzer<TSyntax, TSyntax, TSyntaxList, ExpressionSyntax>
where TSyntax : SyntaxNode
where TSyntaxList : SyntaxNode
{
......
......@@ -4,26 +4,23 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TArgumentSyntax> : IRefactoringHelpersService
internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax> : IRefactoringHelpersService
where TExpressionSyntax : SyntaxNode
where TArgumentSyntax : SyntaxNode
{
public async Task<ImmutableArray<TSyntaxNode>> GetRelevantNodesAsync<TSyntaxNode>(
Document document, TextSpan selectionRaw,
CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
var relevantNodesBuilder = ArrayBuilder<TSyntaxNode>.GetInstance();
// Given selection is trimmed first to enable over-selection that spans multiple lines. Since trailing whitespace ends
// at newline boundary over-selection to e.g. a line after LocalFunctionStatement would cause FindNode to find enclosing
// block's Node. That is because in addition to LocalFunctionStatement the selection would also contain trailing trivia
......@@ -43,16 +40,15 @@ internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TAr
return ImmutableArray<TSyntaxNode>.Empty;
}
var relevantNodesBuilder = ArrayBuilder<TSyntaxNode>.GetInstance();
try
{
// Every time a Node is considered an extractNodes method is called to add all nodes around the original one
// that should also be considered.
//
// That enables us to e.g. return node `b` when Node `var a = b;` is being considered without a complex (and potentially
// lang. & situation dependent) into Children descending code here. We can't just try extracted Node because we might
// want the whole node `var a = b;`
//
// In addition to per-node extractions we also check if current location (if selection is empty) is in a header of higher level
// desired node once. We do that only for locations because otherwise `[|int|] A { get; set; }) would trigger all refactorings for
// Property Decl.
// Handle selections:
// - The smallest node whose FullSpan includes the whole (trimmed) selection
......@@ -64,39 +60,13 @@ internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TAr
// Note: Whether we have selection or location has to be checked against original selection because selecting just
// whitespace could collapse selectionTrimmed into and empty Location. But we don't want `[| |]token`
// registering as ` [||]token`.
if (!selectionTrimmed.IsEmpty)
{
var selectionNode = root.FindNode(selectionTrimmed, getInnermostNodeForTie: true);
var prevNode = selectionNode;
do
{
var nonHiddenExtractedSelectedNodes = ExtractNodesSimple(selectionNode, syntaxFacts).OfType<TSyntaxNode>().Where(n => !n.OverlapsHiddenPosition(cancellationToken));
foreach (var selectedNode in nonHiddenExtractedSelectedNodes)
{
// For selections we need to handle an edge case where only AttributeLists are within selection (e.g. `Func([|[in][out]|] arg1);`).
// In that case the smallest encompassing node is still the whole argument node but it's hard to justify showing refactorings for it
// if user selected only its attributes.
// Selection contains only AttributeLists -> don't consider current Node
var spanWithoutAttributes = GetSpanWithoutAttributes(selectedNode, root, syntaxFacts);
if (!selectionTrimmed.IntersectsWith(spanWithoutAttributes))
{
break;
}
relevantNodesBuilder.Add(selectedNode);
}
prevNode = selectionNode;
selectionNode = selectionNode.Parent;
}
while (selectionNode != null && prevNode.FullWidth() == selectionNode.FullWidth());
return relevantNodesBuilder.ToImmutableAndFree();
AddRelevantNodesForSelection(syntaxFacts, root, selectionTrimmed, relevantNodesBuilder, cancellationToken);
}
// Handle what current selection is touching:
else
{
// No more selection -> Handle what current selection is touching:
//
// Consider touching only for empty selections. Otherwise `[|C|] methodName(){}` would be considered as
// touching the Method's Node (through the left edge, see below) which is something the user probably
......@@ -119,6 +89,42 @@ internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TAr
// - Fourthly, if we're in an expression / argument we consider touching a parent expression whenever we're within it
// as long as it is on the first line of such expression (arbitrary heuristic).
// First we need to get tokens we might potentially be touching, tokenToRightOrIn and tokenToLeft.
var (tokenToRightOrIn, tokenToLeft, location) = await GetTokensToRightOrInToLeftAndUpdatedLocation(
document, root, selectionTrimmed, cancellationToken).ConfigureAwait(false);
// In addition to per-node extr also check if current location (if selection is empty) is in a header of higher level
// desired node once. We do that only for locations because otherwise `[|int|] A { get; set; }) would trigger all refactorings for
// Property Decl.
// We cannot check this any sooner because the above code could've changed current location.
AddNonHiddenCorrectTypeNodes(ExtractNodesInHeader(root, location, syntaxFacts), relevantNodesBuilder, cancellationToken);
// Add Nodes for touching tokens as described above.
AddNodesForTokenToRightOrIn(syntaxFacts, root, relevantNodesBuilder, location, tokenToRightOrIn, cancellationToken);
AddNodesForTokenToLeft(syntaxFacts, relevantNodesBuilder, location, tokenToLeft, cancellationToken);
// If the wanted node is an expression syntax -> traverse upwards even if location is deep within a SyntaxNode.
if (typeof(TSyntaxNode).IsSubclassOf(typeof(TExpressionSyntax)) || typeof(TSyntaxNode) == typeof(TExpressionSyntax))
{
await AddNodesDeepInExpression(document, location, relevantNodesBuilder, cancellationToken).ConfigureAwait(false);
}
}
return relevantNodesBuilder.ToImmutable();
}
finally
{
relevantNodesBuilder.Free();
}
}
private async Task<(SyntaxToken tokenToRightOrIn, SyntaxToken tokenToLeft, int location)> GetTokensToRightOrInToLeftAndUpdatedLocation(
Document document,
SyntaxNode root,
TextSpan selectionTrimmed,
CancellationToken cancellationToken)
{
// get Token for current location
var location = selectionTrimmed.Start;
var tokenOnLocation = root.FindToken(location);
......@@ -175,14 +181,41 @@ internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TAr
}
}
// We add all nodes we're in a header of.
// We can't check any sooner because the code above (that figures out tokenToLeft, ...) can change current
// `location`.
AddNonHiddenCorrectTypeNodes(ExtractNodesInHeader(root, location, syntaxFacts), relevantNodesBuilder, cancellationToken);
return (tokenToRightOrIn, tokenToLeft, location);
}
private void AddNodesForTokenToLeft<TSyntaxNode>(ISyntaxFactsService syntaxFacts, ArrayBuilder<TSyntaxNode> relevantNodesBuilder, int location, SyntaxToken tokenToLeft, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
// there could be multiple (n) tokens to the left if first n-1 are Empty -> iterate over all of them
while (tokenToLeft != default)
{
var leftNode = tokenToLeft.Parent;
do
{
// Consider either a Node that is:
// - Ancestor Node of such Token as long as their span ends on location (it's still on the edge)
AddNonHiddenCorrectTypeNodes(ExtractNodesSimple(leftNode, syntaxFacts), relevantNodesBuilder, cancellationToken);
leftNode = leftNode.Parent;
if (leftNode == null || !(leftNode.GetLastToken().Span.End == location || leftNode.Span.End == location))
{
break;
}
}
while (true);
// as long as current tokenToLeft is empty -> its previous token is also tokenToLeft
tokenToLeft = tokenToLeft.Span.IsEmpty
? tokenToLeft.GetPreviousToken(includeZeroWidth: true)
: default;
}
}
private void AddNodesForTokenToRightOrIn<TSyntaxNode>(ISyntaxFactsService syntaxFacts, SyntaxNode root, ArrayBuilder<TSyntaxNode> relevantNodesBuilder, int location, SyntaxToken tokenToRightOrIn, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
if (tokenToRightOrIn != default)
{
var rightNode = tokenOnLocation.Parent;
var rightNode = tokenToRightOrIn.Parent;
do
{
// Consider either a Node that is:
......@@ -214,71 +247,35 @@ internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TAr
}
while (true);
}
}
// there could be multiple (n) tokens to the left if first n-1 are Empty -> iterate over all of them
while (tokenToLeft != default)
private void AddRelevantNodesForSelection<TSyntaxNode>(ISyntaxFactsService syntaxFacts, SyntaxNode root, TextSpan selectionTrimmed, ArrayBuilder<TSyntaxNode> relevantNodesBuilder, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
var leftNode = tokenToLeft.Parent;
var selectionNode = root.FindNode(selectionTrimmed, getInnermostNodeForTie: true);
var prevNode = selectionNode;
do
{
// Consider either a Node that is:
// - Ancestor Node of such Token as long as their span ends on location (it's still on the edge)
AddNonHiddenCorrectTypeNodes(ExtractNodesSimple(leftNode, syntaxFacts), relevantNodesBuilder, cancellationToken);
leftNode = leftNode.Parent;
if (leftNode == null || !(leftNode.GetLastToken().Span.End == location || leftNode.Span.End == location))
var nonHiddenExtractedSelectedNodes = ExtractNodesSimple(selectionNode, syntaxFacts).OfType<TSyntaxNode>().Where(n => !n.OverlapsHiddenPosition(cancellationToken));
foreach (var selectedNode in nonHiddenExtractedSelectedNodes)
{
break;
}
}
while (true);
// as long as current tokenToLeft is empty -> its previous token is also tokenToLeft
tokenToLeft = tokenToLeft.Span.IsEmpty
? tokenToLeft.GetPreviousToken(includeZeroWidth: true)
: default;
}
// For selections we need to handle an edge case where only AttributeLists are within selection (e.g. `Func([|[in][out]|] arg1);`).
// In that case the smallest encompassing node is still the whole argument node but it's hard to justify showing refactorings for it
// if user selected only its attributes.
// If the wanted node is an expression syntax -> traverse upwards even if location is deep within a SyntaxNode
// ArgumentSyntax isn't an "expression" syntax but we want to treat it as such
if (typeof(TSyntaxNode).IsSubclassOf(typeof(TExpressionSyntax)) || typeof(TSyntaxNode) == typeof(TArgumentSyntax))
// Selection contains only AttributeLists -> don't consider current Node
var spanWithoutAttributes = GetSpanWithoutAttributes(selectedNode, root, syntaxFacts);
if (!selectionTrimmed.IntersectsWith(spanWithoutAttributes))
{
await AddNodesDeepInExpression(document, location, relevantNodesBuilder, cancellationToken).ConfigureAwait(false);
break;
}
return relevantNodesBuilder.ToImmutableAndFree();
static void AddNonHiddenCorrectTypeNodes(IEnumerable<SyntaxNode> nodes, ArrayBuilder<TSyntaxNode> resultBuilder, CancellationToken cancellationToken)
{
var correctTypeNonHiddenNodes = nodes.OfType<TSyntaxNode>().Where(n => !n.OverlapsHiddenPosition(cancellationToken));
foreach (var nodeToBeAdded in correctTypeNonHiddenNodes)
{
resultBuilder.Add(nodeToBeAdded);
}
}
relevantNodesBuilder.Add(selectedNode);
}
private static TextSpan GetSpanWithoutAttributes(SyntaxNode node, SyntaxNode root, ISyntaxFactsService syntaxFacts)
{
// Span without AttributeLists
// - No AttributeLists -> original .Span
// - Some AttributeLists -> (first non-trivia/comment Token.Span.Begin, original.Span.End)
// - We need to be mindful about comments due to:
// // [Test1]
// //Comment1
// [||]object Property1 { get; set; }
// the comment node being part of the next token's (`object`) leading trivia and not the AttributeList's node.
var attributeList = syntaxFacts.GetAttributeLists(node);
if (attributeList.Any())
{
var endOfAttributeLists = attributeList.Last().Span.End;
var afterAttributesToken = root.FindTokenOnRightOfPosition(endOfAttributeLists);
return TextSpan.FromBounds(afterAttributesToken.Span.Start, node.Span.End);
prevNode = selectionNode;
selectionNode = selectionNode.Parent;
}
return node.Span;
while (selectionNode != null && prevNode.FullWidth() == selectionNode.FullWidth());
}
/// <summary>
......@@ -441,5 +438,37 @@ protected virtual IEnumerable<SyntaxNode> ExtractNodesInHeader(SyntaxNode root,
ancestor = ancestor.Parent;
}
}
private static TextSpan GetSpanWithoutAttributes(SyntaxNode node, SyntaxNode root, ISyntaxFactsService syntaxFacts)
{
// Span without AttributeLists
// - No AttributeLists -> original .Span
// - Some AttributeLists -> (first non-trivia/comment Token.Span.Begin, original.Span.End)
// - We need to be mindful about comments due to:
// // [Test1]
// //Comment1
// [||]object Property1 { get; set; }
// the comment node being part of the next token's (`object`) leading trivia and not the AttributeList's node.
var attributeList = syntaxFacts.GetAttributeLists(node);
if (attributeList.Any())
{
var endOfAttributeLists = attributeList.Last().Span.End;
var afterAttributesToken = root.FindTokenOnRightOfPosition(endOfAttributeLists);
return TextSpan.FromBounds(afterAttributesToken.Span.Start, node.Span.End);
}
return node.Span;
}
void AddNonHiddenCorrectTypeNodes<TSyntaxNode>(IEnumerable<SyntaxNode> nodes, ArrayBuilder<TSyntaxNode> resultBuilder, CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode
{
var correctTypeNonHiddenNodes = nodes.OfType<TSyntaxNode>().Where(n => !n.OverlapsHiddenPosition(cancellationToken));
foreach (var nodeToBeAdded in correctTypeNonHiddenNodes)
{
resultBuilder.Add(nodeToBeAdded);
}
}
}
}
......@@ -8,7 +8,6 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
......@@ -30,29 +29,28 @@ internal static class CodeRefactoringContextExtensions
}
}
internal static async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this CodeRefactoringContext context)
internal static Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this CodeRefactoringContext context)
where TSyntaxNode : SyntaxNode
{
(var document, var span, var cancellationToken) = context;
var potentialNodes = await GetRelevantNodes<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
return potentialNodes.FirstOrDefault();
}
=> TryGetRelevantNodeAsync<TSyntaxNode>(context.Document, context.Span, context.CancellationToken);
internal static async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this Document document, TextSpan span, Func<TSyntaxNode, bool> predicate, CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode
{
var potentialNodes = await GetRelevantNodes<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
return potentialNodes.FirstOrDefault(predicate);
}
internal static Task<ImmutableArray<TSyntaxNode>> TryGetSelectedNodesAsync<TSyntaxNode>(this CodeRefactoringContext context)
where TSyntaxNode : SyntaxNode
=> TryGetRelevantNodesAsync<TSyntaxNode>(context.Document, context.Span, context.CancellationToken);
internal static async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this Document document, TextSpan span, CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode
internal static async Task<TSyntaxNode> TryGetRelevantNodeAsync<TSyntaxNode>(
this Document document,
TextSpan span,
CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode
{
var potentialNodes = await GetRelevantNodes<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
var potentialNodes = await TryGetRelevantNodesAsync<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
return potentialNodes.FirstOrDefault();
}
private static async Task<ImmutableArray<TSyntaxNode>> GetRelevantNodes<TSyntaxNode>(Document document, TextSpan span, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
internal static async Task<ImmutableArray<TSyntaxNode>> TryGetRelevantNodesAsync<TSyntaxNode>(
this Document document,
TextSpan span,
CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
var helpers = document.GetLanguageService<IRefactoringHelpersService>();
var potentialNodes = await helpers.GetRelevantNodesAsync<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
......
......@@ -71,8 +71,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
// Due to the way `TryGetSelectedNodeAsync` works and how `TAnonymousObjectCreationExpressionSyntax` is e.g. for C# constructed
// it matches even when caret is next to some tokens within the anonymous object creation node.
// E.g.: `var a = new [||]{ b=1,[||] c=2 };` both match due to the caret being next to `,` and `{`.
var helper = document.GetLanguageService<IRefactoringHelpersService>();
var anonymousObject = await document.TryGetSelectedNodeAsync<TAnonymousObjectCreationExpressionSyntax>(span, cancellationToken).ConfigureAwait(false);
var anonymousObject = await document.TryGetRelevantNodeAsync<TAnonymousObjectCreationExpressionSyntax>(
span, cancellationToken).ConfigureAwait(false);
if (anonymousObject == null)
{
return default;
......
......@@ -46,9 +46,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
private async Task<TLocalDeclarationSyntax> FindDisposableLocalDeclaration(Document document, TextSpan selection, CancellationToken cancellationToken)
{
var refactoringHelperService = document.GetLanguageService<IRefactoringHelpersService>();
var declarationSyntax = await document.TryGetSelectedNodeAsync<TLocalDeclarationSyntax>(selection, cancellationToken).ConfigureAwait(false);
var declarationSyntax = await document.TryGetRelevantNodeAsync<TLocalDeclarationSyntax>(selection, cancellationToken).ConfigureAwait(false);
if (declarationSyntax is null || !CanRefactorToContainBlockStatements(declarationSyntax.Parent))
{
return default;
......
......@@ -9,13 +9,13 @@
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.NameTupleElement
{
abstract class AbstractNameTupleElementCodeRefactoringProvider<TArgumentSyntax, TTupleExpressionSyntax> : CodeRefactoringProvider
abstract class AbstractNameTupleElementCodeRefactoringProvider<TArgumentSyntax, TTupleExpressionSyntax, TExpresionSyntax> : CodeRefactoringProvider
where TArgumentSyntax : SyntaxNode
where TTupleExpressionSyntax : SyntaxNode
where TExpresionSyntax : SyntaxNode
where TTupleExpressionSyntax : TExpresionSyntax
{
protected abstract bool IsCloseParenOrComma(SyntaxToken token);
protected abstract TArgumentSyntax WithName(TArgumentSyntax argument, string argumentName);
......@@ -44,11 +44,10 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return default;
}
var helperService = document.GetLanguageService<IRefactoringHelpersService>();
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
Func<SyntaxNode, bool> isTupleArgumentExpression = n => n.Parent != null && syntaxFacts.IsTupleExpression(n.Parent);
var argument = await document.TryGetSelectedNodeAsync<TArgumentSyntax>(span, isTupleArgumentExpression, cancellationToken).ConfigureAwait(false);
var expressions = await document.TryGetRelevantNodesAsync<TExpresionSyntax>(span, cancellationToken).ConfigureAwait(false);
var argument = expressions.FirstOrDefault(
n => n.Parent is TArgumentSyntax && n.Parent.Parent is TTupleExpressionSyntax)?.Parent as TArgumentSyntax;
if (argument == null || !syntaxFacts.IsSimpleArgument(argument))
{
return default;
......
......@@ -20,17 +20,19 @@ protected interface IAnalyzer
Task ComputeRefactoringsAsync(CodeRefactoringContext context, SyntaxNode root);
}
protected abstract class Analyzer<TBaseArgumentSyntax, TSimpleArgumentSyntax, TArgumentListSyntax> : IAnalyzer
protected abstract class Analyzer<TBaseArgumentSyntax, TSimpleArgumentSyntax, TArgumentListSyntax, TExpressionSyntax> : IAnalyzer
where TBaseArgumentSyntax : SyntaxNode
where TSimpleArgumentSyntax : TBaseArgumentSyntax
where TArgumentListSyntax : SyntaxNode
where TExpressionSyntax : SyntaxNode
{
public async Task ComputeRefactoringsAsync(
CodeRefactoringContext context, SyntaxNode root)
{
var (document, textSpan, cancellationToken) = context;
var argument = await context.TryGetSelectedNodeAsync<TSimpleArgumentSyntax>().ConfigureAwait(false);
var expressions = await context.TryGetSelectedNodesAsync<TExpressionSyntax>().ConfigureAwait(false);
var argument = expressions.FirstOrDefault(n => n is TExpressionSyntax)?.Parent as TSimpleArgumentSyntax;
if (argument == null)
{
return;
......
......@@ -9,7 +9,7 @@ Imports Microsoft.CodeAnalysis.LanguageServices
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings
<ExportLanguageService(GetType(IRefactoringHelpersService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicRefactoringHelpersService
Inherits AbstractRefactoringHelpersService(Of ExpressionSyntax, ArgumentSyntax)
Inherits AbstractRefactoringHelpersService(Of ExpressionSyntax)
Protected Overrides Iterator Function ExtractNodesSimple(node As SyntaxNode, syntaxFacts As ISyntaxFactsService) As IEnumerable(Of SyntaxNode)
For Each baseExtraction In MyBase.ExtractNodesSimple(node, syntaxFacts)
......
......@@ -9,7 +9,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.NameTupleElement
<ExtensionOrder(After:=PredefinedCodeRefactoringProviderNames.IntroduceVariable)>
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=NameOf(VisualBasicNameTupleElementCodeRefactoringProvider)), [Shared]>
Friend Class VisualBasicNameTupleElementCodeRefactoringProvider
Inherits AbstractNameTupleElementCodeRefactoringProvider(Of SimpleArgumentSyntax, TupleExpressionSyntax)
Inherits AbstractNameTupleElementCodeRefactoringProvider(Of SimpleArgumentSyntax, TupleExpressionSyntax, ExpressionSyntax)
<ImportingConstructor>
Public Sub New()
......
......@@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNamedArguments
Inherits AbstractUseNamedArgumentsCodeRefactoringProvider
Private Class ArgumentAnalyzer
Inherits Analyzer(Of ArgumentSyntax, SimpleArgumentSyntax, ArgumentListSyntax)
Inherits Analyzer(Of ArgumentSyntax, SimpleArgumentSyntax, ArgumentListSyntax, ExpressionSyntax)
Protected Overrides Function IsPositionalArgument(argument As SimpleArgumentSyntax) As Boolean
Return argument.NameColonEquals Is Nothing
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册