未验证 提交 b552a023 编写于 作者: P Petr Houška 提交者: GitHub

Merge pull request #37295 from petrroll/move-refa-to-helpers5

Move refa to helpers5
......@@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.AddAwait;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.AddAwait
......@@ -259,9 +260,10 @@ async Task<int> GetNumberAsync()
}
[Fact]
public async Task MissingOnSemiColon()
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task OnSemiColon()
{
await TestMissingInRegularAndScriptAsync(@"
await TestInRegularAndScriptAsync(@"
using System.Threading.Tasks;
class Program
{
......@@ -269,9 +271,64 @@ async Task<int> GetNumberAsync()
{
var x = GetNumberAsync();[||]
}
}", @"
using System.Threading.Tasks;
class Program
{
async Task<int> GetNumberAsync()
{
var x = await GetNumberAsync();
}
}");
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task Selection()
{
await TestInRegularAndScriptAsync(@"
using System.Threading.Tasks;
class Program
{
async Task<int> GetNumberAsync()
{
var x = [|GetNumberAsync()|];
}
}", @"
using System.Threading.Tasks;
class Program
{
async Task<int> GetNumberAsync()
{
var x = await GetNumberAsync();
}
}");
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task Selection2()
{
await TestInRegularAndScriptAsync(@"
using System.Threading.Tasks;
class Program
{
async Task<int> GetNumberAsync()
{
[|var x = GetNumberAsync();|]
}
}", @"
using System.Threading.Tasks;
class Program
{
async Task<int> GetNumberAsync()
{
var x = await GetNumberAsync();
}
}");
}
[Fact]
public async Task ChainedInvocation()
{
......
......@@ -586,9 +586,9 @@ static void Main()
[WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)]
public async Task TestMissingCaretPositionBeforeBody()
public async Task TestCaretPositionBeforeBody()
{
await TestMissingInRegularAndScriptAsync(
await TestInRegularAndScriptAsync(
@"using System;
class Program
......@@ -597,6 +597,15 @@ static void Main()
{
Action a = () => [||]Console.WriteLine();
}
}",
@"using System;
class Program
{
static void Main()
{
Action a = Console.WriteLine;
}
}");
}
......
......@@ -39,6 +39,72 @@ static void Main()
await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
}
[Fact]
[WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")]
public async Task TestSelection1()
{
var code = @"
class C
{
static void Main()
{
[|int i = 0;|]
}
}";
var expected = @"
class C
{
static void Main()
{
var i = 0;
}
}";
await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
}
[Fact]
[WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")]
public async Task TestSelection2()
{
var code = @"
class C
{
static void Main()
{
[|int|] i = 0;
}
}";
var expected = @"
class C
{
static void Main()
{
var i = 0;
}
}";
await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
}
[Fact]
[WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")]
public async Task TestMissingSelectionNotType()
{
var code = @"
class C
{
static void Main()
{
int [|i|] = 0;
}
}";
await TestMissingInRegularAndScriptAsync(code);
}
[Fact]
public async Task TestForeachInsideLocalDeclaration()
{
......
......@@ -295,7 +295,8 @@ static void Main(string[] args)
public async Task TestMissingInHiddenBlock1()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
@"#line default
class Program
{
void Main()
{
......@@ -312,7 +313,8 @@ void Main()
public async Task TestMissingInHiddenBlock2()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
@"#line default
class Program
{
void Main()
{
......@@ -360,7 +362,8 @@ void Main()
public async Task TestAvailableInNonHiddenBlock2()
{
await TestInRegularAndScriptAsync(
@"class Program
@"#line default
class Program
{
void Main()
{
......@@ -373,7 +376,8 @@ void Main()
Bar(x);
}
}",
@"class Program
@"#line default
class Program
{
void Main()
{
......
......@@ -114,9 +114,12 @@ void M(string arg1, int arg2)
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestWithSelection()
{
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((arg1: 1, 2)); }");
}
[Fact]
......@@ -163,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]
......
......@@ -263,6 +263,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
......@@ -886,6 +905,132 @@ C LocalFunction(C c)
}
#endregion
#region TestHidden
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestNextToHidden()
{
var testText = @"
#line default
class C
{
void M()
{
#line hidden
var a = b;
#line default
{|result:C [||]LocalFunction(C c)
{
return null;
}|}
}
}";
await TestAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestNextToHidden2()
{
var testText = @"
#line default
class C
{
void M()
{
#line hidden
var a = b;
#line default
{|result:C [||]LocalFunction(C c)
{
return null;
}|}
#line hidden
var a = b;
#line default
}
}";
await TestAsync<LocalFunctionStatementSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingHidden()
{
var testText = @"
#line default
class C
{
void M()
{
#line hidden
C LocalFunction(C c)
#line default
{
return null;
}[||]
}
}";
await TestMissingAsync<LocalFunctionStatementSyntax>(testText);
}
#endregion
#region Test predicate
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingPredicate()
{
var testText = @"
class C
{
void M()
{
N([||]2+3);
}
void N(int a)
{
}
}";
await TestMissingAsync<ArgumentSyntax>(testText, n => n.Parent is TupleExpressionSyntax);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestArgument()
{
var testText = @"
class C
{
void M()
{
N({|result:[||]2+3|});
}
void N(int a)
{
}
}";
await TestAsync<ArgumentSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestPredicate()
{
var testText = @"
class C
{
void M()
{
var a = ({|result:[||]2 + 3|}, 2 + 3);
}
}";
await TestAsync<ArgumentSyntax>(testText, n => n.Parent is TupleExpressionSyntax);
}
#endregion
#region Test arguments
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
......@@ -1265,5 +1410,67 @@ void Goo()
}
#endregion
#region Test Deep in expression
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestDeepIn()
{
var testText = @"
class C
{
void M()
{
N({|result:2+[||]3+4|});
}
void N(int a)
{
}
}";
await TestAsync<ArgumentSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestMissingDeepInSecondRow()
{
var testText = @"
class C
{
void M()
{
N(2
+[||]3+4);
}
void N(int a)
{
}
}";
await TestMissingAsync<ArgumentSyntax>(testText);
}
[Fact]
[WorkItem(35525, "https://github.com/dotnet/roslyn/issues/35525")]
public async Task TestDeepInExpression()
{
var testText = @"
class C
{
void M()
{
var b = ({|result:N(2[||])|}, 0);
}
int N(int a)
{
return a;
}
}";
await TestAsync<ArgumentSyntax>(testText, predicate: n => n.Parent is TupleExpressionSyntax);
}
#endregion
}
}
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.RefactoringHelpers
......@@ -33,42 +34,64 @@ public override void Dispose()
base.Dispose();
}
protected async Task TestAsync<TNode>(string text) where TNode : SyntaxNode
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
{
MarkupTestFile.GetSpans(text.NormalizeLineEndings(), out text, out IDictionary<string, ImmutableArray<TextSpan>> spans);
text = GetSelectionAndResultSpans(text, out var selection, out var result);
var resultNode = await GetNodeForSelection<TNode>(text, selection, predicate).ConfigureAwait(false);
if (spans.Count != 2 ||
!spans.TryGetValue(string.Empty, out var selection) || selection.Length != 1 ||
!spans.TryGetValue("result", out var result) || result.Length != 1)
{
throw new ArgumentException("Invalid test format: both `[|...|]` (selection) and `{|result:...|}` (retrieved node span) selections are required for a test.");
}
Assert.NotNull(resultNode);
Assert.Equal(result, resultNode.Span);
}
var document = fixture.UpdateDocument(text, SourceCodeKind.Regular);
var service = document.GetLanguageService<IRefactoringHelpersService>();
var resultNode = await service.TryGetSelectedNodeAsync<TNode>(document, selection.First(), CancellationToken.None).ConfigureAwait(false);
protected Task TestMissingAsync<TNode>(string text) where TNode : SyntaxNode => TestMissingAsync<TNode>(text, Functions<TNode>.True);
protected async Task TestMissingAsync<TNode>(string text, Func<TNode, bool> predicate) where TNode : SyntaxNode
{
text = GetSelectionSpan(text, out var selection);
Assert.NotNull(resultNode);
Assert.Equal(resultNode.Span, result.First());
var resultNode = await GetNodeForSelection<TNode>(text, selection, predicate).ConfigureAwait(false);
Assert.Null(resultNode);
}
protected async Task TestMissingAsync<TNode>(string text) where TNode : SyntaxNode
private static string GetSelectionSpan(string text, out TextSpan selection)
{
MarkupTestFile.GetSpans(text.NormalizeLineEndings(), out text, out IDictionary<string, ImmutableArray<TextSpan>> spans);
if (spans.Count != 1 ||
!spans.TryGetValue(string.Empty, out var selection) || selection.Length != 1)
!spans.TryGetValue(string.Empty, out var selections) || selections.Length != 1)
{
throw new ArgumentException("Invalid missing test format: only `[|...|]` (selection) should be present.");
}
var document = fixture.UpdateDocument(text, SourceCodeKind.Regular);
var service = document.GetLanguageService<IRefactoringHelpersService>();
selection = selections.Single();
return text;
}
var resultNode = await service.TryGetSelectedNodeAsync<TNode>(document, selection.First(), CancellationToken.None).ConfigureAwait(false);
private static string GetSelectionAndResultSpans(string text, out TextSpan selection, out TextSpan result)
{
MarkupTestFile.GetSpans(text.NormalizeLineEndings(), out text, out IDictionary<string, ImmutableArray<TextSpan>> spans);
Assert.Null(resultNode);
if (spans.Count != 2 ||
!spans.TryGetValue(string.Empty, out var selections) || selections.Length != 1 ||
!spans.TryGetValue("result", out var results) || results.Length != 1)
{
throw new ArgumentException("Invalid test format: both `[|...|]` (selection) and `{|result:...|}` (retrieved node span) selections are required for a test.");
}
selection = selections.Single();
result = results.Single();
return text;
}
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 relevantNodes = await document.GetRelevantNodesAsync<TNode>(selection, CancellationToken.None).ConfigureAwait(false);
return relevantNodes.FirstOrDefault(predicate);
}
}
}
......@@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.AddAwait
/// This refactoring complements the AddAwait fixer. It allows adding `await` and `await ... .ConfigureAwait(false)` even there is no compiler error to trigger the fixer.
/// </summary>
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.AddAwait), Shared]
internal partial class CSharpAddAwaitCodeRefactoringProvider : AbstractAddAwaitCodeRefactoringProvider<ExpressionSyntax, InvocationExpressionSyntax>
internal partial class CSharpAddAwaitCodeRefactoringProvider : AbstractAddAwaitCodeRefactoringProvider<InvocationExpressionSyntax>
{
[ImportingConstructor]
public CSharpAddAwaitCodeRefactoringProvider()
......
// 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;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServices;
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)
{
foreach (var extractedNode in base.ExtractNodesSimple(node, syntaxFacts))
{
yield return extractedNode;
}
// `var a = b;`
// -> `var a = b`;
if (node is LocalDeclarationStatementSyntax localDeclaration)
{
yield return localDeclaration.Declaration;
}
}
}
}
......@@ -39,7 +39,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return;
}
var localFunction = await context.TryGetSelectedNodeAsync<LocalFunctionStatementSyntax>().ConfigureAwait(false);
var localFunction = await context.TryGetRelevantNodeAsync<LocalFunctionStatementSyntax>().ConfigureAwait(false);
if (localFunction == default)
{
return;
......
......@@ -44,7 +44,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var variableDeclarator = await context.TryGetSelectedNodeAsync<VariableDeclaratorSyntax>().ConfigureAwait(false);
var variableDeclarator = await context.TryGetRelevantNodeAsync<VariableDeclaratorSyntax>().ConfigureAwait(false);
if (variableDeclarator == default)
{
return;
......
......@@ -33,7 +33,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var lambda = await context.TryGetSelectedNodeAsync<LambdaExpressionSyntax>().ConfigureAwait(false);
var lambda = await context.TryGetRelevantNodeAsync<LambdaExpressionSyntax>().ConfigureAwait(false);
if (lambda == null)
{
return;
......
......@@ -34,13 +34,13 @@ protected override async Task<SyntaxNode> GetSelectedNodeAsync(CodeRefactoringCo
// MemberDeclaration: member that can be declared in type (those are the ones we can pull up)
// VariableDeclaratorSyntax: for fields the MemberDeclaration can actually represent multiple declarations, e.g. `int a = 0, b = 1;`.
// ..Since the user might want to select & pull up only one of them (e.g. `int a = 0, [|b = 1|];` we also look for closest VariableDeclaratorSyntax.
var memberDecl = await context.TryGetSelectedNodeAsync<MemberDeclarationSyntax>().ConfigureAwait(false);
var memberDecl = await context.TryGetRelevantNodeAsync<MemberDeclarationSyntax>().ConfigureAwait(false);
if (memberDecl != default)
{
return memberDecl;
}
var varDecl = await context.TryGetSelectedNodeAsync<VariableDeclaratorSyntax>().ConfigureAwait(false);
var varDecl = await context.TryGetRelevantNodeAsync<VariableDeclaratorSyntax>().ConfigureAwait(false);
return varDecl;
}
}
......
......@@ -26,11 +26,6 @@ internal abstract class AbstractUseTypeCodeRefactoringProvider : CodeRefactoring
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
if (!textSpan.IsEmpty)
{
return;
}
if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
{
return;
......@@ -51,12 +46,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return;
}
if (declaredType.OverlapsHiddenPosition(cancellationToken))
{
return;
}
if (!declaredType.Span.IntersectsWith(textSpan.Start))
// only allow the refactoring is selection/location intersects with the type node
if (!declaredType.Span.IntersectsWith(textSpan))
{
return;
}
......@@ -91,25 +82,25 @@ private static async Task<SyntaxNode> GetDeclarationAsync(CodeRefactoringContext
// we also want to enable it when only the type node is selected because this refactoring changes the type. We still have to make sure
// we're only working on TypeNodes for in above-mentioned situations.
var declNode = await context.TryGetSelectedNodeAsync<DeclarationExpressionSyntax>().ConfigureAwait(false);
var declNode = await context.TryGetRelevantNodeAsync<DeclarationExpressionSyntax>().ConfigureAwait(false);
if (declNode != null)
{
return declNode;
}
var variableNode = await context.TryGetSelectedNodeAsync<VariableDeclarationSyntax>().ConfigureAwait(false);
var variableNode = await context.TryGetRelevantNodeAsync<VariableDeclarationSyntax>().ConfigureAwait(false);
if (variableNode != null)
{
return variableNode;
}
var foreachStatement = await context.TryGetSelectedNodeAsync<ForEachStatementSyntax>().ConfigureAwait(false);
var foreachStatement = await context.TryGetRelevantNodeAsync<ForEachStatementSyntax>().ConfigureAwait(false);
if (foreachStatement != null)
{
return foreachStatement;
}
var typeNode = await context.TryGetSelectedNodeAsync<TypeSyntax>().ConfigureAwait(false);
var typeNode = await context.TryGetRelevantNodeAsync<TypeSyntax>().ConfigureAwait(false);
var typeNodeParent = typeNode?.Parent;
if (typeNodeParent != null && typeNodeParent.IsKind(SyntaxKind.DeclarationExpression, SyntaxKind.VariableDeclaration, SyntaxKind.ForEachStatement))
{
......
......@@ -43,7 +43,7 @@ public CSharpConvertLinqQueryToForEachProvider()
/// Finds a node for the span and checks that it is either a QueryExpressionSyntax or a QueryExpressionSyntax argument within ArgumentSyntax.
/// </summary>
protected override Task<QueryExpressionSyntax> FindNodeToRefactorAsync(CodeRefactoringContext context)
=> context.TryGetSelectedNodeAsync<QueryExpressionSyntax>();
=> context.TryGetRelevantNodeAsync<QueryExpressionSyntax>();
private sealed class Converter
{
......
......@@ -21,9 +21,8 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings.AddAwait
/// Or:
/// var x = await GetAsync().ConfigureAwait(false);
/// </summary>
internal abstract class AbstractAddAwaitCodeRefactoringProvider<TExpressionSyntax, TInvocationExpressionSyntax> : CodeRefactoringProvider
where TExpressionSyntax : SyntaxNode
where TInvocationExpressionSyntax : TExpressionSyntax
internal abstract class AbstractAddAwaitCodeRefactoringProvider<TInvocationExpressionSyntax> : CodeRefactoringProvider
where TInvocationExpressionSyntax : SyntaxNode
{
protected abstract string GetTitle();
protected abstract string GetTitleWithConfigureAwait();
......@@ -31,27 +30,19 @@ internal abstract class AbstractAddAwaitCodeRefactoringProvider<TExpressionSynta
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.FindTokenOnLeftOfPosition(textSpan.Start);
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var awaitable = GetAwaitableExpression(token, model, syntaxFacts);
if (awaitable == null)
{
return;
}
if (awaitable.OverlapsHiddenPosition(cancellationToken))
var awaitable = await context.TryGetRelevantNodeAsync<TInvocationExpressionSyntax>().ConfigureAwait(false);
if (awaitable == null || !IsValidAwaitableExpression(awaitable, model, syntaxFacts))
{
return;
}
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindTokenOnLeftOfPosition(textSpan.Start);
context.RegisterRefactoring(
new MyCodeAction(
GetTitle(),
......@@ -64,41 +55,35 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex
c => AddAwaitAsync(document, awaitable, withConfigureAwait: true, c)));
}
private TExpressionSyntax GetAwaitableExpression(SyntaxToken token, SemanticModel model, ISyntaxFactsService syntaxFacts)
private bool IsValidAwaitableExpression(SyntaxNode invocation, SemanticModel model, ISyntaxFactsService syntaxFacts)
{
var invocation = token.GetAncestor<TInvocationExpressionSyntax>();
if (invocation is null)
{
return null;
}
if (syntaxFacts.IsExpressionOfInvocationExpression(invocation.Parent))
{
// Do not offer fix on `MethodAsync()$$.ConfigureAwait()`
// Do offer fix on `MethodAsync()$$.Invalid()`
if (!model.GetTypeInfo(invocation.Parent.Parent).Type.IsErrorType())
{
return null;
return false;
}
}
if (syntaxFacts.IsExpressionOfAwaitExpression(invocation))
{
return null;
return false;
}
var type = model.GetTypeInfo(invocation).Type;
if (type?.IsAwaitableNonDynamic(model, token.SpanStart) == true)
if (type?.IsAwaitableNonDynamic(model, invocation.SpanStart) == true)
{
return invocation;
return true;
}
return null;
return false;
}
private async Task<Document> AddAwaitAsync(
Document document,
TExpressionSyntax invocation,
TInvocationExpressionSyntax invocation,
bool withConfigureAwait,
CancellationToken cancellationToken)
{
......
// 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;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
......@@ -25,13 +29,32 @@ internal static class CodeRefactoringContextExtensions
}
}
internal static Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(this CodeRefactoringContext context)
internal static Task<TSyntaxNode> TryGetRelevantNodeAsync<TSyntaxNode>(this CodeRefactoringContext context)
where TSyntaxNode : SyntaxNode
=> TryGetRelevantNodeAsync<TSyntaxNode>(context.Document, context.Span, context.CancellationToken);
internal static Task<ImmutableArray<TSyntaxNode>> GetRelevantNodesAsync<TSyntaxNode>(this CodeRefactoringContext context)
where TSyntaxNode : SyntaxNode
=> GetRelevantNodesAsync<TSyntaxNode>(context.Document, context.Span, context.CancellationToken);
internal static async Task<TSyntaxNode> TryGetRelevantNodeAsync<TSyntaxNode>(
this Document document,
TextSpan span,
CancellationToken cancellationToken)
where TSyntaxNode : SyntaxNode
{
var document = context.Document;
var helpers = document.GetLanguageService<IRefactoringHelpersService>();
var potentialNodes = await GetRelevantNodesAsync<TSyntaxNode>(document, span, cancellationToken).ConfigureAwait(false);
return potentialNodes.FirstOrDefault();
}
return helpers.TryGetSelectedNodeAsync<TSyntaxNode>(document, context.Span, context.CancellationToken);
internal static async Task<ImmutableArray<TSyntaxNode>> GetRelevantNodesAsync<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);
return potentialNodes;
}
}
}
// 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;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
......@@ -11,8 +13,7 @@ internal interface IRefactoringHelpersService : ILanguageService
{
/// <summary>
/// <para>
/// Returns an instance of <typeparamref name="TSyntaxNode"/> for refactoring given specified selection in document or null
/// if no such instance exists.
/// Returns an array of <typeparamref name="TSyntaxNode"/> instances for refactoring given specified selection in document.
/// </para>
/// <para>
/// A <typeparamref name="TSyntaxNode"/> instance is returned if:
......@@ -21,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>
......@@ -33,6 +35,6 @@ internal interface IRefactoringHelpersService : ILanguageService
/// of tokens gracefully. Over-selection containing leading comments is also handled correctly.
/// </para>
/// </summary>
Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode;
Task<ImmutableArray<TSyntaxNode>> GetRelevantNodesAsync<TSyntaxNode>(Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode;
}
}
......@@ -71,9 +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 helper.TryGetSelectedNodeAsync<TAnonymousObjectCreationExpressionSyntax>(
document, span, cancellationToken).ConfigureAwait(false);
var anonymousObject = await document.TryGetRelevantNodeAsync<TAnonymousObjectCreationExpressionSyntax>(
span, cancellationToken).ConfigureAwait(false);
if (anonymousObject == null)
{
return default;
......
......@@ -67,7 +67,7 @@ internal bool IsValidAutoProperty(SyntaxNode property, IPropertySymbol propertyS
private async Task<SyntaxNode> GetPropertyAsync(CodeRefactoringContext context)
{
var containingProperty = await context.TryGetSelectedNodeAsync<TPropertyDeclarationNode>().ConfigureAwait(false);
var containingProperty = await context.TryGetRelevantNodeAsync<TPropertyDeclarationNode>().ConfigureAwait(false);
if (!(containingProperty?.Parent is TTypeDeclarationNode))
{
return null;
......
......@@ -55,7 +55,7 @@ protected SyntaxAnnotation CreateWarningAnnotation()
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
var foreachStatement = await context.TryGetSelectedNodeAsync<TForEachStatement>().ConfigureAwait(false);
var foreachStatement = await context.TryGetRelevantNodeAsync<TForEachStatement>().ConfigureAwait(false);
if (foreachStatement == null || !IsValid(foreachStatement))
{
return;
......
......@@ -49,7 +49,7 @@ internal abstract class AbstractConvertForToForEachCodeRefactoringProvider<
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
var forStatement = await context.TryGetSelectedNodeAsync<TForStatementSyntax>().ConfigureAwait(false);
var forStatement = await context.TryGetRelevantNodeAsync<TForStatementSyntax>().ConfigureAwait(false);
if (forStatement == null)
{
return;
......
......@@ -66,7 +66,7 @@ public async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
var (document, textSpan, cancellationToken) = context;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var ifStatement = await context.TryGetSelectedNodeAsync<TIfStatementSyntax>().ConfigureAwait(false);
var ifStatement = await context.TryGetRelevantNodeAsync<TIfStatementSyntax>().ConfigureAwait(false);
if (ifStatement == null || ifStatement.ContainsDiagnostics)
{
return;
......
......@@ -118,7 +118,7 @@ internal virtual async Task<SyntaxToken> GetNumericTokenAsync(CodeRefactoringCon
{
var syntaxFacts = context.Document.GetLanguageService<ISyntaxFactsService>();
var literalNode = await context.TryGetSelectedNodeAsync<TNumericLiteralExpression>().ConfigureAwait(false);
var literalNode = await context.TryGetRelevantNodeAsync<TNumericLiteralExpression>().ConfigureAwait(false);
var numericLiteralExpressionNode = syntaxFacts.IsNumericLiteralExpression(literalNode)
? literalNode
: null;
......
......@@ -42,7 +42,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var parameterNode = await context.TryGetSelectedNodeAsync<TParameterSyntax>().ConfigureAwait(false);
var parameterNode = await context.TryGetRelevantNodeAsync<TParameterSyntax>().ConfigureAwait(false);
if (parameterNode == null)
{
return;
......
......@@ -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 refactoringHelperService.TryGetSelectedNodeAsync<TLocalDeclarationSyntax>(document, selection, cancellationToken).ConfigureAwait(false);
var declarationSyntax = await document.TryGetRelevantNodeAsync<TLocalDeclarationSyntax>(selection, cancellationToken).ConfigureAwait(false);
if (declarationSyntax is null || !CanRefactorToContainBlockStatements(declarationSyntax.Parent))
{
return default;
......
......@@ -55,12 +55,12 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex
protected async Task<TExpressionStatementSyntax> GetExpressionStatementAsync(CodeRefactoringContext context)
{
var expressionStatement = await context.TryGetSelectedNodeAsync<TExpressionStatementSyntax>().ConfigureAwait(false);
var expressionStatement = await context.TryGetRelevantNodeAsync<TExpressionStatementSyntax>().ConfigureAwait(false);
if (expressionStatement == null)
{
// If an expression-statement wasn't selected, see if they're selecting
// an expression belonging to an expression-statement instead.
var expression = await context.TryGetSelectedNodeAsync<TExpressionSyntax>().ConfigureAwait(false);
var expression = await context.TryGetRelevantNodeAsync<TExpressionSyntax>().ConfigureAwait(false);
expressionStatement = expression?.Parent as TExpressionStatementSyntax;
}
......
......@@ -40,9 +40,8 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex
var (document, textSpan, cancellationToken) = context;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var ifNode = await context.TryGetSelectedNodeAsync<TIfStatementSyntax>().ConfigureAwait(false);
if (ifNode == null || ifNode.OverlapsHiddenPosition(cancellationToken))
var ifNode = await context.TryGetRelevantNodeAsync<TIfStatementSyntax>().ConfigureAwait(false);
if (ifNode == null)
{
return;
}
......
......@@ -23,7 +23,7 @@ public AbstractMoveDeclarationNearReferenceCodeRefactoringProvider()
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
var statement = await context.TryGetSelectedNodeAsync<TLocalDeclaration>().ConfigureAwait(false);
var statement = await context.TryGetRelevantNodeAsync<TLocalDeclaration>().ConfigureAwait(false);
if (statement == null)
{
return;
......
......@@ -43,29 +43,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return default;
}
if (span.Length > 0)
{
return default;
}
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var position = span.Start;
var token = root.FindToken(position);
if (token.Span.Start == position &&
IsCloseParenOrComma(token))
{
token = token.GetPreviousToken();
if (token.Span.End != position)
{
return default;
}
}
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var argument = root.FindNode(token.Span)
.GetAncestorsOrThis<TArgumentSyntax>()
.FirstOrDefault(node => syntaxFacts.IsTupleExpression(node.Parent));
var potentialArguments = await document.GetRelevantNodesAsync<TArgumentSyntax>(span, cancellationToken).ConfigureAwait(false);
var argument = potentialArguments.FirstOrDefault(n => n?.Parent is TTupleExpressionSyntax);
if (argument == null || !syntaxFacts.IsSimpleArgument(argument))
{
return default;
......@@ -94,6 +74,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return default;
}
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
return (root, argument, element.Name);
}
......
......@@ -14,7 +14,7 @@ internal abstract class AbstractReplaceMethodWithPropertyService<TMethodDeclarat
{
public async Task<SyntaxNode> GetMethodDeclarationAsync(CodeRefactoringContext context)
{
var property = await context.TryGetSelectedNodeAsync<TMethodDeclarationSyntax>().ConfigureAwait(false);
var property = await context.TryGetRelevantNodeAsync<TMethodDeclarationSyntax>().ConfigureAwait(false);
return property;
}
......
......@@ -31,7 +31,7 @@ internal abstract class AbstractReplacePropertyWithMethodsService<TIdentifierNam
protected abstract TExpressionSyntax UnwrapCompoundAssignment(SyntaxNode compoundAssignment, TExpressionSyntax readExpression);
public async Task<SyntaxNode> GetPropertyDeclarationAsync(CodeRefactoringContext context)
{
var property = await context.TryGetSelectedNodeAsync<TPropertySyntax>().ConfigureAwait(false);
var property = await context.TryGetRelevantNodeAsync<TPropertySyntax>().ConfigureAwait(false);
return property;
}
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseNamedArguments
{
......@@ -29,35 +30,7 @@ protected abstract class Analyzer<TBaseArgumentSyntax, TSimpleArgumentSyntax, TA
{
var (document, textSpan, cancellationToken) = context;
var argument = await context.TryGetSelectedNodeAsync<TSimpleArgumentSyntax>().ConfigureAwait(false);
if (argument == null && textSpan.IsEmpty)
{
// For arguments we want to enable cursor anywhere in the expressions (even deep within) as long as
// it is on the first line of said expression. Since the `TryGetSelectedNodeAsync` doesn't do such
// traversing up & checking line numbers -> need to do it manually.
// The rationale for only first line is that arg. expressions can be arbitrarily large.
// see: https://github.com/dotnet/roslyn/issues/18848
var position = textSpan.Start;
var token = root.FindToken(position);
argument = root.FindNode(token.Span).FirstAncestorOrSelfUntil<TBaseArgumentSyntax>(node => node is TArgumentListSyntax) as TSimpleArgumentSyntax;
if (argument == null)
{
return;
}
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var argumentStartLine = sourceText.Lines.GetLineFromPosition(argument.Span.Start).LineNumber;
var caretLine = sourceText.Lines.GetLineFromPosition(position).LineNumber;
if (argumentStartLine != caretLine)
{
return;
}
}
var argument = await context.TryGetRelevantNodeAsync<TBaseArgumentSyntax>().ConfigureAwait(false) as TSimpleArgumentSyntax;
if (argument == null)
{
return;
......
......@@ -8,7 +8,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.AddAwait
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.AddAwait), [Shared]>
Friend Class VisualBasicAddAwaitCodeRefactoringProvider
Inherits AbstractAddAwaitCodeRefactoringProvider(Of ExpressionSyntax, InvocationExpressionSyntax)
Inherits AbstractAddAwaitCodeRefactoringProvider(Of InvocationExpressionSyntax)
<ImportingConstructor>
Public Sub New()
......
......@@ -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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册