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

Removed explicit TryGetFromDeepInExpression. Fix emty Tokens/Node to the left.

上级 46c7a3aa
......@@ -166,9 +166,12 @@ public async Task TestInCall_FirstComma()
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestInCall_FirstComma2()
{
await TestMissingAsync(@"class C { void M((int arg1, int arg2) x) => M((1,[||] 2)); }");
await TestInRegularAndScript1Async(
@"class C { void M((int arg1, int arg2) x) => M((1,[||] 2)); }",
@"class C { void M((int arg1, int arg2) x) => M((1, arg2: 2)); }");
}
[Fact]
......
......@@ -265,6 +265,25 @@ C LocalFunction(C c)
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestInEmptySyntaxNode()
{
var testText = @"
class C
{
void M()
{
N(0, N(0, [||]{|result:|}, 0));
}
int N(int a, int b, int c)
{
}
}";
await TestAsync<ArgumentSyntax>(testText);
}
#endregion
#region Selections
......@@ -1394,7 +1413,7 @@ void Goo()
#endregion
#region Test TryGetDeepInNodeAsync
#region Test Deep in expression
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestDeepIn()
......@@ -1411,7 +1430,7 @@ void N(int a)
{
}
}";
await TestGetDeepInNodeAsync<ArgumentSyntax>(testText, Functions<ArgumentSyntax>.True, Functions<SyntaxNode>.False);
await TestAsync<ArgumentSyntax>(testText);
}
[Fact]
......@@ -1431,12 +1450,12 @@ void N(int a)
{
}
}";
await TestMissingGetDeepInNodeAsync<ArgumentSyntax>(testText, predicate: Functions<ArgumentSyntax>.True, traverseUntil: Functions<SyntaxNode>.False);
await TestMissingAsync<ArgumentSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestDeepInPredicate()
public async Task TestDeepInExpression()
{
var testText = @"
class C
......@@ -1451,46 +1470,9 @@ int N(int a)
return a;
}
}";
await TestGetDeepInNodeAsync<ArgumentSyntax>(testText, predicate: n => n.Parent is TupleExpressionSyntax, traverseUntil: Functions<SyntaxNode>.False);
await TestAsync<ArgumentSyntax>(testText, predicate: n => n.Parent is TupleExpressionSyntax);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingTraverseUntil()
{
var testText = @"
class C
{
void M()
{
N(0, N(0, [||], 0));
}
int N(int a, int b, int c)
{
}
}";
await TestMissingGetDeepInNodeAsync<ArgumentSyntax>(testText, predicate: Functions<ArgumentSyntax>.True, traverseUntil: n => n is ArgumentListSyntax);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestTraverseUntilFirst()
{
var testText = @"
class C
{
void M()
{
N(0, N(0, {|result:0[||]+0|}, 0));
}
int N(int a, int b, int c)
{
}
}";
await TestGetDeepInNodeAsync<ArgumentSyntax>(testText, predicate: Functions<ArgumentSyntax>.True, traverseUntil: n => n is ArgumentListSyntax);
}
#endregion
}
}
......@@ -34,23 +34,6 @@ public override void Dispose()
base.Dispose();
}
protected async Task TestGetDeepInNodeAsync<TNode>(string text, Func<TNode, bool> predicate, Func<SyntaxNode, bool> traverseUntil) where TNode : SyntaxNode
{
text = GetSelectionAndResultSpans(text, out var selection, out var result);
var resultNode = await GetNodeForSelectionDeepIn<TNode>(text, selection, predicate, traverseUntil).ConfigureAwait(false);
Assert.NotNull(resultNode);
Assert.Equal(result, resultNode.Span);
}
protected async Task TestMissingGetDeepInNodeAsync<TNode>(string text, Func<TNode, bool> predicate, Func<SyntaxNode, bool> traverseUntil) where TNode : SyntaxNode
{
text = GetSelectionSpan(text, out var selection);
var resultNode = await GetNodeForSelectionDeepIn<TNode>(text, selection, predicate, traverseUntil).ConfigureAwait(false);
Assert.Null(resultNode);
}
protected Task TestAsync<TNode>(string text) where TNode : SyntaxNode => TestAsync<TNode>(text, Functions<TNode>.True);
protected async Task TestAsync<TNode>(string text, Func<TNode, bool> predicate) where TNode : SyntaxNode
......@@ -111,14 +94,5 @@ private static string GetSelectionAndResultSpans(string text, out TextSpan selec
var resultNode = await service.TryGetSelectedNodeAsync<TNode>(document, selection, predicate, CancellationToken.None).ConfigureAwait(false);
return resultNode;
}
private async Task<TNode> GetNodeForSelectionDeepIn<TNode>(string text, TextSpan selection, Func<TNode, bool> predicate, Func<SyntaxNode, bool> traverseUntil) where TNode : SyntaxNode
{
var document = fixture.UpdateDocument(text, SourceCodeKind.Regular);
var service = document.GetLanguageService<IRefactoringHelpersService>();
var resultNode = await service.TryGetDeepInNodeAsync<TNode>(document, selection, predicate, traverseUntil, CancellationToken.None).ConfigureAwait(false);
return resultNode;
}
}
}
......@@ -11,7 +11,7 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings
{
[ExportLanguageService(typeof(IRefactoringHelpersService), LanguageNames.CSharp), Shared]
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService<ExpressionSyntax, ArgumentSyntax>
{
protected override IEnumerable<SyntaxNode> ExtractNodesSimple(SyntaxNode node, ISyntaxFactsService syntaxFacts)
{
......
......@@ -12,7 +12,9 @@
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersService
internal abstract class AbstractRefactoringHelpersService<TExpressionSyntax, TArgumentSyntax> : IRefactoringHelpersService
where TExpressionSyntax : SyntaxNode
where TArgumentSyntax : SyntaxNode
{
public async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(
Document document,
......@@ -121,6 +123,8 @@ internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersS
// Note: left-edge climbing needs to handle AttributeLists explicitly, see below for more information.
// - Thirdly, if location isn't touching anything, we move the location to the token in whose trivia location is in.
// more about that below.
// - 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).
// get Token for current location
var location = selectionTrimmed.Start;
......@@ -138,7 +142,7 @@ internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersS
{
var tokenPreLocation = (tokenOnLocation.Span.End == location)
? tokenOnLocation
: tokenOnLocation.GetPreviousToken();
: tokenOnLocation.GetPreviousToken(includeZeroWidth: true);
tokenToLeft = (tokenPreLocation.Span.End == location)
? tokenPreLocation
......@@ -188,7 +192,6 @@ internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersS
if (tokenToRightOrIn != default)
{
var rightNode = tokenOnLocation.Parent;
do
{
......@@ -225,7 +228,8 @@ internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersS
while (true);
}
if (tokenToLeft != default)
// 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
......@@ -238,12 +242,25 @@ internal abstract class AbstractRefactoringHelpersService : IRefactoringHelpersS
}
leftNode = leftNode.Parent;
if (leftNode == null || leftNode.GetLastToken().Span.End != location)
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;
}
// 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))
{
return await TryGetDeepInNodeAsync(document, location, predicate, cancellationToken).ConfigureAwait(false);
}
// nothing found -> return null
......@@ -405,26 +422,20 @@ protected virtual IEnumerable<SyntaxNode> ExtractNodesIfInHeader(SyntaxNode root
}
}
public virtual async Task<TSyntaxNode> TryGetDeepInNodeAsync<TSyntaxNode>(
Document document, TextSpan selectionRaw,
protected virtual async Task<TSyntaxNode> TryGetDeepInNodeAsync<TSyntaxNode>(
Document document, int position,
Func<TSyntaxNode, bool> predicate,
Func<SyntaxNode, bool> traverseUntil,
CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
// Traversing up from deep inside is applicable only for empty selections
// If user selected something he wanted something very specific -> not arbitrary traversing up
if (!selectionRaw.IsEmpty)
{
return null;
}
// If we're deep inside we don't have to deal with being on edges (that gets dealt by TryGetSelectedNodeAsync)
// -> can simply FindToken -> proceed with its ancestors.
var location = selectionRaw.Start;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindToken(location);
var expression = token.Parent?.FirstAncestorOrSelfUntil<TSyntaxNode>(predicate, traverseUntil);
var token = root.FindTokenOnRightOfPosition(position, true);
var expression = token.Parent?.FirstAncestorOrSelf<TSyntaxNode>(predicate);
if (expression == null)
{
......@@ -434,7 +445,7 @@ protected virtual IEnumerable<SyntaxNode> ExtractNodesIfInHeader(SyntaxNode root
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var argumentStartLine = sourceText.Lines.GetLineFromPosition(expression.Span.Start).LineNumber;
var caretLine = sourceText.Lines.GetLineFromPosition(location).LineNumber;
var caretLine = sourceText.Lines.GetLineFromPosition(position).LineNumber;
if (argumentStartLine != caretLine)
{
......
......@@ -35,12 +35,6 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var awaitable = await context.TryGetSelectedNodeAsync<TInvocationExpressionSyntax>().ConfigureAwait(false);
if (awaitable == null)
{
// Enable add await refactoring even if caret is deep in an expression.
awaitable = await context.TryGetDeepInNodeAsync<TInvocationExpressionSyntax>().ConfigureAwait(false);
}
if (awaitable == null || !IsValidAwaitableExpression(awaitable, model, syntaxFacts))
{
return;
......
......@@ -35,17 +35,5 @@ internal static Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this Code
return helpers.TryGetSelectedNodeAsync<TSyntaxNode>(document, context.Span, context.CancellationToken);
}
internal static Task<TSyntaxNode> TryGetDeepInNodeAsync<TSyntaxNode>(this CodeRefactoringContext context) where TSyntaxNode : SyntaxNode
=> TryGetDeepInNodeAsync<TSyntaxNode>(context, Functions<TSyntaxNode>.True, Functions<SyntaxNode>.False);
internal static Task<TSyntaxNode> TryGetDeepInNodeAsync<TSyntaxNode>(this CodeRefactoringContext context, Func<TSyntaxNode, bool> predicate, Func<SyntaxNode, bool> traverseUntil)
where TSyntaxNode : SyntaxNode
{
var document = context.Document;
var helpers = document.GetLanguageService<IRefactoringHelpersService>();
return helpers.TryGetDeepInNodeAsync<TSyntaxNode>(document, context.Span, predicate, traverseUntil, context.CancellationToken);
}
}
}
......@@ -22,6 +22,7 @@ internal interface IRefactoringHelpersService : ILanguageService
/// - Selection is zero-width and in whitespace that corresponds to a Token whose direct ancestor is of type of type <typeparamref name="TSyntaxNode"/>.
/// - Selection is zero-width and in a header (defined by ISyntaxFacts helpers) of an node of type of type <typeparamref name="TSyntaxNode"/>.
/// - Token whose direct parent of type <typeparamref name="TSyntaxNode"/> is selected.
/// - Wanted node is an expression / argument and curent empty selection is within such syntax node (arbitrarily deep) on its first line.
/// - Whole node of a type <typeparamref name="TSyntaxNode"/> is selected.
/// </para>
/// <para>
......@@ -36,12 +37,5 @@ internal interface IRefactoringHelpersService : ILanguageService
/// </summary>
Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode;
Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(Document document, TextSpan selection, Func<TSyntaxNode, bool> predicate, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode;
Task<TSyntaxNode> TryGetDeepInNodeAsync<TSyntaxNode>(
Document document, TextSpan selectionRaw,
Func<TSyntaxNode, bool> predicate,
Func<SyntaxNode, bool> traverseUntil,
CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode;
}
}
......@@ -49,17 +49,6 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
Func<SyntaxNode, bool> isTupleArgumentExpression = n => n.Parent != null && syntaxFacts.IsTupleExpression(n.Parent);
var argument = await helperService.TryGetSelectedNodeAsync<TArgumentSyntax>(document, span, isTupleArgumentExpression, cancellationToken).ConfigureAwait(false);
if (argument == null)
{
// Enable refactoring even if we're deep in a argument expression
// -> traverse upwards only until we encounter TTupleExpressionSyntax
// -> might be a few TupleExpressions deep -> want to limit ourselves to the closest one
argument = await helperService.TryGetDeepInNodeAsync<TArgumentSyntax>(
document, span,
predicate: isTupleArgumentExpression,
traverseUntil: n => n is TTupleExpressionSyntax,
cancellationToken).ConfigureAwait(false);
}
if (argument == null || !syntaxFacts.IsSimpleArgument(argument))
{
......
......@@ -31,15 +31,6 @@ protected abstract class Analyzer<TBaseArgumentSyntax, TSimpleArgumentSyntax, TA
var (document, textSpan, cancellationToken) = context;
var argument = await context.TryGetSelectedNodeAsync<TSimpleArgumentSyntax>().ConfigureAwait(false);
if (argument == null)
{
// Enable refactoring even if we're deep in a argument expression
// -> traverse upwards only until we encounter TArgumentListSyntax
// -> might be a few TArgumentListSyntax deep -> want to limit ourselves to the closest one
argument = await context.TryGetDeepInNodeAsync<TSimpleArgumentSyntax>(predicate: Functions<SyntaxNode>.True, traverseUntil: node => node is TArgumentListSyntax).ConfigureAwait(false);
}
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
Inherits AbstractRefactoringHelpersService(Of ExpressionSyntax, ArgumentSyntax)
Protected Overrides Iterator Function ExtractNodesSimple(node As SyntaxNode, syntaxFacts As ISyntaxFactsService) As IEnumerable(Of SyntaxNode)
For Each baseExtraction In MyBase.ExtractNodesSimple(node, syntaxFacts)
......
......@@ -790,24 +790,5 @@ public static TNode FirstAncestorOrSelfUntil<TNode>(this SyntaxNode node, Func<S
return default;
}
public static TNode FirstAncestorOrSelfUntil<TNode>(this SyntaxNode node, Func<TNode, bool> predicate, Func<SyntaxNode, bool> traverseUntil)
where TNode : SyntaxNode
{
for (var current = node; current != null; current = current.GetParent(ascendOutOfTrivia: true))
{
if (current is TNode tnode && predicate(tnode))
{
return tnode;
}
if (traverseUntil(current))
{
break;
}
}
return default;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册