未验证 提交 500ac558 编写于 作者: D dotnet-automerge-bot 提交者: GitHub

Merge pull request #31275 from dotnet/merges/master-to-master-vs-deps

Merge master to master-vs-deps
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.NameTupleElement;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.NameTupleElement
{
[Trait(Traits.Feature, Traits.Features.CodeActionsNameTupleElement)]
public class NameTupleElementTests : AbstractCSharpCodeActionTest
{
protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
=> new CSharpNameTupleElementCodeRefactoringProvider();
[Fact]
public async Task TestInCall_FirstElement()
{
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]
public async Task TestInCall_Deep()
{
await TestInRegularAndScript1Async(
@"class C
{
void M((int arg1, int arg2) x) => M((Method([||]1), 2));
int Method(int x) => throw null;
}",
@"class C
{
void M((int arg1, int arg2) x) => M((arg1: Method(1), 2));
int Method(int x) => throw null;
}");
}
[Fact]
public async Task TestInCall_Deep2()
{
await TestInRegularAndScript1Async(
@"class C
{
void M((int arg1, int arg2) x) => M((1, Method(1[||], 2)));
int Method((int arg3, int arg4) x) => throw null;
}",
@"class C
{
void M((int arg1, int arg2) x) => M((1, arg2: Method(1, 2)));
int Method((int arg3, int arg4) x) => throw null;
}");
}
[Fact]
public async Task TestInCall_Deep3()
{
await TestInRegularAndScript1Async(
@"class C
{
void M((int arg1, int arg2) x) => M((1, Method[||](1, 2)));
int Method((int arg3, int arg4) x) => throw null;
}",
@"class C
{
void M((int arg1, int arg2) x) => M((1, arg2: Method(1, 2)));
int Method((int arg3, int arg4) x) => throw null;
}");
}
[Fact]
public async Task TestInCall_FirstElement_EscapedNamed()
{
await TestInRegularAndScript1Async(
@"class C { void M((int @int, int arg2) x) => M(([||]1, 2)); }",
@"class C { void M((int @int, int arg2) x) => M((@int: 1, 2)); }");
}
[Fact]
public async Task TestInCall_FirstElement_AlreadyNamed()
{
await TestMissingAsync(@"class C { void M((int arg1, int arg2) x) => M(([||]arg1: 1, 2)); }");
}
[Fact]
public async Task TestUntypedTuple()
{
await TestMissingAsync(
@"class C
{
void M()
{
_ = ([||]null, 2);
}
}");
}
[Fact]
public async Task TestInvocationArgument()
{
await TestMissingAsync(
@"class C
{
void M(string arg1, int arg2)
{
M([||]null, 2);
}
}");
}
[Fact]
public async Task TestWithSelection()
{
await TestMissingAsync(@"class C { void M((int arg1, int arg2) x) => M(([|1|], 2)); }");
}
[Fact]
public async Task TestWithConversion()
{
await TestMissingAsync(
@"class C
{
void M(C x) => M(([|1|], 2));
public static implicit operator C((int arg1, int arg2) x) => throw null;
}");
}
[Fact]
public async Task TestInCall_FirstElement_WithTrivia()
{
await TestInRegularAndScript1Async(
@"class C { void M((int arg1, int arg2) x) => M((/*before*/ [||]1 /*after*/, 2)); }",
@"class C { void M((int arg1, int arg2) x) => M((/*before*/ arg1: 1 /*after*/, 2)); }");
}
[Fact]
public async Task TestInCall_FirstElement_Nested()
{
await TestInRegularAndScript1Async(
@"class C
{
int M((int arg1, int arg2) x)
=> M((M(([||]1, 2)), 3));
}",
@"class C
{
int M((int arg1, int arg2) x)
=> M((M((arg1: 1, 2)), 3));
}");
}
[Fact]
public async Task TestInCall_FirstComma()
{
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]
public async Task TestInCall_FirstComma2()
{
await TestMissingAsync(@"class C { void M((int arg1, int arg2) x) => M((1,[||] 2)); }");
}
[Fact]
public async Task TestInCall_SecondElement()
{
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]
public async Task TestInCall_CloseParen()
{
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]
public async Task TestUnnamedTuple()
{
await TestMissingAsync(@"class C { void M((int, int) x) => M(([||]1, 2)); }");
}
[Fact]
public async Task TestArrowReturnedTuple()
{
await TestInRegularAndScript1Async(
@"class C { (int arg1, int arg2, int arg3) M() => ([||]1, 2); }",
@"class C { (int arg1, int arg2, int arg3) M() => (arg1: 1, 2); }");
}
[Fact]
public async Task TestArrowReturnedTuple_LocalFunction()
{
await TestInRegularAndScript1Async(
@"class C
{
void M()
{
(int arg1, int arg2, int arg3) local() => ([||]1, 2);
}
}",
@"class C
{
void M()
{
(int arg1, int arg2, int arg3) local() => (arg1: 1, 2);
}
}");
}
[Fact]
public async Task TestReturnedTuple()
{
await TestInRegularAndScript1Async(
@"class C { (int arg1, int arg2, int arg3) M() { return ([||]1, 2); } }",
@"class C { (int arg1, int arg2, int arg3) M() { return (arg1: 1, 2); } }");
}
[Fact]
public async Task TestReturnedTuple_LongerTuple()
{
await TestMissingAsync(
@"class C { (int arg1, int arg2) M() => (1, 2, [||]3); }");
}
}
}
' 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 Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Imports Microsoft.CodeAnalysis.VisualBasic.NameTupleElement
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.NameTupleElement
<Trait(Traits.Feature, Traits.Features.CodeActionsNameTupleElement)>
Public Class NameTupleElementTests
Inherits AbstractVisualBasicCodeActionTest
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider
Return New VisualBasicNameTupleElementCodeRefactoringProvider()
End Function
<Fact>
Public Async Function TestInCall_FirstElement() As Task
Await TestInRegularAndScript1Async(
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M(([||]1, 2))
End Sub
End Class",
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((arg1:=1, 2))
End Sub
End Class")
End Function
<Fact>
Public Async Function TestInCall_FirstComma() As Task
Await TestInRegularAndScript1Async(
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1[||], 2))
End Sub
End Class",
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((arg1:=1, 2))
End Sub
End Class")
End Function
<Fact>
Public Async Function TestInCall_SecondElement() As Task
Await TestInRegularAndScript1Async(
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1, [||]2))
End Sub
End Class",
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1, arg2:=2))
End Sub
End Class")
End Function
<Fact>
Public Async Function TestInCall_CloseParen() As Task
Await TestInRegularAndScript1Async(
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1, 2[||]))
End Sub
End Class",
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1, arg2:=2))
End Sub
End Class")
End Function
<Fact>
Public Async Function TestInCall_LongerTuple() As Task
Await TestMissingAsync(
"Class C
Sub M(x As (arg1 As Integer, arg2 As Integer))
M((1, 2, [||]3))
End Sub
End Class")
End Function
<Fact>
Public Async Function TestUnnamedTuple() As Task
Await TestMissingAsync(
"Class C
Sub M(x As (Integer, Integer))
M(([||]1, 2))
End Sub
End Class")
End Function
End Class
End Namespace
// 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.Composition;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.NameTupleElement;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.NameTupleElement
{
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.IntroduceVariable)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpNameTupleElementCodeRefactoringProvider)), Shared]
internal class CSharpNameTupleElementCodeRefactoringProvider : AbstractNameTupleElementCodeRefactoringProvider<ArgumentSyntax, TupleExpressionSyntax>
{
protected override bool IsCloseParenOrComma(SyntaxToken token)
=> token.IsKind(SyntaxKind.CloseParenToken, SyntaxKind.CommaToken);
protected override ArgumentSyntax WithName(ArgumentSyntax argument, string argumentName)
=> argument.WithNameColon(SyntaxFactory.NameColon(argumentName.ToIdentifierName()));
}
}
......@@ -341,6 +341,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add tuple element name &apos;{0}&apos;.
/// </summary>
internal static string Add_tuple_element_name_0 {
get {
return ResourceManager.GetString("Add_tuple_element_name_0", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding &apos;{0}&apos; around an active statement will prevent the debug session from continuing..
/// </summary>
......
......@@ -1154,6 +1154,9 @@ This version used in: {2}</value>
<data name="Add_argument_name_0" xml:space="preserve">
<value>Add argument name '{0}'</value>
</data>
<data name="Add_tuple_element_name_0" xml:space="preserve">
<value>Add tuple element name '{0}'</value>
</data>
<data name="Take_0" xml:space="preserve">
<value>Take '{0}'</value>
</data>
......
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.NameTupleElement
{
abstract class AbstractNameTupleElementCodeRefactoringProvider<TArgumentSyntax, TTupleExpressionSyntax> : CodeRefactoringProvider
where TArgumentSyntax : SyntaxNode
where TTupleExpressionSyntax : SyntaxNode
{
protected abstract bool IsCloseParenOrComma(SyntaxToken token);
protected abstract TArgumentSyntax WithName(TArgumentSyntax argument, string argumentName);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var span = context.Span;
var cancellationToken = context.CancellationToken;
var document = context.Document;
var (_, _, elementName) = await TryGetArgumentInfo(document, span, cancellationToken);
if (elementName == null)
{
return;
}
context.RegisterRefactoring(
new MyCodeAction(
string.Format(FeaturesResources.Add_tuple_element_name_0, elementName),
c => AddNamedElementAsync(document, span, cancellationToken)));
}
private async Task<(SyntaxNode root, TArgumentSyntax argument, string argumentName)> TryGetArgumentInfo(
Document document, TextSpan span, CancellationToken cancellationToken)
{
if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles)
{
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));
if (argument == null || !syntaxFacts.IsSimpleArgument(argument))
{
return default;
}
var tuple = (TTupleExpressionSyntax)argument.Parent;
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var tupleType = semanticModel.GetTypeInfo(tuple, cancellationToken).ConvertedType as INamedTypeSymbol;
if (tupleType == null)
{
return default;
}
syntaxFacts.GetPartsOfTupleExpression<TArgumentSyntax>(tuple, out _, out var arguments, out _);
var argumentIndex = arguments.IndexOf(argument);
var elements = tupleType.TupleElements;
if (elements.IsDefaultOrEmpty || argumentIndex >= elements.Length)
{
return default;
}
var element = elements[argumentIndex];
if (element.Equals(element.CorrespondingTupleField))
{
return default;
}
return (root, argument, element.Name);
}
private async Task<Document> AddNamedElementAsync(Document document, TextSpan span, CancellationToken cancellationToken)
{
var (root, argument, elementName) = await TryGetArgumentInfo(document, span, cancellationToken);
var newArgument = WithName(argument, elementName).WithTriviaFrom(argument);
var newRoot = root.ReplaceNode(argument, newArgument);
return document.WithSyntaxRoot(newRoot);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
......@@ -27,6 +27,11 @@
<target state="translated">Akce nemůžou zůstat prázdné.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Aktionen dürfen nicht leer sein.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Las acciones no pueden estar vacías.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Les actions ne peuvent pas être vides.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Il campo Azioni non può essere vuoto.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">アクションは空にできません。</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">작업은 비워 둘 수 없습니다.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Akcje nie mogą być puste.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Ações não podem ficar vazias.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Действия не могут быть пустыми.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">Eylemler boş olamaz.</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">操作不能为空。</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
......@@ -27,6 +27,11 @@
<target state="translated">動作不可為空。</target>
<note />
</trans-unit>
<trans-unit id="Add_tuple_element_name_0">
<source>Add tuple element name '{0}'</source>
<target state="new">Add tuple element name '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Adding_method_with_explicit_interface_specifier_will_prevernt_the_debug_session_from_continuing">
<source>Adding a method with an explicit interface specifier will prevent the debug session from continuing.</source>
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
......
' 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.NameTupleElement
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.NameTupleElement
<ExtensionOrder(After:=PredefinedCodeRefactoringProviderNames.IntroduceVariable)>
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=NameOf(VisualBasicNameTupleElementCodeRefactoringProvider)), [Shared]>
Friend Class VisualBasicNameTupleElementCodeRefactoringProvider
Inherits AbstractNameTupleElementCodeRefactoringProvider(Of SimpleArgumentSyntax, TupleExpressionSyntax)
Protected Overrides Function WithName(argument As SimpleArgumentSyntax, name As String) As SimpleArgumentSyntax
Return argument.WithNameColonEquals(SyntaxFactory.NameColonEquals(name.ToIdentifierName()))
End Function
Protected Overrides Function IsCloseParenOrComma(token As SyntaxToken) As Boolean
Return token.IsKind(SyntaxKind.CloseParenToken, SyntaxKind.CommaToken)
End Function
End Class
End Namespace
......@@ -146,6 +146,7 @@ public static class Features
public const string CodeActionsUseLocalFunction = "CodeActions.UseLocalFunction";
public const string CodeActionsUseNullPropagation = "CodeActions.UseNullPropagation";
public const string CodeActionsUseNamedArguments = "CodeActions.UseNamedArguments";
public const string CodeActionsNameTupleElement = "CodeActions.NameTupleElement";
public const string CodeActionsUseObjectInitializer = "CodeActions.UseObjectInitializer";
public const string CodeActionsUseRangeOperator = "CodeActions.UseRangeOperator";
public const string CodeActionsUseThrowExpression = "CodeActions.UseThrowExpression";
......
......@@ -13,7 +13,8 @@
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal partial class VisualStudioMetadataReference
// TODO: This class is now an empty container just to hold onto the nested type. Renaming that is an invasive change that will be it's own commit.
internal static class VisualStudioMetadataReference
{
/// <summary>
/// Represents a metadata reference corresponding to a specific version of a file.
......
// 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.Diagnostics;
using System.IO;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
internal sealed partial class VisualStudioMetadataReference : IDisposable
{
private readonly VisualStudioMetadataReferenceManager _provider;
private readonly MetadataReferenceProperties _properties;
private readonly FileChangeTracker _fileChangeTracker;
private Snapshot _currentSnapshot;
public event EventHandler UpdatedOnDisk;
public VisualStudioMetadataReference(
VisualStudioMetadataReferenceManager provider,
string filePath,
MetadataReferenceProperties properties)
{
Contract.ThrowIfTrue(properties.Kind != MetadataImageKind.Assembly);
_provider = provider;
_properties = properties;
// We don't track changes to netmodules linked to the assembly.
// Any legitimate change in a linked module will cause the assembly to change as well.
_fileChangeTracker = new FileChangeTracker(provider.FileChangeService, filePath);
_fileChangeTracker.UpdatedOnDisk += OnUpdatedOnDisk;
_fileChangeTracker.StartFileChangeListeningAsync();
}
public string FilePath
{
get { return _fileChangeTracker.FilePath; }
}
public MetadataReferenceProperties Properties
{
get { return _properties; }
}
public PortableExecutableReference CurrentSnapshot
{
get
{
if (_currentSnapshot == null)
{
UpdateSnapshot();
}
return _currentSnapshot;
}
}
private void OnUpdatedOnDisk(object sender, EventArgs e)
{
UpdatedOnDisk?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
_fileChangeTracker.Dispose();
_fileChangeTracker.UpdatedOnDisk -= OnUpdatedOnDisk;
}
public void UpdateSnapshot()
{
_currentSnapshot = new Snapshot(_provider, Properties, this.FilePath, _fileChangeTracker);
}
private string GetDebuggerDisplay()
{
return Path.GetFileName(this.FilePath);
}
}
}
......@@ -86,11 +86,6 @@ public PortableExecutableReference CreateMetadataReferenceSnapshot(string filePa
return new VisualStudioMetadataReference.Snapshot(this, properties, filePath, fileChangeTrackerOpt: null);
}
public VisualStudioMetadataReference CreateMetadataReference(string filePath, MetadataReferenceProperties properties)
{
return new VisualStudioMetadataReference(this, filePath, properties);
}
public void ClearCache()
{
_metadataCache.ClearCache();
......
......@@ -63,7 +63,7 @@ internal sealed class VisualStudioProject
/// The file watching tokens for the documents in this project. We get the tokens even when we're in a batch, so the files here
/// may not be in the actual workspace yet.
/// </summary>
private readonly Dictionary<DocumentId, FileChangeWatcher.IFileWatchingToken> _fileWatchingTokens = new Dictionary<DocumentId, FileChangeWatcher.IFileWatchingToken>();
private readonly Dictionary<DocumentId, FileChangeWatcher.IFileWatchingToken> _documentFileWatchingTokens = new Dictionary<DocumentId, FileChangeWatcher.IFileWatchingToken>();
/// <summary>
/// A file change context used to watch source files and additional files for this project. It's automatically set to watch the user's project
......@@ -76,6 +76,19 @@ internal sealed class VisualStudioProject
/// </summary>
private readonly FileChangeWatcher.IContext _fileReferenceChangeContext;
/// <summary>
/// File watching tokens from <see cref="_fileReferenceChangeContext"/> that are watching metadata references. These are only created once we are actually applying a batch because
/// we don't determine until the batch is applied if the file reference will actually be a file reference or it'll be a converted project reference.
/// </summary>
private readonly Dictionary<PortableExecutableReference, FileChangeWatcher.IFileWatchingToken> _metadataReferenceFileWatchingTokens = new Dictionary<PortableExecutableReference, FileChangeWatcher.IFileWatchingToken>();
/// <summary>
/// <see cref="CancellationTokenSource"/>s for in-flight refreshing of metadata references. When we see a file change, we wait a bit before trying to actually
/// update the workspace. We need cancellation tokens for those so we can cancel them either when a flurry of events come in (so we only do the delay after the last
/// modification), or when we know the project is going away entirely. We don't
/// </summary>
private readonly Dictionary<string, CancellationTokenSource> _metadataReferenceRefreshCancellationTokenSources = new Dictionary<string, CancellationTokenSource>();
/// <summary>
/// track whether we have been subscribed to <see cref="IDynamicFileInfoProvider.Updated"/> event
/// </summary>
......@@ -127,17 +140,17 @@ internal sealed class VisualStudioProject
_documentFileChangeContext = workspace.FileChangeWatcher.CreateContext();
}
_documentFileChangeContext.FileChanged += FileChangeContext_FileChanged;
_documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged;
// TODO: set this to watch the NuGet directory or the reference assemblies directory; since those change rarely and most references
// will come from them, we can avoid creating a bunch of explicit file watchers.
_fileReferenceChangeContext = workspace.FileChangeWatcher.CreateContext();
_fileReferenceChangeContext.FileChanged += FileReferenceChangeContext_FileChanged;
_sourceFiles = new BatchingDocumentCollection(this, (s, d) => s.ContainsDocument(d), (w, d) => w.OnDocumentAdded(d), (w, documentId) => w.OnDocumentRemoved(documentId));
_additionalFiles = new BatchingDocumentCollection(this, (s, d) => s.ContainsAdditionalDocument(d), (w, d) => w.OnAdditionalDocumentAdded(d), (w, documentId) => w.OnAdditionalDocumentRemoved(documentId));
}
private void ChangeProjectProperty<T>(ref T field, T newValue, Func<Solution, Solution> withNewValue, Action<Workspace> changeValue)
{
lock (_gate)
......@@ -336,7 +349,8 @@ private void OnBatchScopeDisposed()
(s, documents) => solution.AddDocuments(documents),
(s, id) =>
{
// Clear any document-specific data now (like open file trackers, etc.)
// Clear any document-specific data now (like open file trackers, etc.). If we called OnRemoveDocument directly this is
// called, but since we're doing this in one large batch we need to do it now.
_workspace.ClearDocumentData(id);
return s.RemoveDocument(id);
});
......@@ -356,7 +370,8 @@ private void OnBatchScopeDisposed()
},
(s, id) =>
{
// Clear any document-specific data now (like open file trackers, etc.)
// Clear any document-specific data now (like open file trackers, etc.). If we called OnRemoveDocument directly this is
// called, but since we're doing this in one large batch we need to do it now.
_workspace.ClearDocumentData(id);
return s.RemoveAdditionalDocument(id);
});
......@@ -377,7 +392,9 @@ private void OnBatchScopeDisposed()
}
else
{
metadataReferencesCreated.Add(_workspace.CreateMetadataReference(metadataReferenceAddedInBatch.path, metadataReferenceAddedInBatch.properties));
var metadataReference = _workspace.CreatePortableExecutableReference(metadataReferenceAddedInBatch.path, metadataReferenceAddedInBatch.properties);
metadataReferencesCreated.Add(metadataReference);
_metadataReferenceFileWatchingTokens.Add(metadataReference, _fileReferenceChangeContext.EnqueueWatchingFile(metadataReference.FilePath));
}
}
......@@ -402,6 +419,10 @@ private void OnBatchScopeDisposed()
var metadataReference = _workspace.CurrentSolution.GetProject(Id).MetadataReferences.Cast<PortableExecutableReference>()
.Single(m => m.FilePath == metadataReferenceRemovedInBatch.path && m.Properties == metadataReferenceRemovedInBatch.properties);
_fileReferenceChangeContext.StopWatchingFile(_metadataReferenceFileWatchingTokens[metadataReference]);
_metadataReferenceFileWatchingTokens.Remove(metadataReference);
CancelOutstandingMetadataReferenceRefreshForFile_NoLock(metadataReference.FilePath);
solution = solution.RemoveMetadataReference(Id, metadataReference);
}
}
......@@ -648,12 +669,81 @@ public void RemoveAnalyzerReference(string fullPath)
#endregion
private void FileChangeContext_FileChanged(object sender, string fullFilePath)
private void DocumentFileChangeContext_FileChanged(object sender, string fullFilePath)
{
_sourceFiles.ProcessFileChange(fullFilePath);
_additionalFiles.ProcessFileChange(fullFilePath);
}
#region Metadata Reference Refreshing
private void FileReferenceChangeContext_FileChanged(object sender, string fullFilePath)
{
lock (_gate)
{
CancelOutstandingMetadataReferenceRefreshForFile_NoLock(fullFilePath);
var cancellationTokenSource = new CancellationTokenSource();
_metadataReferenceRefreshCancellationTokenSources.Add(fullFilePath, cancellationTokenSource);
Task.Delay(TimeSpan.FromSeconds(5), cancellationTokenSource.Token).ContinueWith(_ =>
{
lock (_gate)
{
// We need to re-check the cancellation token source under the lock, since it might have been cancelled and restarted
// due to another event
cancellationTokenSource.Token.ThrowIfCancellationRequested();
RefreshMetadataReferencesForFile_NoLock(fullFilePath);
_metadataReferenceRefreshCancellationTokenSources.Remove(fullFilePath);
}
}, cancellationTokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
private void RefreshMetadataReferencesForFile_NoLock(string fullFilePath)
{
// Since all adds/removals of references for this project happen under our lock, it's safe to do this
// check without taking the main workspace lock.
var project = _workspace.CurrentSolution.GetProject(Id);
foreach (var portableExecutableReference in project.MetadataReferences.OfType<PortableExecutableReference>())
{
// Loop to find each reference with the given path. It's possible that there might be multiple references of the same path;
// the project system could concievably add the same reference multiple times but with different aliases. It's also possible
// we might not find the path at all: when we recieve the file changed event, we aren't checking if the file is still
// in the workspace at that time; it's possible it might have already been removed. We could add a second check for the file
// there, but it's just overhead checking for a rare situation we'll still be able to deal with here.
if (portableExecutableReference.FilePath == fullFilePath)
{
var newPortableExecutableReference = _workspace.CreatePortableExecutableReference(portableExecutableReference.FilePath, portableExecutableReference.Properties);
// We need to swap this out. Time to take the full lock now.
_workspace.ApplyBatchChangeToProject(Id, s =>
{
return s.RemoveMetadataReference(Id, portableExecutableReference)
.AddMetadataReference(Id, newPortableExecutableReference);
});
// Transfer the ownership of the file watching token
var fileWatchingToken = _metadataReferenceFileWatchingTokens[portableExecutableReference];
_metadataReferenceFileWatchingTokens.Remove(portableExecutableReference);
_metadataReferenceFileWatchingTokens.Add(newPortableExecutableReference, fileWatchingToken);
}
}
}
private void CancelOutstandingMetadataReferenceRefreshForFile_NoLock(string fullFilePath)
{
if (_metadataReferenceRefreshCancellationTokenSources.TryGetValue(fullFilePath, out var cancellationTokenSource))
{
cancellationTokenSource.Cancel();
_metadataReferenceRefreshCancellationTokenSources.Remove(fullFilePath);
}
}
#endregion
#region Metadata Reference Addition/Removal
public void AddMetadataReference(string fullPath, MetadataReferenceProperties properties)
......@@ -691,11 +781,12 @@ public void AddMetadataReference(string fullPath, MetadataReferenceProperties pr
}
else
{
w.OnMetadataReferenceAdded(Id, _workspace.CreateMetadataReference(fullPath, properties));
var metadataReference = _workspace.CreatePortableExecutableReference(fullPath, properties);
w.OnMetadataReferenceAdded(Id, metadataReference);
_metadataReferenceFileWatchingTokens.Add(metadataReference, _fileReferenceChangeContext.EnqueueWatchingFile(metadataReference.FilePath));
}
});
}
}
}
......@@ -763,6 +854,10 @@ public void RemoveMetadataReference(string fullPath, MetadataReferenceProperties
.Single(m => m.FilePath == fullPath && m.Properties == properties);
w.OnMetadataReferenceRemoved(Id, metadataReference);
_fileReferenceChangeContext.StopWatchingFile(_metadataReferenceFileWatchingTokens[metadataReference]);
_metadataReferenceFileWatchingTokens.Remove(metadataReference);
CancelOutstandingMetadataReferenceRefreshForFile_NoLock(metadataReference.FilePath);
}
});
}
......@@ -873,6 +968,7 @@ public void RemoveProjectReference(ProjectReference projectReference)
public void RemoveFromWorkspace()
{
_documentFileChangeContext.Dispose();
_fileReferenceChangeContext.Dispose();
lock (_gate)
{
......@@ -883,6 +979,12 @@ public void RemoveFromWorkspace()
}
_eventSubscriptionTracker.Clear();
// Clear any remaining pending refreshes we have for files
foreach (var cancellationTokenSource in _metadataReferenceRefreshCancellationTokenSources.Values)
{
cancellationTokenSource.Cancel();
}
}
_workspace.ApplyChangeToWorkspace(w => w.OnProjectRemoved(Id));
......@@ -1013,7 +1115,7 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta
}
_documentPathsToDocumentIds.Add(fullPath, documentId);
_project._fileWatchingTokens.Add(documentId, _project._documentFileChangeContext.EnqueueWatchingFile(fullPath));
_project._documentFileWatchingTokens.Add(documentId, _project._documentFileChangeContext.EnqueueWatchingFile(fullPath));
if (_project._activeBatchScopes > 0)
{
......@@ -1157,8 +1259,8 @@ public void RemoveFile(string fullPath)
throw new ArgumentException($"'{fullPath}' is not a source file of this project.");
}
_project._documentFileChangeContext.StopWatchingFile(_project._fileWatchingTokens[documentId]);
_project._fileWatchingTokens.Remove(documentId);
_project._documentFileChangeContext.StopWatchingFile(_project._documentFileWatchingTokens[documentId]);
_project._documentFileWatchingTokens.Remove(documentId);
RemoveFileInternal(documentId, fullPath);
}
......
......@@ -1468,8 +1468,8 @@ private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId proje
if (string.Equals(convertedReference.path, outputPath, StringComparison.OrdinalIgnoreCase) &&
convertedReference.projectReference.ProjectId == projectId)
{
var metadataReference =
CreateMetadataReference(
var metadataReference =
CreatePortableExecutableReference(
convertedReference.path,
new MetadataReferenceProperties(
aliases: convertedReference.projectReference.Aliases,
......@@ -1541,12 +1541,7 @@ public ProjectReference TryRemoveConvertedProjectReference(ProjectId referencing
return null;
}
public MetadataReference CreateMetadataReference(string path, MetadataReferenceProperties properties)
{
return Services.GetRequiredService<IMetadataService>().GetReference(path, properties);
}
private void SetSolutionAndRaiseWorkspaceChanged_NoLock(CodeAnalysis.Solution modifiedSolution, ICollection<ProjectId> projectIdsChanged)
{
if (projectIdsChanged.Count > 0)
......
......@@ -11,6 +11,34 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Formatting
{
public class FormattingEngineTriviaTests : CSharpFormattingTestBase
{
[Fact, Trait(Traits.Feature, Traits.Features.Formatting)]
[WorkItem(31130, "https://github.com/dotnet/roslyn/issues/31130")]
public async Task PreprocessorNullable()
{
var content = @"
#nullable
class C
{
#nullable enable
void Method()
{
#nullable disable
}
}";
var expected = @"
#nullable
class C
{
#nullable enable
void Method()
{
#nullable disable
}
}";
await AssertFormatAsync(expected, content);
}
[Fact, Trait(Traits.Feature, Traits.Features.Formatting)]
public async Task PreprocessorInEmptyFile()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册