提交 9688a4b7 编写于 作者: P Petr Houška

Handle header for property declaration + ConvertAutoProperty tests.

上级 1f54caa7
......@@ -865,10 +865,28 @@ public async Task CursorInType()
var text = @"
class TestClass
{
public int[||] Goo { get; set; }
public in[||]t Goo { get; set; }
}
";
await TestMissingAsync(text);
var expected = @"
class TestClass
{
private int goo;
public int Goo
{
get
{
return goo;
}
set
{
goo = value;
}
}
}
";
await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors);
}
[WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")]
......@@ -902,6 +920,36 @@ public int Goo
await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors);
}
[Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)]
public async Task SelectionName()
{
var text = @"
class TestClass
{
public int [|Goo|] { get; set; }
}
";
var expected = @"
class TestClass
{
private int goo;
public int Goo
{
get
{
return goo;
}
set
{
goo = value;
}
}
}
";
await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors);
}
[Fact, Trait(Traits.Feature, Traits.Features.ConvertAutoPropertyToFullProperty)]
public async Task MoreThanOneGetter()
{
......
......@@ -66,7 +66,7 @@ protected Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, TextSpan s
/// of tokens gracefully.
/// </para>
/// </summary>
protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate<SyntaxNode> predicate, Func<SyntaxNode, ISyntaxFactsService, SyntaxNode> extractNode, CancellationToken cancellationToken)
protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, TextSpan selection, Predicate<SyntaxNode> predicate, Func<SyntaxNode, ISyntaxFactsService, bool, SyntaxNode> extractNode, CancellationToken cancellationToken)
{
// 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
......@@ -85,6 +85,10 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
// 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;`
//
// While we want to do most extractions for both selections and just caret location (empty selection), extractions based
// on type's header node (caret is anywhere in a header of wanted type e.g. `in[||]t A { get; set; }) are limited to
// empty selections. Otherwise `[|int|] A { get; set; }) would trigger all refactorings for Property Decl.
//
// See local function TryGetAcceptedNodeOrExtracted DefaultNodeExtractor for more info.
......@@ -98,7 +102,7 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
SyntaxNode prevNode;
do
{
var wantedNode = TryGetAcceptedNodeOrExtracted(node, predicate, extractNode, syntaxFacts);
var wantedNode = TryGetAcceptedNodeOrExtracted(node, predicate, extractNode, syntaxFacts, extractParentsOfHeader: false);
if (wantedNode != null)
{
return wantedNode;
......@@ -115,6 +119,10 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
// touching the Method's Node (through the left edge, see below) which is something the user probably
// didn't want since they specifically selected only the return type.
//
// Whether we have selection of 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`.
//
// What the selection is touching is used in two ways.
// - 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
......@@ -131,23 +139,24 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
return null;
}
// get Token for current selection (empty) location
var tokenOnSelection = root.FindToken(selectionTrimmed.Start);
// get Token for current location
var location = selectionTrimmed.Start;
var tokenOnLocation = root.FindToken(location);
// Gets a token that is directly to the right of current (empty) selection or that encompasses current selection (`[||]tokenITORightOrIn` or `tok[||]enITORightOrIn`)
var tokenToRightOrIn = tokenOnSelection.Span.Contains(selectionTrimmed.Start)
? tokenOnSelection
// Gets a token that is directly to the right of current location or that encompasses current location (`[||]tokenITORightOrIn` or `tok[||]enITORightOrIn`)
var tokenToRightOrIn = tokenOnLocation.Span.Contains(location)
? tokenOnLocation
: default;
if (tokenToRightOrIn != default)
{
var rightNode = tokenOnSelection.Parent;
var rightNode = tokenOnLocation.Parent;
do
{
// Consider either a Node that is:
// - Parent of touched Token (selection can be within)
// - Ancestor Node of such Token as long as their span starts on selection (it's still on the edge)
var wantedNode = TryGetAcceptedNodeOrExtracted(rightNode, predicate, extractNode, syntaxFacts);
// - Parent of touched Token (location can be within)
// - Ancestor Node of such Token as long as their span starts on location (it's still on the edge)
var wantedNode = TryGetAcceptedNodeOrExtracted(rightNode, predicate, extractNode, syntaxFacts, extractParentsOfHeader: true);
if (wantedNode != null)
{
return wantedNode;
......@@ -155,22 +164,22 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
rightNode = rightNode?.Parent;
}
while (rightNode != null && rightNode.Span.Start == selection.Start);
while (rightNode != null && rightNode.Span.Start == location);
}
// if the selection is inside tokenToRightOrIn -> no Token can be to Left (tokenToRightOrIn is also left from selection, e.g: `tok[||]enITORightOrIn`)
if (tokenToRightOrIn != default && tokenToRightOrIn.Span.Start != selectionTrimmed.Start)
// if the location is inside tokenToRightOrIn -> no Token can be to Left (tokenToRightOrIn is also left from location, e.g: `tok[||]enITORightOrIn`)
if (tokenToRightOrIn != default && tokenToRightOrIn.Span.Start != location)
{
return null;
}
// Token to left: a token whose span ends on current (empty) selection
var tokenPreSelection = (tokenOnSelection.Span.End == selectionTrimmed.Start)
? tokenOnSelection
: tokenOnSelection.GetPreviousToken();
// Token to left: a token whose span ends on current location
var tokenPreLocation = (tokenOnLocation.Span.End == location)
? tokenOnLocation
: tokenOnLocation.GetPreviousToken();
var tokenToLeft = (tokenPreSelection.Span.End == selectionTrimmed.Start)
? tokenPreSelection
var tokenToLeft = (tokenPreLocation.Span.End == location)
? tokenPreLocation
: default;
if (tokenToLeft != default)
......@@ -179,8 +188,8 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
do
{
// Consider either a Node that is:
// - Ancestor Node of such Token as long as their span ends on selection (it's still on the edge)
var wantedNode = TryGetAcceptedNodeOrExtracted(leftNode, predicate, extractNode, syntaxFacts);
// - Ancestor Node of such Token as long as their span ends on location (it's still on the edge)
var wantedNode = TryGetAcceptedNodeOrExtracted(leftNode, predicate, extractNode, syntaxFacts, extractParentsOfHeader: true);
if (wantedNode != null)
{
return wantedNode;
......@@ -188,13 +197,13 @@ protected async Task<SyntaxNode> TryGetSelectedNodeAsync(Document document, Text
leftNode = leftNode?.Parent;
}
while (leftNode != null && leftNode.Span.End == selection.Start);
while (leftNode != null && leftNode.Span.End == location);
}
// nothing found -> return null
return null;
static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate<SyntaxNode> predicate, Func<SyntaxNode, ISyntaxFactsService, SyntaxNode> extractNode, ISyntaxFactsService syntaxFacts)
static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate<SyntaxNode> predicate, Func<SyntaxNode, ISyntaxFactsService, bool, SyntaxNode> extractNode, ISyntaxFactsService syntaxFacts, bool extractParentsOfHeader)
{
if (node == null)
{
......@@ -206,7 +215,7 @@ static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate<Synta
return node;
}
var extractedNode = extractNode(node, syntaxFacts);
var extractedNode = extractNode(node, syntaxFacts, extractParentsOfHeader);
return (extractedNode != null && predicate(extractedNode))
? extractedNode
: null;
......@@ -215,7 +224,7 @@ static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate<Synta
/// <summary>
/// <para>
/// Extractor function for <see cref="TryGetSelectedNodeAsync(Document, TextSpan, Predicate{SyntaxNode}, Func{SyntaxNode, ISyntaxFactsService, SyntaxNode}, CancellationToken)"/> method
/// Extractor function for <see cref="TryGetSelectedNodeAsync(Document, TextSpan, Predicate{SyntaxNode}, Func{SyntaxNode, ISyntaxFactsService, bool, SyntaxNode}, CancellationToken)"/> method
/// that retrieves nodes that should also be considered as refactoring targets given <paramref name="node"/> is considered.
/// Can extract both nodes above and under given <paramref name="node"/>.
/// </para>
......@@ -227,7 +236,7 @@ static SyntaxNode TryGetAcceptedNodeOrExtracted(SyntaxNode node, Predicate<Synta
/// to provide refactoring for `b` node. Similarly for other types of refactorings.
/// </para>
/// </summary>
protected virtual SyntaxNode DefaultNodeExtractor(SyntaxNode node, ISyntaxFactsService syntaxFacts)
protected virtual SyntaxNode DefaultNodeExtractor(SyntaxNode node, ISyntaxFactsService syntaxFacts, bool extractParentsOfHeader)
{
// REMARKS:
// The set of currently attempted extractions is in no way exhaustive and covers only cases
......@@ -263,10 +272,13 @@ protected virtual SyntaxNode DefaultNodeExtractor(SyntaxNode node, ISyntaxFactsS
return rightSide;
}
// a => `b`; -> `a => b;`
if (syntaxFacts.IsLambdaBody(node))
if (extractParentsOfHeader)
{
return node.Parent;
// Header: [TestAttribute] `public int a` { get; set; }
if (syntaxFacts.IsPartOfPropertyDeclarationHeader(node))
{
return syntaxFacts.GetContainingPropertyDeclaration(node);
}
}
return node;
......
......@@ -1951,6 +1951,21 @@ public void GetPartsOfCastExpression(SyntaxNode node, out SyntaxNode type, out S
return null;
}
public bool IsLambdaBody(SyntaxNode node) => node.Parent is LambdaExpressionSyntax lambdaNode && lambdaNode.Body == node;
public bool IsPartOfPropertyDeclarationHeader(SyntaxNode node)
{
var propertyDeclaration = node.GetAncestor<PropertyDeclarationSyntax>();
if (propertyDeclaration == null)
{
return false;
}
var start = propertyDeclaration.AttributeLists.LastOrDefault()?.GetLastToken().GetNextToken().SpanStart ??
propertyDeclaration.SpanStart;
var end = propertyDeclaration.Identifier.FullSpan.End;
return node.Span.Start >= start && node.Span.End <= end;
}
public SyntaxNode GetContainingPropertyDeclaration(SyntaxNode node) => node.GetAncestor<PropertyDeclarationSyntax>();
}
}
......@@ -264,7 +264,6 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsVariableDeclarator(SyntaxNode node);
bool IsDeconstructionAssignment(SyntaxNode node);
bool IsDeconstructionForEachStatement(SyntaxNode node);
bool IsLambdaBody(SyntaxNode node);
/// <summary>
/// Returns true for nodes that represent the body of a method.
......@@ -297,8 +296,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsQueryKeyword(SyntaxToken token);
bool IsThrowExpression(SyntaxNode node);
bool IsElementAccessExpression(SyntaxNode node);
bool IsPartOfPropertyDeclarationHeader(SyntaxNode node);
bool IsIndexerMemberCRef(SyntaxNode node);
SyntaxNode GetContainingPropertyDeclaration(SyntaxNode node);
bool IsIdentifierStartCharacter(char c);
bool IsIdentifierPartCharacter(char c);
bool IsIdentifierEscapeCharacter(char c);
......
......@@ -1945,13 +1945,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return MyBase.SpansPreprocessorDirective(tokens)
End Function
Public Function IsLambdaBody(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLambdaBody
If node.Parent IsNot Nothing And TypeOf node.Parent Is LambdaExpressionSyntax Then
Dim lambdaNode = CType(node.Parent, LambdaExpressionSyntax)
Dim lambdaBodies = lambdaNode.GetStatements()
Return lambdaBodies.Count() = 1 And lambdaBodies.First Is node
Public Function IsPartOfPropertyDeclarationHeader(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsPartOfPropertyDeclarationHeader
Dim propertyDeclaration = node.GetAncestor(Of PropertyStatementSyntax)()
If propertyDeclaration Is Nothing Then
Return False
End If
Return False
Dim start = If(propertyDeclaration.AttributeLists.LastOrDefault()?.GetLastToken().GetNextToken().SpanStart, propertyDeclaration.SpanStart)
Dim [end] = If(propertyDeclaration.AsClause?.FullSpan.[End], propertyDeclaration.Identifier.FullSpan.End)
Return node.Span.Start >= start AndAlso node.Span.[End] <= [end]
End Function
Public Function GetContainingPropertyDeclaration(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetContainingPropertyDeclaration
Return node.GetAncestor(Of PropertyStatementSyntax)
End Function
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册