提交 1d43e8be 编写于 作者: P Petr Houska

Enabled RefactoringHelpers to handle location in whitespace.

上级 87ca1880
......@@ -166,18 +166,20 @@ C LocalFunction(C c)
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingLocationAfter()
public async Task TestMissingInTooFarBeforeInWhitespace()
{
var testText = @"
class C
{
void M()
{
[||]
C LocalFunction(C c)
{
return null;
}
[||]
}
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
......@@ -185,18 +187,20 @@ C LocalFunction(C c)
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingLocationBefore()
public async Task TestMissingInWhiteSpaceOnLineWithDifferentStatement()
{
var testText = @"
class C
{
void M()
{
[||]
var a = null; [||]
C LocalFunction(C c)
{
return null;
}
}
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
......@@ -204,23 +208,61 @@ C LocalFunction(C c)
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingLocationBefore2()
public async Task TestBeforeInWhitespace1()
{
var testText = @"
class C
{
void M()
{
[||]//Test comment
{|result:C LocalFunction(C c)
{
return null;
}|}
}
}";
await TestAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestBeforeInWhitespace2()
{
var testText = @"
class C
{
void M()
{
var a = null;
[||] {|result:C LocalFunction(C c)
{
return null;
}|}
}
}";
await TestAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingInNextTokensLeadingTrivia()
{
var testText = @"
class C
{
void M()
{
var a = new object();[||]
C LocalFunction(C c)
{
return null;
}
[||]
}
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
}
#endregion
#region Selections
......@@ -430,6 +472,26 @@ C LocalFunction(C c)
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingSelectionBefore()
{
var testText = @"
class C
{
void M()
{
[| |]C LocalFunction(C c)
{
return null;
}
var a = new object();
}
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
}
#endregion
#region Attributes
......
......@@ -11,7 +11,7 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings
{
[ExportLanguageService(typeof(IRefactoringHelpersService), LanguageNames.CSharp), Shared]
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService<PropertyDeclarationSyntax, ParameterSyntax, MethodDeclarationSyntax>
internal class CSharpRefactoringHelpersService : AbstractRefactoringHelpersService<PropertyDeclarationSyntax, ParameterSyntax, MethodDeclarationSyntax, LocalDeclarationStatementSyntax>
{
protected override IEnumerable<SyntaxNode> ExtractNodeOfHeader(SyntaxNode node, ISyntaxFactsService syntaxFacts)
{
......
......@@ -10,10 +10,11 @@
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
internal abstract class AbstractRefactoringHelpersService<TPropertyDeclaration, TParameter, TMethodDeclaration> : IRefactoringHelpersService
internal abstract class AbstractRefactoringHelpersService<TPropertyDeclaration, TParameter, TMethodDeclaration, TLocalDeclaration> : IRefactoringHelpersService
where TPropertyDeclaration : SyntaxNode
where TParameter : SyntaxNode
where TMethodDeclaration : SyntaxNode
where TLocalDeclaration : SyntaxNode
{
public async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(
Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
......@@ -152,7 +153,7 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
// - Firstly, it is used to handle situation where it touches a Token whose direct ancestor is wanted Node.
// While having the (even empty) selection inside such token or to left of such Token is already handle
// by code above touching it from right `C methodName[||](){}` isn't (the FindNode for that returns Args node).
// - Secondly it is used for left/right edge climbing. E.g. `[||]C methodName(){}` the touching token's direct
// - Secondly, it is used for left/right edge climbing. E.g. `[||]C methodName(){}` the touching token's direct
// ancestor is TypeNode for the return type but it is still reasonable to expect that the user might want to
// be given refactorings for the whole method (as he has caret on the edge of it). Therefore we travel the
// Node tree upwards and as long as we're on the left edge of a Node's span we consider such node & potentially
......@@ -160,6 +161,8 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
// E.g. for right edge `C methodName(){}[||]`: CloseBraceToken -> BlockSyntax -> LocalFunctionStatement -> null (higher
// node doesn't end on position anymore)
// 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.
if (!selectionRaw.IsEmpty)
{
return null;
......@@ -174,6 +177,54 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
? tokenOnLocation
: default;
// A token can be to the left only when there's either no tokenDirectlyToRightOrIn or there's one
// directly starting at current location. Otherwise (otherwise tokenToRightOrIn is also left from location, e.g: `tok[||]enToRightOrIn`)
var tokenToLeft = default(SyntaxToken);
if (tokenToRightOrIn == default || tokenToRightOrIn.Span.Start == location)
{
var tokenPreLocation = (tokenOnLocation.Span.End == location)
? tokenOnLocation
: tokenOnLocation.GetPreviousToken();
tokenToLeft = (tokenPreLocation.Span.End == location)
? tokenPreLocation
: default;
}
// If both tokens directly to left & right are empty -> we're somewhere in the middle of whitespace.
// Since there wouldn't be (m)any other refactorings we can try to offer at least the ones for (semantically)
// closest token/Node. Thus, we move the location to the token in whose `.FullSpan` the original location was.
if (tokenToLeft == default && tokenToRightOrIn == default)
{
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
// assume non-trivia token can't span multiple lines
var tokenLine = sourceText.Lines.GetLineFromPosition(tokenOnLocation.Span.Start);
var locationLine = sourceText.Lines.GetLineFromPosition(location);
// Change location to nearest token only if the token is off by one line or less
if (Math.Abs(tokenLine.LineNumber - locationLine.LineNumber) <= 1)
{
// Note: being a line below a tokenOnLocation is impossible in current model as whitespace
// trailing trivia ends on new line. Which is fine because if you're a line _after_ some node
// you usually don't want refactorings for what's above you.
// tokenOnLocation: token in whose trivia location is at
if (tokenOnLocation.Span.Start >= location)
{
tokenToRightOrIn = tokenOnLocation;
location = tokenToRightOrIn.Span.Start;
}
else
{
tokenToLeft = tokenOnLocation;
location = tokenToLeft.Span.End;
}
}
}
if (tokenToRightOrIn != default)
{
var rightNode = tokenOnLocation.Parent;
......@@ -213,21 +264,6 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
while (true);
}
// if the location is inside tokenToRightOrIn -> no Token can be to Left (tokenToRightOrIn is also left from location, e.g: `tok[||]enToRightOrIn`)
if (tokenToRightOrIn != default && tokenToRightOrIn.Span.Start != location)
{
return null;
}
// Token to left: a token whose span ends on current location
var tokenPreLocation = (tokenOnLocation.Span.End == location)
? tokenOnLocation
: tokenOnLocation.GetPreviousToken();
var tokenToLeft = (tokenPreLocation.Span.End == location)
? tokenPreLocation
: default;
if (tokenToLeft != default)
{
var leftNode = tokenToLeft.Parent;
......@@ -332,6 +368,10 @@ protected virtual IEnumerable<SyntaxNode> DefaultNodeExtractor(SyntaxNode node,
foreach (var headerNode in ExtractNodeOfHeader(node, syntaxFacts))
{
yield return headerNode;
foreach (var extractedNode in ExtractNodeSimple(headerNode, syntaxFacts))
{
yield return extractedNode;
}
}
}
}
......@@ -362,6 +402,16 @@ protected virtual IEnumerable<SyntaxNode> ExtractNodeSimple(SyntaxNode node, ISy
}
}
// var `a = b`;
// -> `var a = b`;
if (node.Parent != null && syntaxFacts.IsLocalDeclarationStatement(node.Parent))
{
if (syntaxFacts.GetVariablesOfLocalDeclarationStatement(node.Parent).Count == 1)
{
yield return node.Parent;
}
}
// `a = b;`
// -> `b`
if (syntaxFacts.IsSimpleAssignmentStatement(node))
......
......@@ -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 PropertyStatementSyntax, ParameterSyntax, MethodStatementSyntax)
Inherits AbstractRefactoringHelpersService(Of PropertyStatementSyntax, ParameterSyntax, MethodStatementSyntax, LocalDeclarationStatementSyntax)
Protected Overrides Iterator Function ExtractNodeSimple(node As SyntaxNode, syntaxFacts As ISyntaxFactsService) As IEnumerable(Of SyntaxNode)
For Each baseExtraction In MyBase.ExtractNodeSimple(node, syntaxFacts)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册