diff --git a/src/EditorFeatures/CSharpTest/ConvertForEachToFor/ConvertForEachToForTests.cs b/src/EditorFeatures/CSharpTest/ConvertForEachToFor/ConvertForEachToForTests.cs index 60b645614bb9d..385cff9b7f674 100644 --- a/src/EditorFeatures/CSharpTest/ConvertForEachToFor/ConvertForEachToForTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertForEachToFor/ConvertForEachToForTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertForEachToFor @@ -576,8 +577,9 @@ void Method() await TestMissingInRegularAndScriptAsync(text); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForEachToFor)] - public async Task WrongCaretPosition1() + public async Task TestCaretBefore() { var text = @" class Test @@ -585,17 +587,30 @@ class Test void Method() { var array = new int[] { 1, 3, 4 }; - [||] foreach (var a in array) + [||] foreach(var a in array) { } } } "; - await TestMissingInRegularAndScriptAsync(text); + var expected = @" +class Test +{ + void Method() + { + var array = new int[] { 1, 3, 4 }; + for (int {|Rename:i|} = 0; i < array.Length; i++) + { + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForEachToFor)] - public async Task WrongCaretPosition2() + public async Task TestCaretAfter() { var text = @" class Test @@ -603,13 +618,56 @@ class Test void Method() { var array = new int[] { 1, 3, 4 }; - foreach (var a in array) [||] + foreach(var a in array) [||] { } } } "; - await TestMissingInRegularAndScriptAsync(text); + var expected = @" +class Test +{ + void Method() + { + var array = new int[] { 1, 3, 4 }; + for (int {|Rename:i|} = 0; i < array.Length; i++) + { + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForEachToFor)] + public async Task TestSelection() + { + var text = @" +class Test +{ + void Method() + { + var array = new int[] { 1, 3, 4 }; + [|foreach(var a in array) + { + }|] + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var array = new int[] { 1, 3, 4 }; + for (int {|Rename:i|} = 0; i < array.Length; i++) + { + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForEachToFor)] diff --git a/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs b/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs index 5860328d7127f..192e02f35b5c7 100644 --- a/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertForToForEach @@ -348,10 +349,11 @@ void Test(string[] array) }"); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)] - public async Task TestMissingOutsideKeyword() + public async Task TestBeforeKeyword() { - await TestMissingInRegularAndScriptAsync( + await TestInRegularAndScript1Async( @"using System; class C @@ -363,6 +365,18 @@ void Test(string[] array) Console.WriteLine(array[i]); } } +}", +@"using System; + +class C +{ + void Test(string[] array) + { + foreach (string {|Rename:v|} in array) + { + Console.WriteLine(v); + } + } }"); } @@ -376,7 +390,7 @@ class C { void Test(string[] array) { - for ([||] int i = 0; i < array.Length; i++) + for ( [||]int i = 0; i < array.Length; i++) { Console.WriteLine(array[i]); } @@ -384,6 +398,37 @@ void Test(string[] array) }"); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)] + public async Task TestInParentheses() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void Test(string[] array) + { + for ([||]int i = 0; i < array.Length; i++) + { + Console.WriteLine(array[i]); + } + } +}", +@"using System; + +class C +{ + void Test(string[] array) + { + foreach (string {|Rename:v|} in array) + { + Console.WriteLine(v); + } + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)] public async Task TestMissingBeforeCloseParen() { @@ -394,7 +439,7 @@ class C { void Test(string[] array) { - for (int i = 0; i < array.Length; i++ [||]) + for (int i = 0; i < array.Length; i++[||] ) { Console.WriteLine(array[i]); } @@ -402,6 +447,37 @@ void Test(string[] array) }"); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)] + public async Task TestInParentheses2() + { + await TestInRegularAndScript1Async( +@"using System; + +class C +{ + void Test(string[] array) + { + for (int i = 0; i < array.Length; i++[||]) + { + Console.WriteLine(array[i]); + } + } +}", +@"using System; + +class C +{ + void Test(string[] array) + { + foreach (string {|Rename:v|} in array) + { + Console.WriteLine(v); + } + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)] public async Task TestAtEndOfFor() { diff --git a/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs b/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs index 1248844667782..ed91ce4a09902 100644 --- a/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs +++ b/src/EditorFeatures/CSharpTest/InvertIf/InvertIfTests.cs @@ -479,6 +479,123 @@ void Goo() }"); } + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + public async Task TestMultiline_IfElseIfElseSelection1() + { + await TestInRegularAndScriptAsync( +@"class A +{ + void Goo() + { + [|if (a) + { + a(); + } + else if (b) + { + b(); + } + else + { + c(); + }|] + } +}", +@"class A +{ + void Goo() + { + if (!a) + { + if (b) + { + b(); + } + else + { + c(); + } + } + else + { + a(); + } + } +}"); + } + + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + public async Task TestMultiline_IfElseIfElseSelection2() + { + await TestInRegularAndScriptAsync( +@"class A +{ + void Goo() + { + [|if (a) + { + a(); + }|] + else if (b) + { + b(); + } + else + { + c(); + } + } +}", +@"class A +{ + void Goo() + { + if (!a) + { + if (b) + { + b(); + } + else + { + c(); + } + } + else + { + a(); + } + } +}"); + } + + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] + public async Task TestMultilineMissing_IfElseIfElseSubSelection() + { + await TestMissingInRegularAndScriptAsync( +@"class A +{ + void Goo() + { + if (a) + { + a(); + } + [|else if (b) + { + b(); + } + else + { + c(); + }|] + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] public async Task TestMultiline_IfElse() { @@ -602,26 +719,6 @@ void Goo() }"); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] - public async Task TestMissingOnNonEmptySpan() - { - await TestMissingInRegularAndScriptAsync( -@"class C -{ - void F() - { - [|if (a) - { - a(); - } - else - { - b(); - }|] - } -}"); - } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] public async Task TestOverlapsHiddenPosition1() { diff --git a/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs b/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs index e28288807d0a6..a726b863c4e48 100644 --- a/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs +++ b/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs @@ -2,8 +2,8 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.MoveDeclarationNearReference; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; -using Microsoft.CodeAnalysis.MoveDeclarationNearReference; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MoveDeclarationNearRefe public class MoveDeclarationNearReferenceTests : AbstractCSharpCodeActionTest { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) - => new MoveDeclarationNearReferenceCodeRefactoringProvider(); + => new CSharpMoveDeclarationNearReferenceCodeRefactoringProvider(); [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveDeclarationNearReference)] public async Task TestMove1() diff --git a/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs b/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs index 4fc8cd96b88cf..61320d80412d3 100644 --- a/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs +++ b/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs @@ -1187,5 +1187,83 @@ C LocalFunction(C c) await TestMissingAsync(testText); } #endregion + + #region Test Ifs + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact] + public async Task TestMultiline_IfElseIfElseSelection1() + { + await TestAsync( +@"class A +{ + void Goo() + { + {|result:[|if (a) + { + a(); + }|] + else if (b) + { + b(); + } + else + { + c(); + }|} + } +}"); + } + + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact] + public async Task TestMultiline_IfElseIfElseSelection2() + { + await TestAsync( +@"class A +{ + void Goo() + { + {|result:[|if (a) + { + a(); + } + else if (b) + { + b(); + } + else + { + c(); + }|]|} + } +}"); + } + + [WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")] + [Fact] + public async Task TestMissingMultiline_IfElseIfElseSelection() + { + await TestMissingAsync( +@"class A +{ + void Goo() + { + if (a) + { + a(); + } + [|else if (b) + { + b(); + } + else + { + c(); + }|] + } +}"); + } + + #endregion } } diff --git a/src/EditorFeatures/VisualBasicTest/ConvertForEachToFor/ConvertForEachToForTests.vb b/src/EditorFeatures/VisualBasicTest/ConvertForEachToFor/ConvertForEachToForTests.vb index f8f60ef4bc9d5..7bc96c7a4ff69 100644 --- a/src/EditorFeatures/VisualBasicTest/ConvertForEachToFor/ConvertForEachToForTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ConvertForEachToFor/ConvertForEachToForTests.vb @@ -412,30 +412,79 @@ End Class Await TestMissingInRegularAndScriptAsync(initial) End Function + - Public Async Function WrongCaretPosition1() As Task + Public Async Function TestBefore() As Task Dim initial = " Class Test Sub Method() - [||] For Each a In New Integer() {1, 2, 3} + Dim array = New Integer() {1, 2, 3} + [||] For Each a In array Next End Sub End Class " - Await TestMissingInRegularAndScriptAsync(initial) + + Dim expected = " +Class Test + Sub Method() + Dim array = New Integer() {1, 2, 3} + For {|Rename:i|} = 0 To array.Length - 1 + Next + End Sub +End Class +" + Await TestInRegularAndScriptAsync(initial, expected) End Function + - Public Async Function WrongCaretPosition2() As Task + Public Async Function TestAfter() As Task Dim initial = " Class Test Sub Method() - For Each a In New Integer() {1, 2, 3} [||] + Dim array = New Integer() {1, 2, 3} + For Each a In array [||] Next End Sub End Class " - Await TestMissingInRegularAndScriptAsync(initial) + + Dim expected = " +Class Test + Sub Method() + Dim array = New Integer() {1, 2, 3} + For {|Rename:i|} = 0 To array.Length - 1 + Next + End Sub +End Class +" + Await TestInRegularAndScriptAsync(initial, expected) + End Function + + + + Public Async Function TestSelection() As Task + Dim initial = " +Class Test + Sub Method() + Dim array = New Integer() {1, 2, 3} + [|For Each a In array + Next|] + End Sub +End Class +" + + Dim expected = " +Class Test + Sub Method() + Dim array = New Integer() {1, 2, 3} + For {|Rename:i|} = 0 To array.Length - 1 + Next + End Sub +End Class +" + Await TestInRegularAndScriptAsync(initial, expected) End Function diff --git a/src/EditorFeatures/VisualBasicTest/ConvertForToForEach/ConvertForToForEachTests.vb b/src/EditorFeatures/VisualBasicTest/ConvertForToForEach/ConvertForToForEachTests.vb index 684b215f94edf..3676a0e301dca 100644 --- a/src/EditorFeatures/VisualBasicTest/ConvertForToForEach/ConvertForToForEachTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ConvertForToForEach/ConvertForToForEachTests.vb @@ -81,9 +81,10 @@ class C end class") End Function + - Public Async Function TestMissingBeforeFor() As Task - Await TestMissingInRegularAndScriptAsync( + Public Async Function TestBeforeFor() As Task + Await TestInRegularAndScript1Async( "imports System class C @@ -92,6 +93,15 @@ class C Console.WriteLine(array(i)) next end sub +end class", +"imports System + +class C + sub Test(array as string()) + For Each {|Rename:v|} In array + Console.WriteLine(v) + next + end sub end class") End Function diff --git a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb index e9d9aee9ebaea..816d56bf1b768 100644 --- a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb +++ b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb @@ -119,18 +119,24 @@ End Module") End Module") End Function + - Public Async Function TestMissingOnNonEmptySpan() As Task - Await TestMissingInRegularAndScriptAsync( -"Module Program - Sub Main() + Public Async Function TestSelection() As Task + Await TestFixOneAsync( +" [|If a Then aMethod() Else bMethod() End If|] - End Sub -End Module") +", +" + If Not a Then + bMethod() + Else + aMethod() + End If +") End Function diff --git a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb index e17c70d649e6d..b924752f56ee5 100644 --- a/src/EditorFeatures/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb +++ b/src/EditorFeatures/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb @@ -440,14 +440,16 @@ End Module Await TestMissingAsync(markup) End Function + - Public Async Function TestMissingOnNonEmptyTextSpan() As Task - Await TestMissingInRegularAndScriptAsync( -"Module Program - Sub Main() - [|If a Th|]en aMethod() Else bMethod() - End Sub -End Module") + Public Async Function TestSelection() As Task + Await TestFixOneAsync( +" + [|If a And b Then aMethod() Else bMethod()|] +", +" + If Not a Or Not b Then bMethod() Else aMethod() +") End Function diff --git a/src/EditorFeatures/VisualBasicTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.vb b/src/EditorFeatures/VisualBasicTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.vb index fb8982099e7fd..a755650601ff6 100644 --- a/src/EditorFeatures/VisualBasicTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.vb @@ -2,14 +2,14 @@ Imports Microsoft.CodeAnalysis.CodeRefactorings Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings -Imports Microsoft.CodeAnalysis.MoveDeclarationNearReference +Imports Microsoft.CodeAnalysis.VisualBasic.MoveDeclarationNearReference Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.MoveDeclarationNearReference Public Class MoveDeclarationNearReferenceTests Inherits AbstractVisualBasicCodeActionTest Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider - Return New MoveDeclarationNearReferenceCodeRefactoringProvider() + Return New VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider() End Function diff --git a/src/EditorFeatures/VisualBasicTest/RefactoringHelpers/RefactoringHelpersTests.vb b/src/EditorFeatures/VisualBasicTest/RefactoringHelpers/RefactoringHelpersTests.vb index 97c83ad9affdd..cfe8f8732e1d9 100644 --- a/src/EditorFeatures/VisualBasicTest/RefactoringHelpers/RefactoringHelpersTests.vb +++ b/src/EditorFeatures/VisualBasicTest/RefactoringHelpers/RefactoringHelpersTests.vb @@ -51,5 +51,34 @@ end class" Await TestMissingAsync(Of ParameterSyntax)(testText) End Function + + + Public Async Function TestForBlockByHeaderExtraction() As Task + Dim testText = " +Imports System + +class CC + sub Test(array as string()) + {|result:[|For i = 0 to array.Length - 1|] + Console.WriteLine(array(i)) + next|} + end sub +end class" + Await TestAsync(Of ForBlockSyntax)(testText) + End Function + + Public Async Function TestForeachBlockByHeaderExtraction() As Task + Dim testText = " +Imports System + +class CC + sub Test(array as string()) + {|[|result:For Each Rename:v In array|] + Console.WriteLine(v) + next|} + end sub +end class" + Await TestAsync(Of ForEachBlockSyntax)(testText) + End Function End Class End Namespace diff --git a/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs index d3857528f9a4e..10a6163846a0f 100644 --- a/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs @@ -2,6 +2,7 @@ using System.Composition; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.ConvertForEachToFor; @@ -25,24 +26,9 @@ public CSharpConvertForEachToForCodeRefactoringProvider() protected override string Title => CSharpFeaturesResources.Convert_to_for; - protected override ForEachStatementSyntax GetForEachStatement(TextSpan selection, SyntaxToken token) - { - var foreachStatement = token.Parent.FirstAncestorOrSelf(); - // https://github.com/dotnet/roslyn/issues/30584: Add tests for this scenario - if (foreachStatement == null || foreachStatement.AwaitKeyword != default) - { - return null; - } - - // support refactoring only if caret is in between "foreach" and ")" - var scope = TextSpan.FromBounds(foreachStatement.ForEachKeyword.Span.Start, foreachStatement.CloseParenToken.Span.End); - if (!scope.IntersectsWith(selection)) - { - return null; - } - - return foreachStatement; - } + // https://github.com/dotnet/roslyn/issues/30584: Add tests for this scenario + protected override bool IsValid(ForEachStatementSyntax foreachStatement) + => foreachStatement.AwaitKeyword == default; protected override bool ValidLocation(ForEachInfo foreachInfo) { diff --git a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs index 0a37bdaca7716..96ff3a48485f3 100644 --- a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs @@ -29,17 +29,6 @@ public CSharpConvertForToForEachCodeRefactoringProvider() protected override string GetTitle() => CSharpFeaturesResources.Convert_to_foreach; - protected override bool IsValidCursorPosition(ForStatementSyntax forStatement, int cursorPos) - { - // If there isn't a selection, then we allow the refactoring from the start of - // 'for' to the start of the open paren, or in the trailing trivia of the c - // close paren. - var startSpan = TextSpan.FromBounds(forStatement.ForKeyword.SpanStart, forStatement.OpenParenToken.SpanStart); - var endSpan = TextSpan.FromBounds(forStatement.CloseParenToken.Span.End, forStatement.CloseParenToken.FullSpan.End); - - return startSpan.IntersectsWith(cursorPos) || endSpan.IntersectsWith(cursorPos); - } - protected override SyntaxList GetBodyStatements(ForStatementSyntax forStatement) => forStatement.Statement is BlockSyntax block ? block.Statements diff --git a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs index 63ccde3de31e6..f0241145ab74f 100644 --- a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs @@ -58,13 +58,6 @@ protected override StatementSyntax GetEmptyEmbeddedStatement() protected override StatementSyntax GetElseBody(IfStatementSyntax ifNode) => ifNode.Else.Statement; - protected override TextSpan GetHeaderSpan(IfStatementSyntax ifNode) - { - return TextSpan.FromBounds( - ifNode.IfKeyword.SpanStart, - ifNode.CloseParenToken.Span.End); - } - protected override bool CanControlFlowOut(SyntaxNode node) { switch (node.Kind()) diff --git a/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..55fe9483ad614 --- /dev/null +++ b/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license + +using System.Composition; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.MoveDeclarationNearReference; + +namespace Microsoft.CodeAnalysis.CSharp.MoveDeclarationNearReference +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.MoveDeclarationNearReference), Shared] + [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.InlineTemporary)] + class CSharpMoveDeclarationNearReferenceCodeRefactoringProvider : AbstractMoveDeclarationNearReferenceCodeRefactoringProvider + { + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs index 24ed456385794..45275f48be05b 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs @@ -389,6 +389,18 @@ protected virtual IEnumerable ExtractNodesIfInHeader(SyntaxNode root { yield return localDeclaration; } + + // Header: `if(...)`{ }; + if (syntaxFacts.IsOnIfStatementHeader(root, location, out var ifStatement)) + { + yield return ifStatement; + } + + // Header: `foreach (var a in b)` { } + if (syntaxFacts.IsOnForeachHeader(root, location, out var foreachStatement)) + { + yield return foreachStatement; + } } } } diff --git a/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs index d91e04f07cd08..531476522c208 100644 --- a/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertForEachToFor/AbstractConvertForEachToForCodeRefactoringProvider.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ConvertForEachToFor @@ -35,20 +34,17 @@ internal abstract class AbstractConvertForEachToForCodeRefactoringProvider).FullName, typeof(IReadOnlyList<>).FullName, typeof(IList).FullName); protected abstract string Title { get; } - protected abstract TForEachStatement GetForEachStatement(TextSpan selelction, SyntaxToken token); protected abstract bool ValidLocation(ForEachInfo foreachInfo); protected abstract (SyntaxNode start, SyntaxNode end) GetForEachBody(TForEachStatement foreachStatement); protected abstract void ConvertToForStatement( SemanticModel model, ForEachInfo info, SyntaxEditor editor, CancellationToken cancellationToken); + protected abstract bool IsValid(TForEachStatement foreachNode); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = root.FindToken(textSpan.Start); - - var foreachStatement = GetForEachStatement(textSpan, token); - if (foreachStatement == null) + var foreachStatement = await context.TryGetSelectedNodeAsync().ConfigureAwait(false); + if (foreachStatement == null || !IsValid(foreachStatement)) { return; } @@ -58,12 +54,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var semanticFact = document.GetLanguageService(); var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var foreachInfo = GetForeachInfo(semanticFact, options, model, foreachStatement, cancellationToken); - if (foreachInfo == null) - { - return; - } - - if (!ValidLocation(foreachInfo)) + if (foreachInfo == null || !ValidLocation(foreachInfo)) { return; } diff --git a/src/Features/Core/Portable/ConvertForToForEach/AbstractConvertForToForEachCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertForToForEach/AbstractConvertForToForEachCodeRefactoringProvider.cs index 84774078a92b8..97cd6147ae034 100644 --- a/src/Features/Core/Portable/ConvertForToForEach/AbstractConvertForToForEachCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertForToForEach/AbstractConvertForToForEachCodeRefactoringProvider.cs @@ -33,7 +33,6 @@ internal abstract class AbstractConvertForToForEachCodeRefactoringProvider< { protected abstract string GetTitle(); - protected abstract bool IsValidCursorPosition(TForStatementSyntax forStatement, int cursorPos); protected abstract SyntaxList GetBodyStatements(TForStatementSyntax forStatement); protected abstract bool IsValidVariableDeclarator(TVariableDeclaratorSyntax firstVariable); @@ -50,32 +49,12 @@ internal abstract class AbstractConvertForToForEachCodeRefactoringProvider< public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = root.FindToken(textSpan.Start); - - var forStatement = token.Parent.GetAncestorOrThis(); + var forStatement = await context.TryGetSelectedNodeAsync().ConfigureAwait(false); if (forStatement == null) { return; } - if (!textSpan.IsEmpty) - { - // if there is a selection, it must match the 'for' span exactly. - if (textSpan != forStatement.GetFirstToken().Span) - { - return; - } - } - else - { - // if there's no selection, defer to the language to decide if it's in an ok location. - if (!IsValidCursorPosition(forStatement, textSpan.Start)) - { - return; - } - } - if (!TryGetForStatementComponents(forStatement, out var iterationVariable, out var initializer, out var memberAccess, out var stepValueExpressionOpt, cancellationToken)) diff --git a/src/Features/Core/Portable/ConvertIfToSwitch/AbstractConvertIfToSwitchCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertIfToSwitch/AbstractConvertIfToSwitchCodeRefactoringProvider.cs index 7d8d1ea6a4c9a..4f91862979e65 100644 --- a/src/Features/Core/Portable/ConvertIfToSwitch/AbstractConvertIfToSwitchCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertIfToSwitch/AbstractConvertIfToSwitchCodeRefactoringProvider.cs @@ -66,20 +66,8 @@ public async Task ComputeRefactoringsAsync(CodeRefactoringContext context) var (document, textSpan, cancellationToken) = context; var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var ifStatement = root.FindNode(textSpan).FirstAncestorOrSelf(); - if (ifStatement == null) - { - return; - } - - if (ifStatement.ContainsDiagnostics) - { - return; - } - - // To prevent noisiness, only show this feature on the 'if' keyword of the if-statement. - var token = ifStatement.GetFirstToken(); - if (!token.Span.Contains(textSpan)) + var ifStatement = await context.TryGetSelectedNodeAsync().ConfigureAwait(false); + if (ifStatement == null || ifStatement.ContainsDiagnostics) { return; } diff --git a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs index 48d61f88121ac..4d6467f066f50 100644 --- a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs @@ -38,27 +38,11 @@ private enum InvertIfStyle public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; - if (!textSpan.IsEmpty) - { - return; - } - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var token = root.FindToken(textSpan.Start); - var ifNode = token.GetAncestor(); - if (ifNode == null) - { - return; - } - - if (ifNode.OverlapsHiddenPosition(cancellationToken)) - { - return; - } + var ifNode = await context.TryGetSelectedNodeAsync().ConfigureAwait(false); - var headerSpan = GetHeaderSpan(ifNode); - if (!headerSpan.IntersectsWith(textSpan)) + if (ifNode == null || ifNode.OverlapsHiddenPosition(cancellationToken)) { return; } @@ -415,7 +399,6 @@ private ImmutableArray GetSubsequentStatementRanges(TIfStatement protected abstract StatementRange GetIfBodyStatementRange(TIfStatementSyntax ifNode); protected abstract SyntaxNode GetCondition(TIfStatementSyntax ifNode); - protected abstract TextSpan GetHeaderSpan(TIfStatementSyntax ifNode); protected abstract IEnumerable UnwrapBlock(TEmbeddedStatement ifBody); protected abstract TEmbeddedStatement GetIfBody(TIfStatementSyntax ifNode); diff --git a/src/Features/Core/Portable/MoveDeclarationNearReference/MoveDeclarationNearReferenceCodeRefactoringProvider.cs b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs similarity index 53% rename from src/Features/Core/Portable/MoveDeclarationNearReference/MoveDeclarationNearReferenceCodeRefactoringProvider.cs rename to src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs index 97799404c6167..c21d1f8d5e6ef 100644 --- a/src/Features/Core/Portable/MoveDeclarationNearReference/MoveDeclarationNearReferenceCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs @@ -13,24 +13,17 @@ namespace Microsoft.CodeAnalysis.MoveDeclarationNearReference { - [ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeRefactoringProviderNames.MoveDeclarationNearReference), Shared] - [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.InlineTemporary)] - internal sealed class MoveDeclarationNearReferenceCodeRefactoringProvider : CodeRefactoringProvider + internal abstract class AbstractMoveDeclarationNearReferenceCodeRefactoringProvider : CodeRefactoringProvider where TLocalDeclaration : SyntaxNode { [ImportingConstructor] - public MoveDeclarationNearReferenceCodeRefactoringProvider() + public AbstractMoveDeclarationNearReferenceCodeRefactoringProvider() { } public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; - if (!textSpan.IsEmpty) - { - return; - } - - var statement = await GetLocalDeclarationStatementAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + var statement = await context.TryGetSelectedNodeAsync().ConfigureAwait(false); if (statement == null) { return; @@ -43,17 +36,6 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - // Don't offer the refactoring inside the initializer for the variable. - var initializer = syntaxFacts.GetInitializerOfVariableDeclarator(variables[0]); - var applicableSpan = initializer == null - ? statement.Span - : TextSpan.FromBounds(statement.SpanStart, initializer.SpanStart); - - if (!applicableSpan.IntersectsWith(textSpan.Start)) - { - return; - } - var service = document.GetLanguageService(); if (!await service.CanMoveDeclarationNearReferenceAsync(document, statement, cancellationToken).ConfigureAwait(false)) { @@ -61,26 +43,13 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte } context.RegisterRefactoring( - new MyCodeAction(c => MoveDeclarationNearReferenceAsync(document, textSpan, c))); - } - - private async Task GetLocalDeclarationStatementAsync( - Document document, TextSpan textSpan, CancellationToken cancellationToken) - { - var syntaxFacts = document.GetLanguageService(); - - var position = textSpan.Start; - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var statement = root.FindToken(position).Parent.Ancestors().FirstOrDefault(n => syntaxFacts.IsLocalDeclarationStatement(n)); - return statement; + new MyCodeAction(c => MoveDeclarationNearReferenceAsync(document, statement, c))); } private async Task MoveDeclarationNearReferenceAsync( - Document document, TextSpan span, CancellationToken cancellationToken) + Document document, SyntaxNode statement, CancellationToken cancellationToken) { - var statement = await GetLocalDeclarationStatementAsync(document, span, cancellationToken).ConfigureAwait(false); var service = document.GetLanguageService(); - return await service.MoveDeclarationNearReferenceAsync(document, statement, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb index daecea599125d..a6b4814d16563 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/VisualBasicRefactoringHelpersService.vb @@ -21,6 +21,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings If IsIdentifierOfParameter(node) Then Yield node.Parent End If + + ' In VB Statement both for/foreach are split into Statement (header) and the rest + ' selecting the header should still count for the whole blockSyntax + If TypeOf node Is ForEachStatementSyntax And TypeOf node.Parent Is ForEachBlockSyntax Then + Dim foreachStatement = CType(node, ForEachStatementSyntax) + Yield foreachStatement.Parent + End If + + If TypeOf node Is ForStatementSyntax And TypeOf node.Parent Is ForBlockSyntax Then + Dim forStatement = CType(node, ForStatementSyntax) + Yield forStatement.Parent + End If End Function Function IsIdentifierOfParameter(node As SyntaxNode) As Boolean diff --git a/src/Features/VisualBasic/Portable/ConvertForEachToFor/VisualBasicConvertForEachToForCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/ConvertForEachToFor/VisualBasicConvertForEachToForCodeRefactoringProvider.vb index 4cb42ae9fcda3..db2231131b404 100644 --- a/src/Features/VisualBasic/Portable/ConvertForEachToFor/VisualBasicConvertForEachToForCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/ConvertForEachToFor/VisualBasicConvertForEachToForCodeRefactoringProvider.vb @@ -6,7 +6,6 @@ Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeRefactorings Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.ConvertForEachToFor -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertForEachToFor @@ -20,24 +19,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertForEachToFor Protected Overrides ReadOnly Property Title As String = VBFeaturesResources.Convert_to_For - Protected Overrides Function GetForEachStatement(selection As TextSpan, token As SyntaxToken) As ForEachBlockSyntax - Dim forEachBlock = token.Parent.FirstAncestorOrSelf(Of ForEachBlockSyntax)() - If forEachBlock Is Nothing Then - Return Nothing - End If - - ' support refactoring only if caret Is on for each statement - Dim scope = forEachBlock.ForEachStatement.Span - If Not scope.IntersectsWith(selection) Then - Return Nothing - End If - + Protected Overrides Function IsValid(foreachNode As ForEachBlockSyntax) As Boolean ' we don't support colon separated statements - If forEachBlock.DescendantTrivia().Any(Function(t) t.IsKind(SyntaxKind.ColonTrivia)) Then - Return Nothing - End If - - Return forEachBlock + Return Not foreachNode.DescendantTrivia().Any(Function(t) t.IsKind(SyntaxKind.ColonTrivia)) End Function Protected Overrides Function ValidLocation(foreachInfo As ForEachInfo) As Boolean diff --git a/src/Features/VisualBasic/Portable/ConvertForToForEach/VisualBasicConvertForToForEachCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/ConvertForToForEach/VisualBasicConvertForToForEachCodeRefactoringProvider.vb index 401f1adbc2c00..d30e5fa82d138 100644 --- a/src/Features/VisualBasic/Portable/ConvertForToForEach/VisualBasicConvertForToForEachCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/ConvertForToForEach/VisualBasicConvertForToForEachCodeRefactoringProvider.vb @@ -27,17 +27,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertForToForEach Return VBFeaturesResources.Convert_to_For_Each End Function - Protected Overrides Function IsValidCursorPosition(forBlock As ForBlockSyntax, cursorPos As Integer) As Boolean - ' If there isn't a selection, then we allow the refactoring in the 'for' keyword, or at the end - ' of hte for-statement signature. - - Dim forStatement = forBlock.ForStatement - Dim startSpan = forStatement.ForKeyword.Span - Dim endSpan = TextSpan.FromBounds(forStatement.Span.End, forStatement.FullSpan.End) - - Return startSpan.IntersectsWith(cursorPos) OrElse endSpan.IntersectsWith(cursorPos) - End Function - Protected Overrides Function GetBodyStatements(forStatement As ForBlockSyntax) As SyntaxList(Of StatementSyntax) Return forStatement.Statements End Function diff --git a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb index d4a6210c3dee2..b498a2b94e10e 100644 --- a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb +++ b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb @@ -14,12 +14,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InvertIf Public Sub New() End Sub - Protected Overrides Function GetHeaderSpan(ifNode As MultiLineIfBlockSyntax) As TextSpan - Return TextSpan.FromBounds( - ifNode.IfStatement.IfKeyword.SpanStart, - ifNode.IfStatement.Condition.Span.End) - End Function - Protected Overrides Function IsElseless(ifNode As MultiLineIfBlockSyntax) As Boolean Return ifNode.ElseBlock Is Nothing End Function diff --git a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb index 68cfa6594db90..85a707dec5773 100644 --- a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb +++ b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb @@ -14,12 +14,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InvertIf Public Sub New() End Sub - Protected Overrides Function GetHeaderSpan(ifNode As SingleLineIfStatementSyntax) As TextSpan - Return TextSpan.FromBounds( - ifNode.IfKeyword.SpanStart, - ifNode.Condition.Span.End) - End Function - Protected Overrides Function IsElseless(ifNode As SingleLineIfStatementSyntax) As Boolean Return ifNode.ElseClause Is Nothing End Function diff --git a/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearRefactoringProvider.vb b/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearRefactoringProvider.vb new file mode 100644 index 0000000000000..257a32bbc7707 --- /dev/null +++ b/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearRefactoringProvider.vb @@ -0,0 +1,14 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.MoveDeclarationNearReference +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.MoveDeclarationNearReference + + + Class VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider + Inherits AbstractMoveDeclarationNearReferenceCodeRefactoringProvider(Of LocalDeclarationStatementSyntax) + End Class +End Namespace diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index c8371a97dca81..51564133faf8a 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -1757,6 +1757,30 @@ public bool IsOnLocalDeclarationHeader(SyntaxNode root, int position, out Syntax return IsOnHeader(position, node, node, holes: initializersExpressions); } + public bool IsOnIfStatementHeader(SyntaxNode root, int position, out SyntaxNode ifStatement) + { + var node = TryGetAncestorForLocation(position, root); + ifStatement = node; + if (ifStatement == null) + { + return false; + } + + return IsOnHeader(position, node, node.CloseParenToken); + } + + public bool IsOnForeachHeader(SyntaxNode root, int position, out SyntaxNode foreachStatement) + { + var node = TryGetAncestorForLocation(position, root); + foreachStatement = node; + if (foreachStatement == null) + { + return false; + } + + return IsOnHeader(position, node, node.CloseParenToken); + } + public bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int position) { var token = root.FindToken(position); diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index 0a6e8cec4b537..6471fa87bbda4 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -418,6 +418,8 @@ internal interface ISyntaxFactsService : ILanguageService bool IsOnMethodHeader(SyntaxNode root, int position, out SyntaxNode method); bool IsOnLocalFunctionHeader(SyntaxNode root, int position, out SyntaxNode localFunction); bool IsOnLocalDeclarationHeader(SyntaxNode root, int position, out SyntaxNode localDeclaration); + bool IsOnIfStatementHeader(SyntaxNode root, int position, out SyntaxNode ifStatement); + bool IsOnForeachHeader(SyntaxNode root, int position, out SyntaxNode foreachStatement); bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int position); // Walks the tree, starting from contextNode, looking for the first construct diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 988710f88b448..0c2ebfacda3cb 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1796,6 +1796,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return IsOnHeader(position, node, node, initializersExpressions) End Function + Public Function IsOnIfStatementHeader(root As SyntaxNode, position As Integer, ByRef ifStatement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOnIfStatementHeader + ifStatement = Nothing + + Dim multipleLineNode = TryGetAncestorForLocation(Of MultiLineIfBlockSyntax)(position, root) + If multipleLineNode IsNot Nothing Then + ifStatement = multipleLineNode + Return IsOnHeader(position, multipleLineNode.IfStatement, multipleLineNode.IfStatement) + End If + + Dim singleLineNode = TryGetAncestorForLocation(Of SingleLineIfStatementSyntax)(position, root) + If singleLineNode IsNot Nothing Then + ifStatement = singleLineNode + Return IsOnHeader(position, singleLineNode, singleLineNode.Condition) + End If + + Return False + End Function + Public Function IsBetweenTypeMembers(sourceText As SourceText, root As SyntaxNode, position As Integer) As Boolean Implements ISyntaxFactsService.IsBetweenTypeMembers Dim token = root.FindToken(position) Dim typeDecl = token.GetAncestor(Of TypeBlockSyntax) @@ -2006,5 +2024,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function GetAttributeLists(node As SyntaxNode) As SyntaxList(Of SyntaxNode) Implements ISyntaxFactsService.GetAttributeLists Return VisualBasicSyntaxGenerator.GetAttributeLists(node) End Function + + Public Function IsOnForeachHeader(root As SyntaxNode, position As Integer, ByRef foreachStatement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOnForeachHeader + Dim node = TryGetAncestorForLocation(Of ForEachBlockSyntax)(position, root) + foreachStatement = node + + If foreachStatement Is Nothing Then + Return False + End If + + Return IsOnHeader(position, node, node.ForEachStatement) + End Function End Class End Namespace