From 0874a505fa20ceb0b904f1b90be1908b72e8e8ad Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 14 May 2021 09:11:19 +0200 Subject: [PATCH 01/15] Rename 'Basic' to 'Visual Basic' in Visual Studio options page --- .../VisualBasic/Impl/LanguageService/VisualBasicPackage.vb | 2 ++ src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb index 740139e98436f..f2106d45d9726 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb @@ -30,6 +30,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic ' Advanced ' Code Style (category) ' General + ' Naming + ' IntelliSense diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index ef4f168cef62d..83f3b2e4c621e 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -65,6 +65,8 @@ // Code Style (category) // General // Naming +// IntelliSense + [$RootKey$\Languages\Language Services\Basic\EditorToolsOptions\IntelliSense] @="#112" @@ -167,7 +169,7 @@ [$RootKey$\Languages\Language Services\Basic] @="{e34acdc0-baae-11d0-88bf-00a0c9110049}" "Package"="{574fc912-f74f-4b4e-92c3-f695c208a2bb}" -"LangResID"=dword:00000000 +"LangResID"=dword:00000065 "DefaultToInsertSpaces"=dword:00000001 "EnableAdvancedMembersOption"=dword:00000001 From e2a77740fb1db14480b57d1db42b3bfb35e7f825 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Sat, 15 May 2021 20:17:28 +0200 Subject: [PATCH 02/15] Update comments per feedback --- .../VisualBasic/Impl/LanguageService/VisualBasicPackage.vb | 2 +- src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb index f2106d45d9726..7a4b6f8167fdd 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb @@ -23,7 +23,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic ' The option page configuration is duplicated in PackageRegistration.pkgdef. ' ' VB option pages tree - ' Basic + ' Visual Basic ' General (from editor) ' Scroll Bars (from editor) ' Tabs (from editor) diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index 83f3b2e4c621e..bfb3a04c6856c 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -57,7 +57,7 @@ // The option page configuration is duplicated on VisualBasicPackage type definition. // // VB option pages tree -// Basic +// Visual Basic // General (from editor) // Scroll Bars (from editor) // Tabs (from editor) From f50621ab200f0e376a2df51d61da6e0651bb9573 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 15 Feb 2022 16:03:01 -0800 Subject: [PATCH 03/15] Colorize 'args' in top-level program --- .../Classification/TotalClassifierTests.cs | 52 +++++++++++++++++++ .../QuickInfo/SemanticQuickInfoSourceTests.cs | 35 +++++++++++++ .../Classification/AbstractClassifierTests.cs | 9 ++++ .../NameSyntaxClassifier.cs | 7 +++ 4 files changed, 103 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index 50199eed066c3..343c641ab89bd 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -2238,6 +2238,58 @@ static C() { } Punctuation.CloseParen, Punctuation.OpenCurly, Punctuation.CloseCurly, +Punctuation.CloseCurly); + } + + [Theory] + [CombinatorialData] + [WorkItem(59569, "https://github.com/dotnet/roslyn/issues/59569")] + public async Task TestArgsInTopLevel(TestHost testHost) + { + await TestAsync( +@" +[|foreach (var arg in args) +{ +}|]", + testHost, + parseOptions: null, +ControlKeyword("foreach"), +Punctuation.OpenParen, +Keyword("var"), +Local("arg"), +ControlKeyword("in"), +Keyword("args"), +Punctuation.CloseParen, +Punctuation.OpenCurly, +Punctuation.CloseCurly); + } + + [Theory] + [CombinatorialData] + [WorkItem(59569, "https://github.com/dotnet/roslyn/issues/59569")] + public async Task TestArgsInNormalProgram(TestHost testHost) + { + await TestAsync( +@" +class Program +{ + static void Main(string[] args) + { + [|foreach (var arg in args) + { + }|] + } +}", + testHost, + parseOptions: null, +ControlKeyword("foreach"), +Punctuation.OpenParen, +Keyword("var"), +Local("arg"), +ControlKeyword("in"), +Parameter("args"), +Punctuation.CloseParen, +Punctuation.OpenCurly, Punctuation.CloseCurly); } } diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 046a3fe451a93..e1401aad99e31 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -8192,5 +8192,40 @@ Hello world Hello world """"""")); } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestArgsInTopLevel() + { + var markup = +@" +forach (var arg in $$args) +{ +} +"; + + await TestWithOptionsAsync( + Options.Regular, markup, + MainDescription($"({FeaturesResources.parameter}) string[] args")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task TestArgsInNormalProgram() + { + var markup = +@" +class Program +{ + static void Main(string[] args) + { + foreach (var arg in $$args) + { + } + } +} +"; + + await TestAsync(markup, + MainDescription($"({FeaturesResources.parameter}) string[] args")); + } } } diff --git a/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs b/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs index 1001e756a9598..ecf5be5384e69 100644 --- a/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs +++ b/src/EditorFeatures/TestUtilities/Classification/AbstractClassifierTests.cs @@ -102,6 +102,15 @@ protected async Task TestAsync( await DefaultTestAsync(code, code, testHost, expected); } + protected async Task TestAsync( + string code, + TestHost testHost, + ParseOptions? parseOptions, + params FormattedClassification[] expected) + { + await TestAsync(code, code, testHost, parseOptions, expected); + } + protected async Task TestAsync( string code, string allCode, diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs index 4e2502c394e51..e6d353864a7ba 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs @@ -175,6 +175,13 @@ private static bool TryClassifySymbol( } } + if (name is IdentifierNameSyntax { Identifier.Text: "args" } && + symbol is IParameterSymbol { ContainingSymbol: IMethodSymbol { Name: WellKnownMemberNames.TopLevelStatementsEntryPointMethodName } }) + { + classifiedSpan = new ClassifiedSpan(name.Span, ClassificationTypeNames.Keyword); + return true; + } + if (name.IsNint || name.IsNuint) { if (symbol is ITypeSymbol type && type.IsNativeIntegerType) From 20cb688c496f475ac606d311a43f6be6c1306c34 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 15 Feb 2022 22:32:19 -0800 Subject: [PATCH 04/15] Handle nullability attributes in param-nullchecking (#59393) --- .../Portable/FlowAnalysis/NullableWalker.cs | 6 +- .../Symbols/Source/ParameterHelpers.cs | 34 +++- .../Semantics/NullCheckedParameterTests.cs | 174 +++++++++++++++++- 3 files changed, 201 insertions(+), 13 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index d052ecd9e89cf..d3cb290a6dc5c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -2067,7 +2067,7 @@ internal static bool AreParameterAnnotationsCompatible( overriddenType, overriddenAnnotations, // We don't consider '!!' when deciding whether 'overridden' is compatible with 'override' - isNullChecked: false); + applyParameterNullCheck: false); if (isBadAssignment(valueState, overridingType, overridingAnnotations)) { return false; @@ -2527,9 +2527,9 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param return null; } - internal static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, bool isNullChecked) + internal static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, bool applyParameterNullCheck) { - if (isNullChecked) + if (applyParameterNullCheck) { return TypeWithState.Create(parameterType.Type, NullableFlowState.NotNull); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index 910cd4273f970..1b51643ada5cc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -809,15 +809,43 @@ internal static void ReportParameterNullCheckingErrors(DiagnosticBag diagnostics { diagnostics.Add(ErrorCode.ERR_DiscardCannotBeNullChecked, location); } - if (parameter.TypeWithAnnotations.NullableAnnotation.IsAnnotated() - || parameter.Type.IsNullableTypeOrTypeParameter()) + + var annotations = parameter.FlowAnalysisAnnotations; + if ((annotations & FlowAnalysisAnnotations.NotNull) == 0 + && NullableWalker.GetParameterState(parameter.TypeWithAnnotations, annotations, applyParameterNullCheck: false).State.MayBeNull() + && !isTypeParameterWithPossiblyNonNullableType(parameter.TypeWithAnnotations, annotations)) { diagnostics.Add(ErrorCode.WRN_NullCheckingOnNullableType, location, parameter); } - else if (parameter.Type.IsValueType && !parameter.Type.IsPointerOrFunctionPointer()) + + if (parameter.Type.IsNonNullableValueType() && !parameter.Type.IsPointerOrFunctionPointer()) { diagnostics.Add(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, location, parameter); } + + // For type parameters, we only want to give the warning if no type argument would result in a non-nullable type. + static bool isTypeParameterWithPossiblyNonNullableType(TypeWithAnnotations typeWithAnnotations, FlowAnalysisAnnotations annotations) + { + if (!typeWithAnnotations.Type.IsTypeParameter()) + { + return false; + } + + // We avoid checking the nullable annotations, etc. of constraints due to implementation complexity, + // and consider it acceptable to miss "!! on nullable type" warnings in scenarios like `void M(U u!!) where U : T?`. + if (typeWithAnnotations.NullableAnnotation.IsAnnotated()) + { + return false; + } + + // `void M([AllowNull] T t!!)` + if ((annotations & FlowAnalysisAnnotations.AllowNull) != 0) + { + return false; + } + + return true; + } } internal static ImmutableArray ConditionallyCreateInModifiers(RefKind refKind, bool addRefReadOnlyModifier, Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs index 781d1b889736d..5b22538850a77 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs @@ -725,16 +725,13 @@ internal override void F(U u!! = default) { } } class B3 : A { - internal override void F(U u!! = default) { } + internal override void F(U u!! = default) { } // note: 'U' is a nullable type here but we don't give a warning due to complexity of accurately searching the constraints. }"; var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); compilation.VerifyDiagnostics( - // (12,35): warning CS8719: Parameter 'u' is null-checked but is null by default. - // internal override void F(U u!! = default) { } - Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "u").WithArguments("u").WithLocation(12, 35), - // (16,35): warning CS8721: Nullable value type 'U' is null-checked and will throw if null. - // internal override void F(U u!! = default) { } - Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "u").WithArguments("U").WithLocation(16, 35)); + // (12,35): warning CS8993: Parameter 'u' is null-checked but is null by default. + // internal override void F(U u!! = default) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "u").WithArguments("u").WithLocation(12, 35)); } [Fact] @@ -1034,6 +1031,9 @@ static void M4([DisallowNull] T x3, [DisallowNull] T y3!!) // (16,9): warning CS8602: Dereference of a possibly null reference. // x1.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(16, 9), + // (19,55): warning CS8995: Nullable type 'T' is null-checked and will throw if null. + // static void M3([AllowNull] T x2, [AllowNull] T y2!!) + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "y2").WithArguments("T").WithLocation(19, 55), // (21,9): warning CS8602: Dereference of a possibly null reference. // x2.ToString(); // 4 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(21, 9)); @@ -1070,5 +1070,165 @@ public override void M2(string s) { } // 3 Diagnostic(ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnOverride, "M2").WithArguments("s").WithLocation(13, 26) ); } + + [Fact] + public void TestNullabilityAttributes() + { + var source = +@" +#nullable enable +using System.Diagnostics.CodeAnalysis; + +class C +{ + void M( + string s1!!, + [NotNull] string s2!!, + [DisallowNull] string s3!!, + [AllowNull] string s4!!, // 1 + [AllowNull, DisallowNull] string s5!!, // 2 + [AllowNull, NotNull] string s6!!, + + string? s7!!, // 3 + [NotNull] string? s8!!, // ok: this is a typical signature for an 'AssertNotNull' style method. + [DisallowNull] string? s9!!, + [AllowNull] string? s10!!, // 4 + [AllowNull, DisallowNull] string? s11!!, // 5 + [AllowNull, NotNull] string? s12!!, + + int i1!!, // 6 + [NotNull] int i2!!, // 7 + [DisallowNull] int i3!!, // 8 + [AllowNull] int i4!!, // 9 + [AllowNull, DisallowNull] int i5!!, // 10 + [AllowNull, NotNull] int i6!!, // 11 + + int? i7!!, // 12 + [NotNull] int? i8!!, + [DisallowNull] int? i9!!, + [AllowNull] int? i10!!, // 13 + [AllowNull, DisallowNull] int? i11!!, // 14 + [AllowNull, NotNull] int? i12!! + ) { } +}"; + var comp = CreateCompilation(new[] { source, AllowNullAttributeDefinition, DisallowNullAttributeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (11,28): warning CS8995: Nullable type 'string' is null-checked and will throw if null. + // [AllowNull] string s4!!, // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s4").WithArguments("string").WithLocation(11, 28), + // (12,42): warning CS8995: Nullable type 'string' is null-checked and will throw if null. + // [AllowNull, DisallowNull] string s5!!, // 2 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s5").WithArguments("string").WithLocation(12, 42), + // (15,17): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // string? s7!!, // 3 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s7").WithArguments("string?").WithLocation(15, 17), + // (18,29): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // [AllowNull] string? s10!!, // 4 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s10").WithArguments("string?").WithLocation(18, 29), + // (19,43): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // [AllowNull, DisallowNull] string? s11!!, // 5 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s11").WithArguments("string?").WithLocation(19, 43), + // (22,13): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // int i1!!, // 6 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i1").WithArguments("int").WithLocation(22, 13), + // (23,23): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // [NotNull] int i2!!, // 7 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i2").WithArguments("int").WithLocation(23, 23), + // (24,28): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // [DisallowNull] int i3!!, // 8 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i3").WithArguments("int").WithLocation(24, 28), + // (25,25): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // [AllowNull] int i4!!, // 9 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i4").WithArguments("int").WithLocation(25, 25), + // (26,39): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // [AllowNull, DisallowNull] int i5!!, // 10 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i5").WithArguments("int").WithLocation(26, 39), + // (27,34): error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked. + // [AllowNull, NotNull] int i6!!, // 11 + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "i6").WithArguments("int").WithLocation(27, 34), + // (29,14): warning CS8995: Nullable type 'int?' is null-checked and will throw if null. + // int? i7!!, // 12 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "i7").WithArguments("int?").WithLocation(29, 14), + // (32,26): warning CS8995: Nullable type 'int?' is null-checked and will throw if null. + // [AllowNull] int? i10!!, // 13 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "i10").WithArguments("int?").WithLocation(32, 26), + // (33,40): warning CS8995: Nullable type 'int?' is null-checked and will throw if null. + // [AllowNull, DisallowNull] int? i11!!, // 14 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "i11").WithArguments("int?").WithLocation(33, 40) + ); + } + + [Theory] + [InlineData("")] + [InlineData("where T : class")] + [InlineData("where T : class?")] + [InlineData("where T : notnull")] + public void TestNullabilityAttributes_Generic(string constraints) + { + var source = +@" +#nullable enable +using System.Diagnostics.CodeAnalysis; + +class C +{ + void M( + T t1!!, + [NotNull] T t2!!, + [DisallowNull] T t3!!, + [AllowNull] T t4!!, // 1 + [AllowNull, DisallowNull] T t5!!, // 2 + [AllowNull, NotNull] T t6!!, + + T? t7!!, // 3 + [NotNull] T? t8!!, + [DisallowNull] T? t9!!, + [AllowNull] T? t10!!, // 4 + [AllowNull, DisallowNull] T? t11!!, // 5 + [AllowNull, NotNull] T? t12!! + ) " + constraints + @" { } +}"; + var comp = CreateCompilation(new[] { source, AllowNullAttributeDefinition, DisallowNullAttributeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (11,23): warning CS8995: Nullable type 'T' is null-checked and will throw if null. + // [AllowNull] T t4!!, // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t4").WithArguments("T").WithLocation(11, 23), + // (12,37): warning CS8995: Nullable type 'T' is null-checked and will throw if null. + // [AllowNull, DisallowNull] T t5!!, // 2 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t5").WithArguments("T").WithLocation(12, 37), + // (15,12): warning CS8995: Nullable type 'T?' is null-checked and will throw if null. + // T? t7!!, // 3 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t7").WithArguments("T?").WithLocation(15, 12), + // (18,24): warning CS8995: Nullable type 'T?' is null-checked and will throw if null. + // [AllowNull] T? t10!!, // 4 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t10").WithArguments("T?").WithLocation(18, 24), + // (19,38): warning CS8995: Nullable type 'T?' is null-checked and will throw if null. + // [AllowNull, DisallowNull] T? t11!! // 5 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t11").WithArguments("T?").WithLocation(19, 38) + ); + } + + [Fact] + public void AnnotatedTypeParameter_Indirect() + { + var source = @" +#nullable enable + +class C +{ + void M( + T? t!!, // 1 + U u!!) where U : T? + { + } +}"; + // note: U is always nullable when a reference type, + // but we don't warn on '!!' for it due to complexity of accurately searching the constraints. + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,12): warning CS8995: Nullable type 'T?' is null-checked and will throw if null. + // T? t!!, // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t").WithArguments("T?").WithLocation(7, 12)); + } } } From e96fb91c8380c1f05592ea6d48de828b8f6f84f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 16 Feb 2022 10:38:28 -0800 Subject: [PATCH 05/15] Simplify fields and lambdas --- .../Core/Portable/CodeFixes/CodeFixService.cs | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs index c28c4dcc1c907..7c755da5f6618 100644 --- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs @@ -39,8 +39,9 @@ internal partial class CodeFixService : ICodeFixService new((d1, d2) => DiagnosticId.CompareOrdinal(d1.Id, d2.Id)); private readonly IDiagnosticAnalyzerService _diagnosticService; + private readonly ImmutableArray> _fixers; + private readonly Dictionary>> _fixersPerLanguageMap; - private readonly Func>>>> _getWorkspaceFixersMap; private ImmutableDictionary>>>? _lazyWorkspaceFixersMap; private readonly ConditionalWeakTable, ImmutableDictionary>> _projectFixersMap; @@ -48,7 +49,6 @@ internal partial class CodeFixService : ICodeFixService private ImmutableDictionary> _fixerToFixableIdsMap = ImmutableDictionary>.Empty; private readonly Lazy> _lazyFixerToMetadataMap; - private readonly Func>>> _getFixerPriorityMap; private ImmutableDictionary>>? _lazyFixerPriorityMap; private readonly ConditionalWeakTable _analyzerReferenceToFixersMap; @@ -70,16 +70,14 @@ public CodeFixService( _errorLoggers = loggers; _diagnosticService = diagnosticAnalyzerService; + _fixers = fixers.ToImmutableArray(); + _fixersPerLanguageMap = _fixers.ToPerLanguageMapWithMultipleLanguages(); + _lazyFixerToMetadataMap = new(() => fixers.Where(service => service.IsValueCreated).ToImmutableDictionary(service => service.Value, service => service.Metadata)); - var fixersPerLanguageMap = fixers.ToPerLanguageMapWithMultipleLanguages(); var configurationProvidersPerLanguageMap = configurationProviders.ToPerLanguageMapWithMultipleLanguages(); - _getWorkspaceFixersMap = workspace => GetFixerPerLanguageMap(fixersPerLanguageMap, workspace); _configurationProvidersMap = GetConfigurationProvidersPerLanguageMap(configurationProvidersPerLanguageMap); - // REVIEW: currently, fixer's priority is statically defined by the fixer itself. might considering making it more dynamic or configurable. - _getFixerPriorityMap = workspace => GetFixerPriorityPerLanguageMap(fixersPerLanguageMap, workspace); - // Per-project fixers _projectFixersMap = new ConditionalWeakTable, ImmutableDictionary>>(); _analyzerReferenceToFixersMap = new ConditionalWeakTable(); @@ -306,7 +304,7 @@ private bool TryGetWorkspaceFixersMap(Document document, [NotNullWhen(true)] out { if (_lazyWorkspaceFixersMap == null) { - var workspaceFixersMap = _getWorkspaceFixersMap(document.Project.Solution.Workspace); + var workspaceFixersMap = GetFixerPerLanguageMap(document.Project.Solution.Workspace); Interlocked.CompareExchange(ref _lazyWorkspaceFixersMap, workspaceFixersMap, null); } @@ -317,7 +315,7 @@ private bool TryGetWorkspaceFixersPriorityMap(Document document, [NotNullWhen(tr { if (_lazyFixerPriorityMap == null) { - var fixersPriorityByLanguageMap = _getFixerPriorityMap(document.Project.Solution.Workspace); + var fixersPriorityByLanguageMap = GetFixerPriorityPerLanguageMap(document.Project.Solution.Workspace); Interlocked.CompareExchange(ref _lazyFixerPriorityMap, fixersPriorityByLanguageMap, null); } @@ -846,18 +844,17 @@ private static ImmutableArray GetAndTestFixableDiagnosticIds(CodeFixProv } private ImmutableDictionary>>> GetFixerPerLanguageMap( - Dictionary>> fixersPerLanguage, Workspace workspace) { var fixerMap = ImmutableDictionary.Create>>>(); var extensionManager = workspace.Services.GetService(); - foreach (var languageKindAndFixers in fixersPerLanguage) + foreach (var (diagnosticId, lazyFixers) in _fixersPerLanguageMap) { var lazyMap = new Lazy>>(() => { var mutableMap = new Dictionary>(); - foreach (var lazyFixer in languageKindAndFixers.Value) + foreach (var lazyFixer in lazyFixers) { if (!TryGetWorkspaceFixer(lazyFixer, workspace, logExceptionWithInfoBar: true, out var fixer)) { @@ -885,7 +882,7 @@ private ImmutableDictionary GetConfigurationFixProviders(Li } } - private ImmutableDictionary>> GetFixerPriorityPerLanguageMap( - Dictionary>> fixersPerLanguage, - Workspace workspace) + private ImmutableDictionary>> GetFixerPriorityPerLanguageMap(Workspace workspace) { var languageMap = ImmutableDictionary.CreateBuilder>>(); - foreach (var languageAndFixers in fixersPerLanguage) + foreach (var (diagnosticId, lazyFixers) in _fixersPerLanguageMap) { var lazyMap = new Lazy>(() => { var priorityMap = ImmutableDictionary.CreateBuilder(); - var lazyFixers = ExtensionOrderer.Order(languageAndFixers.Value); - for (var i = 0; i < lazyFixers.Count; i++) + var fixers = ExtensionOrderer.Order(lazyFixers); + for (var i = 0; i < fixers.Count; i++) { if (!TryGetWorkspaceFixer(lazyFixers[i], workspace, logExceptionWithInfoBar: false, out var fixer)) { @@ -941,7 +936,7 @@ private ImmutableDictionary Date: Wed, 16 Feb 2022 10:46:05 -0800 Subject: [PATCH 06/15] Simplify further --- .../Core/Portable/CodeFixes/CodeFixService.cs | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs index 7c755da5f6618..12cdfeea51d91 100644 --- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs @@ -42,22 +42,22 @@ internal partial class CodeFixService : ICodeFixService private readonly ImmutableArray> _fixers; private readonly Dictionary>> _fixersPerLanguageMap; - private ImmutableDictionary>>>? _lazyWorkspaceFixersMap; - private readonly ConditionalWeakTable, ImmutableDictionary>> _projectFixersMap; + private readonly ConditionalWeakTable, ImmutableDictionary>> _projectFixersMap = new(); // Shared by project fixers and workspace fixers. - private ImmutableDictionary> _fixerToFixableIdsMap = ImmutableDictionary>.Empty; private readonly Lazy> _lazyFixerToMetadataMap; - private ImmutableDictionary>>? _lazyFixerPriorityMap; - - private readonly ConditionalWeakTable _analyzerReferenceToFixersMap; - private readonly ConditionalWeakTable.CreateValueCallback _createProjectCodeFixProvider; + private readonly ConditionalWeakTable _analyzerReferenceToFixersMap = new(); + private readonly ConditionalWeakTable.CreateValueCallback _createProjectCodeFixProvider = r => new ProjectCodeFixProvider(r); private readonly ImmutableDictionary>> _configurationProvidersMap; private readonly IEnumerable> _errorLoggers; - private ImmutableDictionary _fixAllProviderMap; + private ImmutableDictionary>>>? _lazyWorkspaceFixersMap; + private ImmutableDictionary>>? _lazyFixerPriorityMap; + + private ImmutableDictionary> _fixerToFixableIdsMap = ImmutableDictionary>.Empty; + private ImmutableDictionary _fixAllProviderMap = ImmutableDictionary.Empty; [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -74,15 +74,8 @@ public CodeFixService( _fixersPerLanguageMap = _fixers.ToPerLanguageMapWithMultipleLanguages(); _lazyFixerToMetadataMap = new(() => fixers.Where(service => service.IsValueCreated).ToImmutableDictionary(service => service.Value, service => service.Metadata)); - var configurationProvidersPerLanguageMap = configurationProviders.ToPerLanguageMapWithMultipleLanguages(); - - _configurationProvidersMap = GetConfigurationProvidersPerLanguageMap(configurationProvidersPerLanguageMap); - // Per-project fixers - _projectFixersMap = new ConditionalWeakTable, ImmutableDictionary>>(); - _analyzerReferenceToFixersMap = new ConditionalWeakTable(); - _createProjectCodeFixProvider = new ConditionalWeakTable.CreateValueCallback(r => new ProjectCodeFixProvider(r)); - _fixAllProviderMap = ImmutableDictionary.Empty; + _configurationProvidersMap = GetConfigurationProvidersPerLanguageMap(configurationProviders); } private ImmutableDictionary FixerToMetadataMap => _lazyFixerToMetadataMap.Value; @@ -238,10 +231,10 @@ int GetValue(CodeFixCollection c) { // Ensure that we do not register duplicate configuration fixes. using var _ = PooledHashSet.GetInstance(out var registeredConfigurationFixTitles); - foreach (var spanAndDiagnostic in aggregatedDiagnostics) + foreach (var (span, diagnosticList) in aggregatedDiagnostics) { await AppendConfigurationsAsync( - document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, + document, span, diagnosticList, result, registeredConfigurationFixTitles, cancellationToken).ConfigureAwait(false); } } @@ -874,9 +867,9 @@ private ImmutableDictionary>(); - foreach (var diagnosticIdAndFixers in mutableMap) + foreach (var (diagnosticId, fixers) in mutableMap) { - immutableMap.Add(diagnosticIdAndFixers.Key, diagnosticIdAndFixers.Value.AsImmutableOrEmpty()); + immutableMap.Add(diagnosticId, fixers.AsImmutableOrEmpty()); } return immutableMap.ToImmutable(); @@ -889,16 +882,18 @@ private ImmutableDictionary>> GetConfigurationProvidersPerLanguageMap( - Dictionary>> configurationProvidersPerLanguage) + IEnumerable> configurationProviders) { - var configurationFixerMap = ImmutableDictionary.Create>>(); - foreach (var languageKindAndFixers in configurationProvidersPerLanguage) + var configurationProvidersPerLanguageMap = configurationProviders.ToPerLanguageMapWithMultipleLanguages(); + + var configurationFixerMap = ImmutableDictionary.CreateBuilder>>(); + foreach (var (diagnosticId, lazyFixers) in configurationProvidersPerLanguageMap) { - var lazyConfigurationFixers = new Lazy>(() => GetConfigurationFixProviders(languageKindAndFixers.Value)); - configurationFixerMap = configurationFixerMap.Add(languageKindAndFixers.Key, lazyConfigurationFixers); + var lazyConfigurationFixers = new Lazy>(() => GetConfigurationFixProviders(lazyFixers)); + configurationFixerMap.Add(diagnosticId, lazyConfigurationFixers); } - return configurationFixerMap; + return configurationFixerMap.ToImmutable(); static ImmutableArray GetConfigurationFixProviders(List> languageKindAndFixers) { From 25a439f7783c81f45cc49e1c74b761d257cf16ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 16 Feb 2022 10:50:52 -0800 Subject: [PATCH 07/15] Simplify further --- .../Core/Portable/CodeFixes/CodeFixService.cs | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs index 12cdfeea51d91..1c186eab3a7d8 100644 --- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs @@ -46,12 +46,10 @@ internal partial class CodeFixService : ICodeFixService // Shared by project fixers and workspace fixers. private readonly Lazy> _lazyFixerToMetadataMap; - private readonly ConditionalWeakTable _analyzerReferenceToFixersMap = new(); private readonly ConditionalWeakTable.CreateValueCallback _createProjectCodeFixProvider = r => new ProjectCodeFixProvider(r); - private readonly ImmutableDictionary>> _configurationProvidersMap; - private readonly IEnumerable> _errorLoggers; + private readonly ImmutableArray> _errorLoggers; private ImmutableDictionary>>>? _lazyWorkspaceFixersMap; private ImmutableDictionary>>? _lazyFixerPriorityMap; @@ -67,8 +65,8 @@ public CodeFixService( [ImportMany] IEnumerable> fixers, [ImportMany] IEnumerable> configurationProviders) { - _errorLoggers = loggers; _diagnosticService = diagnosticAnalyzerService; + _errorLoggers = loggers.ToImmutableArray(); _fixers = fixers.ToImmutableArray(); _fixersPerLanguageMap = _fixers.ToPerLanguageMapWithMultipleLanguages(); @@ -162,7 +160,7 @@ public async Task> GetFixesAsync( // group diagnostics by their diagnostics span // invariant: later code gathers & runs CodeFixProviders for diagnostics with one identical diagnostics span (that gets set later as CodeFixCollection's TextSpan) // order diagnostics by span. - SortedDictionary>? aggregatedDiagnostics = null; + var aggregatedDiagnostics = new SortedDictionary>(); // For 'CodeActionPriorityRequest.Normal' or 'CodeActionPriorityRequest.Low', we do not compute suppression/configuration fixes, // those fixes have a dedicated request priority 'CodeActionPriorityRequest.Lowest'. @@ -187,11 +185,10 @@ public async Task> GetFixesAsync( cancellationToken.ThrowIfCancellationRequested(); - aggregatedDiagnostics ??= new SortedDictionary>(); aggregatedDiagnostics.GetOrAdd(diagnostic.GetTextSpan(), _ => new List()).Add(diagnostic); } - if (aggregatedDiagnostics == null) + if (aggregatedDiagnostics.Count == 0) return ImmutableArray.Empty; // Order diagnostics by DiagnosticId so the fixes are in a deterministic order. @@ -793,8 +790,6 @@ private bool IsInteractiveCodeFixProvider(CodeFixProvider provider) AddImport.AbstractAddImportCodeFixProvider; } - private static readonly Func> s_createList = _ => new List(); - private ImmutableArray GetFixableDiagnosticIds(CodeFixProvider fixer, IExtensionManager? extensionManager) { // If we are passed a null extension manager it means we do not have access to a document so there is nothing to @@ -861,7 +856,7 @@ private ImmutableDictionary new List()); list.Add(fixer); } } @@ -942,13 +937,14 @@ private ImmutableDictionary> GetProjectFixer // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive return project.Solution.Workspace.Kind == WorkspaceKind.Interactive ? ImmutableDictionary>.Empty - : _projectFixersMap.GetValue(project.AnalyzerReferences, pId => ComputeProjectFixers(project)); + : _projectFixersMap.GetValue(project.AnalyzerReferences, _ => ComputeProjectFixers(project)); } private ImmutableDictionary> ComputeProjectFixers(Project project) { var extensionManager = project.Solution.Workspace.Services.GetService(); - ImmutableDictionary>.Builder? builder = null; + + var builder = ImmutableDictionary.CreateBuilder>(); foreach (var reference in project.AnalyzerReferences) { var projectCodeFixerProvider = _analyzerReferenceToFixersMap.GetValue(reference, _createProjectCodeFixProvider); @@ -958,22 +954,14 @@ private ImmutableDictionary> ComputeProjectF foreach (var id in fixableIds) { if (string.IsNullOrWhiteSpace(id)) - { continue; - } - builder ??= ImmutableDictionary.CreateBuilder>(); - var list = builder.GetOrAdd(id, s_createList); + var list = builder.GetOrAdd(id, static _ => new List()); list.Add(fixer); } } } - if (builder == null) - { - return ImmutableDictionary>.Empty; - } - return builder.ToImmutable(); } } From f0a81c843c3078b2584e989aa32ca3fadd8c56a9 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 16 Feb 2022 11:46:06 -0800 Subject: [PATCH 08/15] List-patterns: report error when no suitable length member (#59475) --- .../CSharp/Portable/Binder/Binder_Patterns.cs | 21 +- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../IOperationTests_IIsPatternExpression.cs | 3 + .../PatternMatchingTests_ListPatterns.cs | 308 ++++++++++++++++++ 18 files changed, 393 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 4075977e98ce0..9b3082b53180d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -289,7 +289,12 @@ private BoundListPattern BindListPattern( BoundListPatternReceiverPlaceholder? receiverPlaceholder; BoundListPatternIndexPlaceholder? argumentPlaceholder; - if (inputType.IsErrorType()) + if (inputType.IsDynamic()) + { + Error(diagnostics, ErrorCode.ERR_UnsupportedTypeForListPattern, node, inputType); + } + + if (inputType.IsErrorType() || inputType.IsDynamic()) { hasErrors = true; elementType = inputType; @@ -337,13 +342,9 @@ private bool IsCountableAndIndexable(SyntaxNode node, TypeSymbol inputType, out private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inputType, uint inputValEscape, BindingDiagnosticBag diagnostics, out BoundExpression indexerAccess, out BoundExpression lengthAccess, out BoundListPatternReceiverPlaceholder? receiverPlaceholder, out BoundListPatternIndexPlaceholder argumentPlaceholder) { - bool hasErrors = false; - if (inputType.IsDynamic()) - { - hasErrors |= true; - Error(diagnostics, ErrorCode.ERR_UnsupportedTypeForListPattern, node, inputType); - } + Debug.Assert(!inputType.IsDynamic()); + bool hasErrors = false; receiverPlaceholder = new BoundListPatternReceiverPlaceholder(node, GetValEscape(inputType, inputValEscape), inputType) { WasCompilerGenerated = true }; if (inputType.IsSZArray()) { @@ -359,7 +360,11 @@ private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inpu } else { - hasErrors |= !TryBindLengthOrCount(node, receiverPlaceholder, out lengthAccess, diagnostics); + if (!TryBindLengthOrCount(node, receiverPlaceholder, out lengthAccess, diagnostics)) + { + hasErrors = true; + Error(diagnostics, ErrorCode.ERR_ListPatternRequiresLength, node, inputType); + } } var analyzedArguments = AnalyzedArguments.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 5fd068b866369..27dc5ef9d0477 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6760,6 +6760,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ List patterns may not be used for a value of type '{0}'. + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + Slice patterns may not be used for a value of type '{0}'. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index f891efdf9ae55..9e700b7a7ffab 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2020,6 +2020,7 @@ internal enum ErrorCode ERR_StructHasInitializersAndNoDeclaredConstructor = 8983, ERR_EncUpdateFailedDelegateTypeChanged = 8984, + ERR_ListPatternRequiresLength = 8985, ERR_DiscardCannotBeNullChecked = 8990, ERR_MustNullCheckInImplementation = 8991, ERR_NonNullableValueTypeIsNullChecked = 8992, diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index cf8a63c0ca222..9c336908ebfc4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -807,6 +807,11 @@ Hodnota direktivy #line chybí nebo je mimo rozsah. + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Žádná přetížená metoda {0} neodpovídá ukazateli na funkci {1}. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index f26a9dabd49f8..934438d76aadf 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -807,6 +807,11 @@ Der Wert der #line-Anweisung fehlt oder liegt außerhalb des gültigen Bereichs. + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Keine Überladung für "{0}" stimmt mit dem Funktionszeiger "{1}" überein. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index bf487453d1132..2bd8e9edd9051 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -807,6 +807,11 @@ Falta el valor de directiva #line o está fuera del rango + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Ninguna sobrecarga correspondiente a "{0}" coincide con el puntero de función "{1}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 42694a4856397..39aa3091023dd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -807,6 +807,11 @@ La valeur de directive #line est manquante ou hors limites + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Aucune surcharge pour '{0}' ne correspond au pointeur de fonction '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 970507838682e..74f6a27c71f0e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -807,6 +807,11 @@ Il valore della direttiva #line manca oppure non è compreso nell'intervallo + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Nessun overload per '{0}' corrisponde al puntatore a funzione '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 8e4920c4ec36e..1509c49c2c691 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -807,6 +807,11 @@ #line ディレクティブの値が見つからないか、範囲外です + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' 関数ポインター '{1}' に一致する '{0}' のオーバーロードはありません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 02c6a1afa275d..48281d4c3f598 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -807,6 +807,11 @@ #line 지시문 값이 없거나 범위를 벗어났습니다. + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' 함수 포인터 '{1}'과(와) 일치하는 '{0}'에 대한 오버로드가 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 0b54d90f80b4f..088f9743b49da 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -807,6 +807,11 @@ Brak wartości dyrektywy #line lub jest ona poza zakresem + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Żadne z przeciążeń dla elementu „{0}” nie pasuje do wskaźnika funkcji „{1}” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 5d7a3f8be564c..bdf7271904d24 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -807,6 +807,11 @@ O valor da diretiva de #line está ausente ou fora do intervalo + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Nenhuma sobrecarga de '{0}' corresponde ao ponteiro de função '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 5e4da7accecbc..81f2ee366e999 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -807,6 +807,11 @@ Значение директивы #line отсутствует или находится за пределами допустимого диапазона + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' Нет перегруженного метода для "{0}", который соответствует указателю на функцию "{1}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 96705aed73f17..09d21e1a96de5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -807,6 +807,11 @@ #line yönerge değeri eksik veya aralık dışı + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' '{0}' için aşırı yüklemelerin hiçbiri '{1}' işlev işaretçisiyle eşleşmiyor diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index d9a0d08a783b2..0649d83ec8cea 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -807,6 +807,11 @@ #line 指令值缺失或超出范围 + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' “{0}”没有与函数指针“{1}”匹配的重载 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 551b2ed6161a4..a575eaa5beaac 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -807,6 +807,11 @@ #Line 指示詞值遺漏或超出範圍 + + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + List patterns may not be used for a value of type '{0}'. No suitable 'Length' or 'Count' property was found. + + No overload for '{0}' matches function pointer '{1}' '{0}' 沒有任何多載符合函式指標 '{1}' diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs index 79b3158d0aff7..6fe2b772abfe0 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs @@ -2319,6 +2319,9 @@ void M() "; var expectedDiagnostics = new[] { + // (8,31): error CS8985: List patterns may not be used for a value of type 'X'. No suitable 'Length' or 'Count' property was found. + // _ = /**/this is []/**/; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("X").WithLocation(8, 31), // (8,31): error CS0518: Predefined type 'System.Index' is not defined or imported // _ = /**/this is []/**/; Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(8, 31) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs index 9daac7b509bb6..f5d0698de3da1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs @@ -927,6 +927,9 @@ void M(object o) "; var expectedDiagnostics = new[] { + // error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found. + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, listPattern).WithArguments("object"), + // error CS0021: Cannot apply indexing with [] to an expression of type 'object' Diagnostic(ErrorCode.ERR_BadIndexLHS, listPattern).WithArguments("object"), @@ -3674,6 +3677,9 @@ public void M() // (4,17): error CS0547: 'C.Length': property or indexer cannot have void type // public void Length => throw null; Diagnostic(ErrorCode.ERR_PropertyCantHaveVoidType, "Length").WithArguments("C.Length").WithLocation(4, 17), + // (8,21): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = this is [1]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[1]").WithArguments("C").WithLocation(8, 21), // (8,21): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' // _ = this is [1]; Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "System.Index", "int").WithLocation(8, 21), @@ -3701,6 +3707,9 @@ public void M() "; var compilation = CreateCompilation(new[] { source, TestSources.Index }); compilation.VerifyEmitDiagnostics( + // (9,21): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = this is [1]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[1]").WithArguments("C").WithLocation(9, 21), // (9,21): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' // _ = this is [1]; Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "System.Index", "int").WithLocation(9, 21), @@ -3710,6 +3719,30 @@ public void M() ); } + [Fact] + public void ListPattern_StringLength_SystemIndexIndexer() + { + var source = @" +class C +{ + public string Length => throw null; + public int this[System.Index i] => throw null; + + public void M() + { + _ = this is [1]; + _ = this[^1]; + } +} +"; + var compilation = CreateCompilation(new[] { source, TestSources.Index }); + compilation.VerifyEmitDiagnostics( + // (9,21): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = this is [1]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[1]").WithArguments("C").WithLocation(9, 21) + ); + } + [Fact] public void SlicePattern_VoidReturn() { @@ -4503,6 +4536,9 @@ public void M() "; var compilation = CreateCompilationWithIL(new[] { source, TestSources.Index, TestSources.Range }, il); compilation.VerifyEmitDiagnostics( + // (6,24): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = new C() is [var item, ..var rest]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[var item, ..var rest]").WithArguments("C").WithLocation(6, 24), // (6,24): error CS0021: Cannot apply indexing with [] to an expression of type 'C' // _ = new C() is [var item, ..var rest]; Diagnostic(ErrorCode.ERR_BadIndexLHS, "[var item, ..var rest]").WithArguments("C").WithLocation(6, 24), @@ -4727,6 +4763,9 @@ public void M() "; var compilation = CreateCompilationWithIL(source, il); compilation.VerifyEmitDiagnostics( + // (6,24): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = new C() is [var item, ..var rest]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[var item, ..var rest]").WithArguments("C").WithLocation(6, 24), // (6,24): error CS0021: Cannot apply indexing with [] to an expression of type 'C' // _ = new C() is [var item, ..var rest]; Diagnostic(ErrorCode.ERR_BadIndexLHS, "[var item, ..var rest]").WithArguments("C").WithLocation(6, 24), @@ -5954,6 +5993,9 @@ class C }"; var comp = CreateCompilation(new[] { src, TestSources.Index }); comp.VerifyEmitDiagnostics( + // (4,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // [..] => 1, + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[..]").WithArguments("C").WithLocation(4, 5), // (4,5): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' // [..] => 1, Diagnostic(ErrorCode.ERR_BadArgType, "[..]").WithArguments("1", "System.Index", "int").WithLocation(4, 5), @@ -5981,6 +6023,9 @@ class C }"; var comp = CreateCompilation(new[] { src, TestSources.Index }); comp.VerifyEmitDiagnostics( + // (4,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // [..] => 1, + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[..]").WithArguments("C").WithLocation(4, 5), // (4,5): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' // [..] => 1, Diagnostic(ErrorCode.ERR_BadArgType, "[..]").WithArguments("1", "System.Index", "int").WithLocation(4, 5), @@ -7634,6 +7679,9 @@ class C "; var compilation = CreateCompilationWithIndex(source); compilation.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = new C() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(2, 16), // (2,16): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' // _ = new C() is []; Diagnostic(ErrorCode.ERR_BadArgType, "[]").WithArguments("1", "System.Index", "int").WithLocation(2, 16), @@ -7781,6 +7829,9 @@ public int this[System.Range r] { set { } } "; var comp = CreateCompilation(new[] { source, TestSources.Index, TestSources.Range }); comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // _ = new C() is [var x, .. var y]; // 1, 2, 3 + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[var x, .. var y]").WithArguments("C").WithLocation(2, 16), // (2,16): error CS0154: The property or indexer 'C.this[Index]' cannot be used in this context because it lacks the get accessor // _ = new C() is [var x, .. var y]; // 1, 2, 3 Diagnostic(ErrorCode.ERR_PropertyLacksGet, "[var x, .. var y]").WithArguments("C.this[System.Index]").WithLocation(2, 16), @@ -7796,6 +7847,38 @@ public int this[System.Range r] { set { } } ); } + [Fact] + public void ListPattern_SetOnlyIndexers_LengthWithGetter() + { + var source = @" +_ = new C() is [var x, .. var y]; // 1, 2 +_ = new C()[^1]; // 3 +_ = new C()[..]; // 4 + +class C +{ + public int Length => 0; + public int this[System.Index i] { set { } } + public int this[System.Range r] { set { } } +} +"; + var comp = CreateCompilation(new[] { source, TestSources.Index, TestSources.Range }); + comp.VerifyEmitDiagnostics( + // (2,16): error CS0154: The property or indexer 'C.this[Index]' cannot be used in this context because it lacks the get accessor + // _ = new C() is [var x, .. var y]; // 1, 2 + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "[var x, .. var y]").WithArguments("C.this[System.Index]").WithLocation(2, 16), + // (2,24): error CS0154: The property or indexer 'C.this[Range]' cannot be used in this context because it lacks the get accessor + // _ = new C() is [var x, .. var y]; // 1, 2 + Diagnostic(ErrorCode.ERR_PropertyLacksGet, ".. var y").WithArguments("C.this[System.Range]").WithLocation(2, 24), + // (3,5): error CS0154: The property or indexer 'C.this[Index]' cannot be used in this context because it lacks the get accessor + // _ = new C()[^1]; // 3 + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "new C()[^1]").WithArguments("C.this[System.Index]").WithLocation(3, 5), + // (4,5): error CS0154: The property or indexer 'C.this[Range]' cannot be used in this context because it lacks the get accessor + // _ = new C()[..]; // 4 + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "new C()[..]").WithArguments("C.this[System.Range]").WithLocation(4, 5) + ); + } + [Fact] public void SlicePattern_OnConsList() { @@ -8282,6 +8365,9 @@ class C : INotCountable "; var comp = CreateCompilation(new[] { source, TestSources.Index, TestSources.Range }); comp.VerifyEmitDiagnostics( + // (2,34): error CS8985: List patterns may not be used for a value of type 'INotCountable'. No suitable 'Length' or 'Count' property was found. + // _ = new C() is INotCountable and [var x, .. var y]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[var x, .. var y]").WithArguments("INotCountable").WithLocation(2, 34), // (2,34): error CS0021: Cannot apply indexing with [] to an expression of type 'INotCountable' // _ = new C() is INotCountable and [var x, .. var y]; Diagnostic(ErrorCode.ERR_BadIndexLHS, "[var x, .. var y]").WithArguments("INotCountable").WithLocation(2, 34), @@ -8337,6 +8423,9 @@ public void ListPattern_Tuples() "; var comp = CreateCompilation(new[] { source, TestSources.Index, TestSources.Range, TestSources.ITuple }); comp.VerifyEmitDiagnostics( + // (2,15): error CS8985: List patterns may not be used for a value of type '(int, int)'. No suitable 'Length' or 'Count' property was found. + // _ = (1, 2) is [var x, .. var y]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[var x, .. var y]").WithArguments("(int, int)").WithLocation(2, 15), // (2,15): error CS0021: Cannot apply indexing with [] to an expression of type '(int, int)' // _ = (1, 2) is [var x, .. var y]; Diagnostic(ErrorCode.ERR_BadIndexLHS, "[var x, .. var y]").WithArguments("(int, int)").WithLocation(2, 15), @@ -8381,6 +8470,219 @@ public void ListPattern_NullTestOnSlice() ); } + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength() + { + var source = @" +_ = new S() is []; +_ = new S() is [..]; +_ = new S() is [0, .. var x, 1]; + +struct S +{ + public int this[System.Index i] => 0; + public int this[System.Range i] => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16), + // (3,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is [..]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[..]").WithArguments("S").WithLocation(3, 16), + // (4,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is [0, .. var x, 1]; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[0, .. var x, 1]").WithArguments("S").WithLocation(4, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_WithIntIndexer() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[int i] => i; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16), + // (2,16): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_BadArgType, "[]").WithArguments("1", "System.Index", "int").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_PrivateLength() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + private int Length => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_ProtectedLength() + { + var source = @" +_ = new S() is []; + +class S +{ + public int this[System.Index i] => 0; + protected int Length => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_PrivateCount() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + private int Count => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_LengthMethod() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + public int Length() => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_WriteOnlyLength() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + public int Length { set { throw null; } } +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_ObjectLength() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + public object Length => null; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_StaticLength() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[System.Index i] => 0; + public static int Length => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16) + ); + } + + [Fact, WorkItem(59465, "https://github.com/dotnet/roslyn/issues/59465")] + public void MissingLength_StaticLength_IntIndexer() + { + var source = @" +_ = new S() is []; + +struct S +{ + public int this[int i] => 0; + public static int Length => 0; +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + comp.VerifyEmitDiagnostics( + // (2,16): error CS8985: List patterns may not be used for a value of type 'S'. No suitable 'Length' or 'Count' property was found. + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("S").WithLocation(2, 16), + // (2,16): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = new S() is []; + Diagnostic(ErrorCode.ERR_BadArgType, "[]").WithArguments("1", "System.Index", "int").WithLocation(2, 16) + ); + } + [Fact, WorkItem(58738, "https://github.com/dotnet/roslyn/issues/58738")] public void ListPattern_AbstractFlowPass_isBoolTest() { @@ -8405,6 +8707,9 @@ public void ListPattern_AbstractFlowPass_isBoolTest() // (4,22): error CS0029: Cannot implicitly convert type 'int' to 'int[]' // if (a is [var x] and x is [1]) Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("int", "int[]").WithLocation(4, 22), + // (4,27): error CS8985: List patterns may not be used for a value of type 'bool'. No suitable 'Length' or 'Count' property was found. + // if (a is [var x] and x is [1]) + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[1]").WithArguments("bool").WithLocation(4, 27), // (4,27): error CS0021: Cannot apply indexing with [] to an expression of type 'bool' // if (a is [var x] and x is [1]) Diagnostic(ErrorCode.ERR_BadIndexLHS, "[1]").WithArguments("bool").WithLocation(4, 27), @@ -8417,6 +8722,9 @@ public void ListPattern_AbstractFlowPass_isBoolTest() // (13,23): error CS0029: Cannot implicitly convert type 'bool' to 'bool[]' // if ((b is [var z] and z) is [true]) Diagnostic(ErrorCode.ERR_NoImplicitConv, "z").WithArguments("bool", "bool[]").WithLocation(13, 23), + // (13,29): error CS8985: List patterns may not be used for a value of type 'bool'. No suitable 'Length' or 'Count' property was found. + // if ((b is [var z] and z) is [true]) + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[true]").WithArguments("bool").WithLocation(13, 29), // (13,29): error CS0021: Cannot apply indexing with [] to an expression of type 'bool' // if ((b is [var z] and z) is [true]) Diagnostic(ErrorCode.ERR_BadIndexLHS, "[true]").WithArguments("bool").WithLocation(13, 29) From 52bdc01db337967e9d71080b548a5419a44ed996 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 16 Feb 2022 22:45:09 +0200 Subject: [PATCH 09/15] Respect PreferParameterNullChecking user option (#59561) --- .../AddParameterCheckTests.cs | 47 +++++++++++++++++-- ...ddParameterCheckCodeRefactoringProvider.cs | 9 +++- ...ddParameterCheckCodeRefactoringProvider.cs | 4 +- ...ddParameterCheckCodeRefactoringProvider.vb | 4 +- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs index cc1287e100ecb..6c2f63b18d9a0 100644 --- a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs @@ -29,8 +29,10 @@ public async Task TestEmptyFile() await VerifyCS.VerifyRefactoringAsync(code, code); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] - public async Task TestSimpleReferenceType() + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + [InlineData("csharp_style_prefer_parameter_null_checking = true")] + [InlineData("")] + public async Task TestSimpleReferenceType(string editorConfig) { await new VerifyCS.Test { @@ -52,7 +54,46 @@ class C public C(string s!!) { } -}" +}", + EditorConfig = $@" +[*] +{editorConfig} +" + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestDoNotPreferParameterNullChecking() + { + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = @" +using System; + +class C +{ + public C([||]string s) + { + } +}", + FixedCode = @" +using System; + +class C +{ + public C(string s) + { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + } +}", + EditorConfig = @" +[*] +csharp_style_prefer_parameter_null_checking = false +", }.RunAsync(); } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs index fd114d1d3d3ac..50021c5381471 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs @@ -5,6 +5,7 @@ using System; using System.Composition; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -102,14 +103,18 @@ protected override StatementSyntax CreateParameterCheckIfStatement(DocumentOptio @else: null); } - protected override Document? TryAddNullCheckToParameterDeclaration(Document document, ParameterSyntax parameterSyntax, CancellationToken cancellationToken) + protected override async Task TryAddNullCheckToParameterDeclarationAsync(Document document, ParameterSyntax parameterSyntax, CancellationToken cancellationToken) { var tree = parameterSyntax.SyntaxTree; if (!tree.Options.LanguageVersion().IsCSharp11OrAbove()) return null; + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + if (!options.GetOption(CSharpCodeStyleOptions.PreferParameterNullChecking).Value) + return null; + // We expect the syntax tree to already be in memory since we already have a node from the tree - var syntaxRoot = tree.GetRoot(cancellationToken); + var syntaxRoot = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); syntaxRoot = syntaxRoot.ReplaceNode( parameterSyntax, parameterSyntax.WithExclamationExclamationToken(Token(SyntaxKind.ExclamationExclamationToken))); diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index 62c1143f6fcda..01ef17c1bd039 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -42,7 +42,7 @@ internal abstract partial class AbstractAddParameterCheckCodeRefactoringProvider protected abstract bool PrefersThrowExpression(DocumentOptionSet options); protected abstract string EscapeResourceString(string input); protected abstract TStatementSyntax CreateParameterCheckIfStatement(DocumentOptionSet options, TExpressionSyntax condition, TStatementSyntax ifTrueStatement); - protected abstract Document? TryAddNullCheckToParameterDeclaration(Document document, TParameterSyntax parameterSyntax, CancellationToken cancellationToken); + protected abstract Task TryAddNullCheckToParameterDeclarationAsync(Document document, TParameterSyntax parameterSyntax, CancellationToken cancellationToken); protected override async Task> GetRefactoringsForAllParametersAsync( Document document, @@ -331,7 +331,7 @@ private async Task AddNullCheckAsync( CancellationToken cancellationToken) { // First see if we can adopt the '!!' parameter null checking syntax. - var modifiedDocument = TryAddNullCheckToParameterDeclaration(document, parameterSyntax, cancellationToken); + var modifiedDocument = await TryAddNullCheckToParameterDeclarationAsync(document, parameterSyntax, cancellationToken).ConfigureAwait(false); if (modifiedDocument != null) { return modifiedDocument; diff --git a/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb index 8872ff80664a8..98d6344cad377 100644 --- a/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb @@ -72,8 +72,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter elseBlock:=Nothing) End Function - Protected Overrides Function TryAddNullCheckToParameterDeclaration(document As Document, parameterSyntax As ParameterSyntax, cancellationToken As CancellationToken) As Document - Return Nothing + Protected Overrides Function TryAddNullCheckToParameterDeclarationAsync(document As Document, parameterSyntax As ParameterSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Return Task.FromResult(Of Document)(Nothing) End Function End Class End Namespace From fb703600cc6e8ca32bb6db3ab0f6d0e4938028c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 16 Feb 2022 13:24:37 -0800 Subject: [PATCH 10/15] Move global options to EditorFeature layer (#59071) * ForceLowMemoryMode options * RemoteHostOptions --- .../Core/Remote}/RemoteHostOptions.cs | 24 ---------------- .../Core/Remote}/SolutionChecksumUpdater.cs | 0 .../VisualStudioRemoteHostClientProvider.cs | 8 ++++-- ...osoft.VisualStudio.LanguageServices.csproj | 1 - .../OptionPages/ForceLowMemoryMode.cs | 28 +++++++++++-------- .../OptionPages/ForceLowMemoryMode_Options.cs | 19 ------------- .../VisualStudioDiagnosticsWindowPackage.cs | 4 +-- .../Remote/InProcRemostHostClient.cs | 2 +- .../Api/UnitTestingRemoteHostClient.cs | 8 ------ .../Remote/Core/RemoteProcessConfiguration.cs | 22 +++++++++++++++ .../Remote/Core/ServiceDescriptors.cs | 20 ++++++++----- .../Remote/Core/ServiceHubRemoteHostClient.cs | 14 ++++------ .../RemoteAssetSynchronizationService.cs | 2 +- 13 files changed, 68 insertions(+), 84 deletions(-) rename src/{Workspaces/Remote/Core => EditorFeatures/Core/Remote}/RemoteHostOptions.cs (67%) rename src/{Workspaces/Remote/Core => EditorFeatures/Core/Remote}/SolutionChecksumUpdater.cs (100%) delete mode 100644 src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs create mode 100644 src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs diff --git a/src/Workspaces/Remote/Core/RemoteHostOptions.cs b/src/EditorFeatures/Core/Remote/RemoteHostOptions.cs similarity index 67% rename from src/Workspaces/Remote/Core/RemoteHostOptions.cs rename to src/EditorFeatures/Core/Remote/RemoteHostOptions.cs index 17f6a21e1c774..d97aef64a012a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostOptions.cs +++ b/src/EditorFeatures/Core/Remote/RemoteHostOptions.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options.Providers; @@ -35,11 +33,6 @@ internal sealed class RemoteHostOptions : IOptionProvider FeatureName, nameof(OOP64Bit), defaultValue: true, storageLocation: new LocalUserProfileStorageLocation(LocalRegistryPath + nameof(OOP64Bit))); - // use Server GC for 64-bit OOP - public static readonly Option2 OOPServerGC = new( - FeatureName, nameof(OOPServerGC), defaultValue: false, - storageLocation: new LocalUserProfileStorageLocation(LocalRegistryPath + nameof(OOPServerGC))); - public static readonly Option2 OOPServerGCFeatureFlag = new( FeatureName, nameof(OOPServerGCFeatureFlag), defaultValue: false, new FeatureFlagStorageLocation("Roslyn.OOPServerGC")); @@ -52,7 +45,6 @@ internal sealed class RemoteHostOptions : IOptionProvider ImmutableArray IOptionProvider.Options { get; } = ImmutableArray.Create( SolutionChecksumMonitorBackOffTimeSpanInMS, OOP64Bit, - OOPServerGC, OOPServerGCFeatureFlag, OOPCoreClrFeatureFlag); @@ -61,21 +53,5 @@ internal sealed class RemoteHostOptions : IOptionProvider public RemoteHostOptions() { } - - public static bool IsServiceHubProcessServerGC(IGlobalOptionService globalOptions) - => globalOptions.GetOption(OOPServerGC) || globalOptions.GetOption(OOPServerGCFeatureFlag); - - /// - /// Determines whether ServiceHub out-of-process execution is enabled for Roslyn. - /// - public static bool IsUsingServiceHubOutOfProcess(IGlobalOptionService globalOptions) - => Environment.Is64BitOperatingSystem && globalOptions.GetOption(OOP64Bit); - - public static bool IsServiceHubProcessCoreClr(IGlobalOptionService globalOptions) - => globalOptions.GetOption(OOPCoreClrFeatureFlag); - - public static bool IsCurrentProcessRunningOnCoreClr() - => !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework") && - !RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); } } diff --git a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs similarity index 100% rename from src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs rename to src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs b/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs index 280ed5877add2..9ef7d6e85938c 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/VisualStudioRemoteHostClientProvider.cs @@ -54,7 +54,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { // We don't want to bring up the OOP process in a VS cloud environment client instance // Avoids proffering brokered services on the client instance. - if (!RemoteHostOptions.IsUsingServiceHubOutOfProcess(_globalOptions) || + if (!_globalOptions.GetOption(RemoteHostOptions.OOP64Bit) || workspaceServices.Workspace is not VisualStudioWorkspace || workspaceServices.GetRequiredService().IsCloudEnvironmentClient()) { @@ -99,8 +99,12 @@ private VisualStudioRemoteHostClientProvider( var brokeredServiceContainer = await _vsServiceProvider.GetServiceAsync().ConfigureAwait(false); var serviceBroker = brokeredServiceContainer.GetFullAccessServiceBroker(); + var configuration = + (_globalOptions.GetOption(RemoteHostOptions.OOPCoreClrFeatureFlag) ? RemoteProcessConfiguration.Core : 0) | + (_globalOptions.GetOption(RemoteHostOptions.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0); + // VS AsyncLazy does not currently support cancellation: - var client = await ServiceHubRemoteHostClient.CreateAsync(_services, _globalOptions, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false); + var client = await ServiceHubRemoteHostClient.CreateAsync(_services, configuration, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false); // proffer in-proc brokered services: _ = brokeredServiceContainer.Proffer(SolutionAssetProvider.ServiceDescriptor, (_, _, _, _) => ValueTaskFactory.FromResult(new SolutionAssetProvider(_services))); diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 2dd33442b401e..efa1ddfddfdd6 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -64,7 +64,6 @@ - diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs index ff51b0672e50c..ec0b8b1dd46b6 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Threading; @@ -12,23 +10,31 @@ namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages { - internal sealed partial class ForceLowMemoryMode + internal sealed class ForceLowMemoryMode { - private readonly IOptionService _optionService; - private MemoryHogger _hogger; + private const string FeatureName = "ForceLowMemoryMode"; + + public static readonly Option2 Enabled = new(FeatureName, "Enabled", defaultValue: false, + storageLocation: new LocalUserProfileStorageLocation(@"Roslyn\ForceLowMemoryMode\Enabled")); + + public static readonly Option2 SizeInMegabytes = new(FeatureName, "SizeInMegabytes", defaultValue: 500, + storageLocation: new LocalUserProfileStorageLocation(@"Roslyn\ForceLowMemoryMode\SizeInMegabytes")); + + private readonly IGlobalOptionService _globalOptions; + private MemoryHogger? _hogger; - public ForceLowMemoryMode(IOptionService optionService) + public ForceLowMemoryMode(IGlobalOptionService globalOptions) { - _optionService = optionService; + _globalOptions = globalOptions; - optionService.OptionChanged += Options_OptionChanged; + globalOptions.OptionChanged += Options_OptionChanged; RefreshFromSettings(); } private void Options_OptionChanged(object sender, OptionChangedEventArgs e) { - if (e.Option.Feature == nameof(ForceLowMemoryMode)) + if (e.Option.Feature == FeatureName) { RefreshFromSettings(); } @@ -36,7 +42,7 @@ private void Options_OptionChanged(object sender, OptionChangedEventArgs e) private void RefreshFromSettings() { - var enabled = _optionService.GetOption(Enabled); + var enabled = _globalOptions.GetOption(Enabled); if (_hogger != null) { @@ -47,7 +53,7 @@ private void RefreshFromSettings() if (enabled) { _hogger = new MemoryHogger(); - _ = _hogger.PopulateAndMonitorAsync(_optionService.GetOption(SizeInMegabytes)); + _ = _hogger.PopulateAndMonitorAsync(_globalOptions.GetOption(SizeInMegabytes)); } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs deleted file mode 100644 index a529713238c60..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using Microsoft.CodeAnalysis.Options; - -namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages -{ - internal sealed partial class ForceLowMemoryMode - { - public static readonly Option2 Enabled = new(nameof(ForceLowMemoryMode), nameof(Enabled), defaultValue: false, - storageLocation: new LocalUserProfileStorageLocation(@"Roslyn\ForceLowMemoryMode\Enabled")); - - public static readonly Option2 SizeInMegabytes = new(nameof(ForceLowMemoryMode), nameof(SizeInMegabytes), defaultValue: 500, - storageLocation: new LocalUserProfileStorageLocation(@"Roslyn\ForceLowMemoryMode\SizeInMegabytes")); - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs index ab77128bea674..6d91c44f6f5d0 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs @@ -81,9 +81,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke Assumes.Present(menuCommandService); _threadingContext = componentModel.GetService(); + var globalOptions = componentModel.GetService(); _workspace = componentModel.GetService(); - _ = new ForceLowMemoryMode(_workspace.Services.GetService()); + _ = new ForceLowMemoryMode(globalOptions); // Add our command handlers for menu (commands must exist in the .vsct file) if (menuCommandService is OleMenuCommandService mcs) @@ -95,7 +96,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke } // set logger at start up - var globalOptions = componentModel.GetService(); PerformanceLoggersPage.SetLoggers(globalOptions, _threadingContext, _workspace.Services); } #endregion diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index d9ffbca4b7003..7aba10a48ba24 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -64,7 +64,7 @@ public RemoteWorkspace GetRemoteWorkspace() public override RemoteServiceConnection CreateConnection(object? callbackTarget) where T : class { - var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), isRemoteHostServerGC: GCSettings.IsServerGC, isRemoteHostCoreClr: RemoteHostOptions.IsCurrentProcessRunningOnCoreClr()); + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), RemoteProcessConfiguration.ServerGC | (ServiceDescriptors.IsCurrentProcessRunningOnCoreClr() ? RemoteProcessConfiguration.Core : 0)); var callbackDispatcher = (descriptor.ClientInterface != null) ? _callbackDispatchers.GetDispatcher(typeof(T)) : null; return new BrokeredServiceConnection( diff --git a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs index b031bf104c4c2..d74c0d32d26ac 100644 --- a/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ExternalAccess/UnitTesting/Api/UnitTestingRemoteHostClient.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api @@ -33,13 +32,6 @@ internal UnitTestingRemoteHostClient(ServiceHubRemoteHostClient client, UnitTest return new UnitTestingRemoteHostClient((ServiceHubRemoteHostClient)client, serviceDescriptors, callbackDispatchers); } - [Obsolete("Use UnitTestingGlobalOptions.IsServiceHubProcessCoreClr instead")] - public static bool IsServiceHubProcessCoreClr(HostWorkspaceServices services) - { - var optionServices = services.GetRequiredService(); - return optionServices.GetOption(RemoteHostOptions.OOPCoreClrFeatureFlag); - } - public UnitTestingRemoteServiceConnectionWrapper CreateConnection(object? callbackTarget) where TService : class => new(_client.CreateConnection(_serviceDescriptors.UnderlyingObject, _callbackDispatchers, callbackTarget)); diff --git a/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs b/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs new file mode 100644 index 0000000000000..aac8916b583d2 --- /dev/null +++ b/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Remote +{ + [Flags] + internal enum RemoteProcessConfiguration + { + /// + /// Remote host runs on .NET 6+. + /// + Core = 1, + + /// + /// Remote host uses server GC. + /// + ServerGC = 1 << 1 + } +} diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index 339d2fd811c94..f1388af0864f2 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CodeLens; @@ -120,18 +121,23 @@ internal static string GetSimpleName(Type serviceInterface) return (descriptor64, descriptor64ServerGC, descriptorCoreClr64, descriptorCoreClr64ServerGC); } + public static bool IsCurrentProcessRunningOnCoreClr() + => !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework") && + !RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); + public ServiceDescriptor GetServiceDescriptorForServiceFactory(Type serviceType) - => GetServiceDescriptor(serviceType, isRemoteHostServerGC: GCSettings.IsServerGC, isRemoteHostCoreClr: RemoteHostOptions.IsCurrentProcessRunningOnCoreClr()); + => GetServiceDescriptor(serviceType, RemoteProcessConfiguration.ServerGC | (IsCurrentProcessRunningOnCoreClr() ? RemoteProcessConfiguration.Core : 0)); - public ServiceDescriptor GetServiceDescriptor(Type serviceType, bool isRemoteHostServerGC, bool isRemoteHostCoreClr) + public ServiceDescriptor GetServiceDescriptor(Type serviceType, RemoteProcessConfiguration configuration) { var (descriptor64, descriptor64ServerGC, descriptorCoreClr64, descriptorCoreClr64ServerGC) = _descriptors[serviceType]; - return (isRemoteHostServerGC, isRemoteHostCoreClr) switch + return (configuration & (RemoteProcessConfiguration.Core | RemoteProcessConfiguration.ServerGC)) switch { - (false, false) => descriptor64, - (false, true) => descriptorCoreClr64, - (true, false) => descriptor64ServerGC, - (true, true) => descriptorCoreClr64ServerGC, + 0 => descriptor64, + RemoteProcessConfiguration.Core => descriptorCoreClr64, + RemoteProcessConfiguration.ServerGC => descriptor64ServerGC, + RemoteProcessConfiguration.Core | RemoteProcessConfiguration.ServerGC => descriptorCoreClr64ServerGC, + _ => throw ExceptionUtilities.Unreachable }; } diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index 3569e9de6c292..73769d50adacd 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -32,12 +32,11 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient private readonly IRemoteHostClientShutdownCancellationService? _shutdownCancellationService; private readonly IRemoteServiceCallbackDispatcherProvider _callbackDispatcherProvider; - private readonly bool _isRemoteHostServerGC; - private readonly bool _isRemoteHostCoreClr; + public readonly RemoteProcessConfiguration Configuration; private ServiceHubRemoteHostClient( HostWorkspaceServices services, - IGlobalOptionService globalOptions, + RemoteProcessConfiguration configuration, ServiceBrokerClient serviceBrokerClient, HubClient hubClient, IRemoteServiceCallbackDispatcherProvider callbackDispatcherProvider) @@ -53,13 +52,12 @@ private ServiceHubRemoteHostClient( _assetStorage = services.GetRequiredService().AssetStorage; _errorReportingService = services.GetService(); _shutdownCancellationService = services.GetService(); - _isRemoteHostServerGC = RemoteHostOptions.IsServiceHubProcessServerGC(globalOptions); - _isRemoteHostCoreClr = RemoteHostOptions.IsServiceHubProcessCoreClr(globalOptions); + Configuration = configuration; } public static async Task CreateAsync( HostWorkspaceServices services, - IGlobalOptionService globalOptions, + RemoteProcessConfiguration configuration, AsynchronousOperationListenerProvider listenerProvider, IServiceBroker serviceBroker, RemoteServiceCallbackDispatcherRegistry callbackDispatchers, @@ -74,7 +72,7 @@ public static async Task CreateAsync( var hubClient = new HubClient("ManagedLanguage.IDE.RemoteHostClient"); - var client = new ServiceHubRemoteHostClient(services, globalOptions, serviceBrokerClient, hubClient, callbackDispatchers); + var client = new ServiceHubRemoteHostClient(services, configuration, serviceBrokerClient, hubClient, callbackDispatchers); var syntaxTreeConfigurationService = services.GetService(); if (syntaxTreeConfigurationService != null) @@ -104,7 +102,7 @@ public override RemoteServiceConnection CreateConnection(object? callbackT /// internal RemoteServiceConnection CreateConnection(ServiceDescriptors descriptors, IRemoteServiceCallbackDispatcherProvider callbackDispatcherProvider, object? callbackTarget) where T : class { - var descriptor = descriptors.GetServiceDescriptor(typeof(T), _isRemoteHostServerGC, _isRemoteHostCoreClr); + var descriptor = descriptors.GetServiceDescriptor(typeof(T), Configuration); var callbackDispatcher = (descriptor.ClientInterface != null) ? callbackDispatcherProvider.GetDispatcher(typeof(T)) : null; return new BrokeredServiceConnection( diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 6f8a33d9e34ba..b410618048d0c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Remote { /// - /// This service is used by the to proactively update the solution snapshot in + /// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in /// the out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after an edit /// once a feature is asking for a snapshot. /// From b89ec3d4bde3ebffea2a820dd8742b9050df115f Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Wed, 16 Feb 2022 14:00:38 -0800 Subject: [PATCH 11/15] Move VisualStudioProject.BatchingDocumentCollection to it's own file This is just a direct invocation of the refactoring; no other work was done. --- ...tudioProject.BatchingDocumentCollection.cs | 623 ++++++++++++++++++ .../ProjectSystem/VisualStudioProject.cs | 603 +---------------- 2 files changed, 624 insertions(+), 602 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs new file mode 100644 index 0000000000000..fa83aa1b49ec3 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs @@ -0,0 +1,623 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal sealed partial class VisualStudioProject + { + /// + /// Helper class to manage collections of source-file like things; this exists just to avoid duplicating all the logic for regular source files + /// and additional files. + /// + /// This class should be free-threaded, and any synchronization is done via . + /// This class is otherwise free to operate on private members of if needed. + private sealed class BatchingDocumentCollection + { + private readonly VisualStudioProject _project; + + /// + /// The map of file paths to the underlying . This document may exist in or has been + /// pushed to the actual workspace. + /// + private readonly Dictionary _documentPathsToDocumentIds = new(StringComparer.OrdinalIgnoreCase); + + /// + /// A map of explicitly-added "always open" and their associated . This does not contain + /// any regular files that have been open. + /// + private IBidirectionalMap _sourceTextContainersToDocumentIds = BidirectionalMap.Empty; + + /// + /// The map of to whose got added into + /// + private readonly Dictionary _documentIdToDynamicFileInfoProvider = new(); + + /// + /// The current list of documents that are to be added in this batch. + /// + private readonly ImmutableArray.Builder _documentsAddedInBatch = ImmutableArray.CreateBuilder(); + + /// + /// The current list of documents that are being removed in this batch. Once the document is in this list, it is no longer in . + /// + private readonly List _documentsRemovedInBatch = new(); + + /// + /// The current list of document file paths that will be ordered in a batch. + /// + private ImmutableList? _orderedDocumentsInBatch = null; + + private readonly Func _documentAlreadyInWorkspace; + private readonly Action _documentAddAction; + private readonly Action _documentRemoveAction; + private readonly Func _documentTextLoaderChangedAction; + private readonly WorkspaceChangeKind _documentChangedWorkspaceKind; + + public BatchingDocumentCollection(VisualStudioProject project, + Func documentAlreadyInWorkspace, + Action documentAddAction, + Action documentRemoveAction, + Func documentTextLoaderChangedAction, + WorkspaceChangeKind documentChangedWorkspaceKind) + { + _project = project; + _documentAlreadyInWorkspace = documentAlreadyInWorkspace; + _documentAddAction = documentAddAction; + _documentRemoveAction = documentRemoveAction; + _documentTextLoaderChangedAction = documentTextLoaderChangedAction; + _documentChangedWorkspaceKind = documentChangedWorkspaceKind; + } + + public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + var documentId = DocumentId.CreateNewId(_project.Id, fullPath); + var textLoader = new FileTextLoader(fullPath, defaultEncoding: null); + var documentInfo = DocumentInfo.Create( + documentId, + FileNameUtilities.GetFileName(fullPath), + folders: folders.IsDefault ? null : folders, + sourceCodeKind: sourceCodeKind, + loader: textLoader, + filePath: fullPath, + isGenerated: false); + + using (_project._gate.DisposableWait()) + { + if (_documentPathsToDocumentIds.ContainsKey(fullPath)) + { + throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); + } + + // If we have an ordered document ids batch, we need to add the document id to the end of it as well. + _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); + + _documentPathsToDocumentIds.Add(fullPath, documentId); + _project._documentFileWatchingTokens.Add(documentId, _project._documentFileChangeContext.EnqueueWatchingFile(fullPath)); + + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); + _project._workspace.QueueCheckForFilesBeingOpen(ImmutableArray.Create(fullPath)); + } + } + + return documentId; + } + + public DocumentId AddTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders, bool designTimeOnly, IDocumentServiceProvider? documentServiceProvider) + { + if (textContainer == null) + { + throw new ArgumentNullException(nameof(textContainer)); + } + + var documentId = DocumentId.CreateNewId(_project.Id, fullPath); + var textLoader = new SourceTextLoader(textContainer, fullPath); + var documentInfo = DocumentInfo.Create( + documentId, + FileNameUtilities.GetFileName(fullPath), + folders: folders.NullToEmpty(), + sourceCodeKind: sourceCodeKind, + loader: textLoader, + filePath: fullPath, + isGenerated: false, + designTimeOnly: designTimeOnly, + documentServiceProvider: documentServiceProvider); + + using (_project._gate.DisposableWait()) + { + if (_sourceTextContainersToDocumentIds.ContainsKey(textContainer)) + { + throw new ArgumentException($"{nameof(textContainer)} is already added to this project.", nameof(textContainer)); + } + + if (fullPath != null) + { + if (_documentPathsToDocumentIds.ContainsKey(fullPath)) + { + throw new ArgumentException($"'{fullPath}' has already been added to this project."); + } + + _documentPathsToDocumentIds.Add(fullPath, documentId); + } + + _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.Add(textContainer, documentInfo.Id); + + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => + { + _project._workspace.AddDocumentToDocumentsNotFromFiles_NoLock(documentInfo.Id); + _documentAddAction(w, documentInfo); + w.OnDocumentOpened(documentInfo.Id, textContainer); + }); + } + } + + return documentId; + } + + public void AddDynamicFile_NoLock(IDynamicFileInfoProvider fileInfoProvider, DynamicFileInfo fileInfo, ImmutableArray folders) + { + Debug.Assert(_project._gate.CurrentCount == 0); + + var documentInfo = CreateDocumentInfoFromFileInfo(fileInfo, folders.NullToEmpty()); + + // Generally, DocumentInfo.FilePath can be null, but we always have file paths for dynamic files. + Contract.ThrowIfNull(documentInfo.FilePath); + var documentId = documentInfo.Id; + + var filePath = documentInfo.FilePath; + if (_documentPathsToDocumentIds.ContainsKey(filePath)) + { + throw new ArgumentException($"'{filePath}' has already been added to this project.", nameof(filePath)); + } + + // If we have an ordered document ids batch, we need to add the document id to the end of it as well. + _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); + + _documentPathsToDocumentIds.Add(filePath, documentId); + + _documentIdToDynamicFileInfoProvider.Add(documentId, fileInfoProvider); + + if (_project._eventSubscriptionTracker.Add(fileInfoProvider)) + { + // subscribe to the event when we use this provider the first time + fileInfoProvider.Updated += _project.OnDynamicFileInfoUpdated; + } + + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + // right now, assumption is dynamically generated file can never be opened in editor + _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); + } + } + + public IDynamicFileInfoProvider RemoveDynamicFile_NoLock(string fullPath) + { + Debug.Assert(_project._gate.CurrentCount == 0); + + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId) || + !_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)) + { + throw new ArgumentException($"'{fullPath}' is not a dynamic file of this project."); + } + + _documentIdToDynamicFileInfoProvider.Remove(documentId); + + RemoveFileInternal(documentId, fullPath); + + return fileInfoProvider; + } + + public void RemoveFile(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + using (_project._gate.DisposableWait()) + { + if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId)) + { + throw new ArgumentException($"'{fullPath}' is not a source file of this project."); + } + + _project._documentFileChangeContext.StopWatchingFile(_project._documentFileWatchingTokens[documentId]); + _project._documentFileWatchingTokens.Remove(documentId); + + RemoveFileInternal(documentId, fullPath); + } + } + + private void RemoveFileInternal(DocumentId documentId, string fullPath) + { + _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Remove(documentId); + _documentPathsToDocumentIds.Remove(fullPath); + + // There are two cases: + // + // 1. This file is actually been pushed to the workspace, and we need to remove it (either + // as a part of the active batch or immediately) + // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch + if (_documentAlreadyInWorkspace(_project._workspace.CurrentSolution, documentId)) + { + if (_project._activeBatchScopes > 0) + { + _documentsRemovedInBatch.Add(documentId); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => _documentRemoveAction(w, documentId)); + } + } + else + { + for (var i = 0; i < _documentsAddedInBatch.Count; i++) + { + if (_documentsAddedInBatch[i].Id == documentId) + { + _documentsAddedInBatch.RemoveAt(i); + break; + } + } + } + } + + public void RemoveTextContainer(SourceTextContainer textContainer) + { + if (textContainer == null) + { + throw new ArgumentNullException(nameof(textContainer)); + } + + using (_project._gate.DisposableWait()) + { + if (!_sourceTextContainersToDocumentIds.TryGetValue(textContainer, out var documentId)) + { + throw new ArgumentException($"{nameof(textContainer)} is not a text container added to this project."); + } + + _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.RemoveKey(textContainer); + + // if the TextContainer had a full path provided, remove it from the map. + var entry = _documentPathsToDocumentIds.Where(kv => kv.Value == documentId).FirstOrDefault(); + if (entry.Key != null) + { + _documentPathsToDocumentIds.Remove(entry.Key); + } + + // There are two cases: + // + // 1. This file is actually been pushed to the workspace, and we need to remove it (either + // as a part of the active batch or immediately) + // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch + if (_project._workspace.CurrentSolution.GetDocument(documentId) != null) + { + if (_project._activeBatchScopes > 0) + { + _documentsRemovedInBatch.Add(documentId); + } + else + { + _project._workspace.ApplyChangeToWorkspace(w => + { + // Just pass null for the filePath, since this document is immediately being removed + // anyways -- whatever we set won't really be read since the next change will + // come through. + // TODO: Can't we just remove the document without closing it? + w.OnDocumentClosed(documentId, new SourceTextLoader(textContainer, filePath: null)); + _documentRemoveAction(w, documentId); + _project._workspace.RemoveDocumentToDocumentsNotFromFiles_NoLock(documentId); + }); + } + } + else + { + for (var i = 0; i < _documentsAddedInBatch.Count; i++) + { + if (_documentsAddedInBatch[i].Id == documentId) + { + _documentsAddedInBatch.RemoveAt(i); + break; + } + } + } + } + } + + public bool ContainsFile(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); + } + + using (_project._gate.DisposableWait()) + { + return _documentPathsToDocumentIds.ContainsKey(fullPath); + } + } + + public async ValueTask ProcessRegularFileChangesAsync(ImmutableArray filePaths) + { + using (await _project._gate.DisposableWaitAsync().ConfigureAwait(false)) + { + // If our project has already been removed, this is a stale notification, and we can disregard. + if (_project.HasBeenRemoved) + { + return; + } + + var documentsToChange = ArrayBuilder<(DocumentId, TextLoader)>.GetInstance(filePaths.Length); + + foreach (var filePath in filePaths) + { + if (_documentPathsToDocumentIds.TryGetValue(filePath, out var documentId)) + { + // We create file watching prior to pushing the file to the workspace in batching, so it's + // possible we might see a file change notification early. In this case, toss it out. Since + // all adds/removals of documents for this project happen under our lock, it's safe to do this + // check without taking the main workspace lock. We don't have to check for documents removed in + // the batch, since those have already been removed out of _documentPathsToDocumentIds. + if (!_documentsAddedInBatch.Any(d => d.Id == documentId)) + { + documentsToChange.Add((documentId, new FileTextLoader(filePath, defaultEncoding: null))); + } + } + } + + // Nothing actually matched, so we're done + if (documentsToChange.Count == 0) + { + return; + } + + await _project._workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: true, s => + { + var accumulator = new SolutionChangeAccumulator(s); + + foreach (var (documentId, textLoader) in documentsToChange) + { + if (!s.Workspace.IsDocumentOpen(documentId)) + { + accumulator.UpdateSolutionForDocumentAction( + _documentTextLoaderChangedAction(accumulator.Solution, documentId, textLoader), + _documentChangedWorkspaceKind, + SpecializedCollections.SingletonEnumerable(documentId)); + } + } + + return accumulator; + }).ConfigureAwait(false); + + documentsToChange.Free(); + } + } + + /// + /// Process file content changes + /// + /// filepath given from project system + /// filepath used in workspace. it might be different than projectSystemFilePath + public void ProcessDynamicFileChange(string projectSystemFilePath, string workspaceFilePath) + { + using (_project._gate.DisposableWait()) + { + // If our project has already been removed, this is a stale notification, and we can disregard. + if (_project.HasBeenRemoved) + { + return; + } + + if (_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out var documentId)) + { + // We create file watching prior to pushing the file to the workspace in batching, so it's + // possible we might see a file change notification early. In this case, toss it out. Since + // all adds/removals of documents for this project happen under our lock, it's safe to do this + // check without taking the main workspace lock. We don't have to check for documents removed in + // the batch, since those have already been removed out of _documentPathsToDocumentIds. + if (_documentsAddedInBatch.Any(d => d.Id == documentId)) + { + return; + } + + Contract.ThrowIfFalse(_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)); + + _project._workspace.ApplyChangeToWorkspace(w => + { + if (w.IsDocumentOpen(documentId)) + { + return; + } + + // we do not expect JTF to be used around this code path. and contract of fileInfoProvider is it being real free-threaded + // meaning it can't use JTF to go back to UI thread. + // so, it is okay for us to call regular ".Result" on a task here. + var fileInfo = fileInfoProvider.GetDynamicFileInfoAsync( + _project.Id, _project._filePath, projectSystemFilePath, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); + + // Right now we're only supporting dynamic files as actual source files, so it's OK to call GetDocument here + var document = w.CurrentSolution.GetRequiredDocument(documentId); + + var documentInfo = DocumentInfo.Create( + document.Id, + document.Name, + document.Folders, + document.SourceCodeKind, + loader: fileInfo.TextLoader, + document.FilePath, + document.State.Attributes.IsGenerated, + document.State.Attributes.DesignTimeOnly, + documentServiceProvider: fileInfo.DocumentServiceProvider); + + w.OnDocumentReloaded(documentInfo); + }); + } + } + } + + public void ReorderFiles(ImmutableArray filePaths) + { + if (filePaths.IsEmpty) + { + throw new ArgumentOutOfRangeException("The specified files are empty.", nameof(filePaths)); + } + + using (_project._gate.DisposableWait()) + { + if (_documentPathsToDocumentIds.Count != filePaths.Length) + { + throw new ArgumentException("The specified files do not equal the project document count.", nameof(filePaths)); + } + + var documentIds = ImmutableList.CreateBuilder(); + + foreach (var filePath in filePaths) + { + if (_documentPathsToDocumentIds.TryGetValue(filePath, out var documentId)) + { + documentIds.Add(documentId); + } + else + { + throw new InvalidOperationException($"The file '{filePath}' does not exist in the project."); + } + } + + if (_project._activeBatchScopes > 0) + { + _orderedDocumentsInBatch = documentIds.ToImmutable(); + } + else + { + _project._workspace.ApplyChangeToWorkspace(_project.Id, solution => solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); + } + } + } + + internal void UpdateSolutionForBatch( + SolutionChangeAccumulator solutionChanges, + ImmutableArray.Builder documentFileNamesAdded, + List<(DocumentId documentId, SourceTextContainer textContainer)> documentsToOpen, + Func, Solution> addDocuments, + WorkspaceChangeKind addDocumentChangeKind, + Func, Solution> removeDocuments, + WorkspaceChangeKind removeDocumentChangeKind) + { + // Document adding... + solutionChanges.UpdateSolutionForDocumentAction( + newSolution: addDocuments(solutionChanges.Solution, _documentsAddedInBatch.ToImmutable()), + changeKind: addDocumentChangeKind, + documentIds: _documentsAddedInBatch.Select(d => d.Id)); + + foreach (var documentInfo in _documentsAddedInBatch) + { + Contract.ThrowIfNull(documentInfo.FilePath, "We shouldn't be adding documents without file paths."); + documentFileNamesAdded.Add(documentInfo.FilePath); + + if (_sourceTextContainersToDocumentIds.TryGetKey(documentInfo.Id, out var textContainer)) + { + documentsToOpen.Add((documentInfo.Id, textContainer)); + } + } + + ClearAndZeroCapacity(_documentsAddedInBatch); + + // Document removing... + solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()), + removeDocumentChangeKind, + _documentsRemovedInBatch); + + ClearAndZeroCapacity(_documentsRemovedInBatch); + + // Update project's order of documents. + if (_orderedDocumentsInBatch != null) + { + solutionChanges.UpdateSolutionForProjectAction( + _project.Id, + solutionChanges.Solution.WithProjectDocumentsOrder(_project.Id, _orderedDocumentsInBatch)); + _orderedDocumentsInBatch = null; + } + } + + private DocumentInfo CreateDocumentInfoFromFileInfo(DynamicFileInfo fileInfo, ImmutableArray folders) + { + Contract.ThrowIfTrue(folders.IsDefault); + + // we use this file path for editorconfig. + var filePath = fileInfo.FilePath; + + var name = FileNameUtilities.GetFileName(filePath); + var documentId = DocumentId.CreateNewId(_project.Id, filePath); + + var textLoader = fileInfo.TextLoader; + var documentServiceProvider = fileInfo.DocumentServiceProvider; + + return DocumentInfo.Create( + documentId, + name, + folders: folders, + sourceCodeKind: fileInfo.SourceCodeKind, + loader: textLoader, + filePath: filePath, + isGenerated: false, + designTimeOnly: true, + documentServiceProvider: documentServiceProvider); + } + + private sealed class SourceTextLoader : TextLoader + { + private readonly SourceTextContainer _textContainer; + private readonly string? _filePath; + + public SourceTextLoader(SourceTextContainer textContainer, string? filePath) + { + _textContainer = textContainer; + _filePath = filePath; + } + + public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) + => Task.FromResult(TextAndVersion.Create(_textContainer.CurrentText, VersionStamp.Create(), _filePath)); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index a477703d61ba2..2f2373de209ce 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; @@ -24,7 +23,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - internal sealed class VisualStudioProject + internal sealed partial class VisualStudioProject { private static readonly ImmutableArray s_defaultMetadataReferenceProperties = ImmutableArray.Create(default(MetadataReferenceProperties)); @@ -1305,605 +1304,5 @@ private static void ClearAndZeroCapacity(ImmutableArray.Builder list) list.Clear(); list.Capacity = 0; } - - /// - /// Helper class to manage collections of source-file like things; this exists just to avoid duplicating all the logic for regular source files - /// and additional files. - /// - /// This class should be free-threaded, and any synchronization is done via . - /// This class is otherwise free to operate on private members of if needed. - private sealed class BatchingDocumentCollection - { - private readonly VisualStudioProject _project; - - /// - /// The map of file paths to the underlying . This document may exist in or has been - /// pushed to the actual workspace. - /// - private readonly Dictionary _documentPathsToDocumentIds = new(StringComparer.OrdinalIgnoreCase); - - /// - /// A map of explicitly-added "always open" and their associated . This does not contain - /// any regular files that have been open. - /// - private IBidirectionalMap _sourceTextContainersToDocumentIds = BidirectionalMap.Empty; - - /// - /// The map of to whose got added into - /// - private readonly Dictionary _documentIdToDynamicFileInfoProvider = new(); - - /// - /// The current list of documents that are to be added in this batch. - /// - private readonly ImmutableArray.Builder _documentsAddedInBatch = ImmutableArray.CreateBuilder(); - - /// - /// The current list of documents that are being removed in this batch. Once the document is in this list, it is no longer in . - /// - private readonly List _documentsRemovedInBatch = new(); - - /// - /// The current list of document file paths that will be ordered in a batch. - /// - private ImmutableList? _orderedDocumentsInBatch = null; - - private readonly Func _documentAlreadyInWorkspace; - private readonly Action _documentAddAction; - private readonly Action _documentRemoveAction; - private readonly Func _documentTextLoaderChangedAction; - private readonly WorkspaceChangeKind _documentChangedWorkspaceKind; - - public BatchingDocumentCollection(VisualStudioProject project, - Func documentAlreadyInWorkspace, - Action documentAddAction, - Action documentRemoveAction, - Func documentTextLoaderChangedAction, - WorkspaceChangeKind documentChangedWorkspaceKind) - { - _project = project; - _documentAlreadyInWorkspace = documentAlreadyInWorkspace; - _documentAddAction = documentAddAction; - _documentRemoveAction = documentRemoveAction; - _documentTextLoaderChangedAction = documentTextLoaderChangedAction; - _documentChangedWorkspaceKind = documentChangedWorkspaceKind; - } - - public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders) - { - if (string.IsNullOrEmpty(fullPath)) - { - throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); - } - - var documentId = DocumentId.CreateNewId(_project.Id, fullPath); - var textLoader = new FileTextLoader(fullPath, defaultEncoding: null); - var documentInfo = DocumentInfo.Create( - documentId, - FileNameUtilities.GetFileName(fullPath), - folders: folders.IsDefault ? null : folders, - sourceCodeKind: sourceCodeKind, - loader: textLoader, - filePath: fullPath, - isGenerated: false); - - using (_project._gate.DisposableWait()) - { - if (_documentPathsToDocumentIds.ContainsKey(fullPath)) - { - throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); - } - - // If we have an ordered document ids batch, we need to add the document id to the end of it as well. - _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); - - _documentPathsToDocumentIds.Add(fullPath, documentId); - _project._documentFileWatchingTokens.Add(documentId, _project._documentFileChangeContext.EnqueueWatchingFile(fullPath)); - - if (_project._activeBatchScopes > 0) - { - _documentsAddedInBatch.Add(documentInfo); - } - else - { - _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); - _project._workspace.QueueCheckForFilesBeingOpen(ImmutableArray.Create(fullPath)); - } - } - - return documentId; - } - - public DocumentId AddTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders, bool designTimeOnly, IDocumentServiceProvider? documentServiceProvider) - { - if (textContainer == null) - { - throw new ArgumentNullException(nameof(textContainer)); - } - - var documentId = DocumentId.CreateNewId(_project.Id, fullPath); - var textLoader = new SourceTextLoader(textContainer, fullPath); - var documentInfo = DocumentInfo.Create( - documentId, - FileNameUtilities.GetFileName(fullPath), - folders: folders.NullToEmpty(), - sourceCodeKind: sourceCodeKind, - loader: textLoader, - filePath: fullPath, - isGenerated: false, - designTimeOnly: designTimeOnly, - documentServiceProvider: documentServiceProvider); - - using (_project._gate.DisposableWait()) - { - if (_sourceTextContainersToDocumentIds.ContainsKey(textContainer)) - { - throw new ArgumentException($"{nameof(textContainer)} is already added to this project.", nameof(textContainer)); - } - - if (fullPath != null) - { - if (_documentPathsToDocumentIds.ContainsKey(fullPath)) - { - throw new ArgumentException($"'{fullPath}' has already been added to this project."); - } - - _documentPathsToDocumentIds.Add(fullPath, documentId); - } - - _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.Add(textContainer, documentInfo.Id); - - if (_project._activeBatchScopes > 0) - { - _documentsAddedInBatch.Add(documentInfo); - } - else - { - _project._workspace.ApplyChangeToWorkspace(w => - { - _project._workspace.AddDocumentToDocumentsNotFromFiles_NoLock(documentInfo.Id); - _documentAddAction(w, documentInfo); - w.OnDocumentOpened(documentInfo.Id, textContainer); - }); - } - } - - return documentId; - } - - public void AddDynamicFile_NoLock(IDynamicFileInfoProvider fileInfoProvider, DynamicFileInfo fileInfo, ImmutableArray folders) - { - Debug.Assert(_project._gate.CurrentCount == 0); - - var documentInfo = CreateDocumentInfoFromFileInfo(fileInfo, folders.NullToEmpty()); - - // Generally, DocumentInfo.FilePath can be null, but we always have file paths for dynamic files. - Contract.ThrowIfNull(documentInfo.FilePath); - var documentId = documentInfo.Id; - - var filePath = documentInfo.FilePath; - if (_documentPathsToDocumentIds.ContainsKey(filePath)) - { - throw new ArgumentException($"'{filePath}' has already been added to this project.", nameof(filePath)); - } - - // If we have an ordered document ids batch, we need to add the document id to the end of it as well. - _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); - - _documentPathsToDocumentIds.Add(filePath, documentId); - - _documentIdToDynamicFileInfoProvider.Add(documentId, fileInfoProvider); - - if (_project._eventSubscriptionTracker.Add(fileInfoProvider)) - { - // subscribe to the event when we use this provider the first time - fileInfoProvider.Updated += _project.OnDynamicFileInfoUpdated; - } - - if (_project._activeBatchScopes > 0) - { - _documentsAddedInBatch.Add(documentInfo); - } - else - { - // right now, assumption is dynamically generated file can never be opened in editor - _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); - } - } - - public IDynamicFileInfoProvider RemoveDynamicFile_NoLock(string fullPath) - { - Debug.Assert(_project._gate.CurrentCount == 0); - - if (string.IsNullOrEmpty(fullPath)) - { - throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); - } - - if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId) || - !_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)) - { - throw new ArgumentException($"'{fullPath}' is not a dynamic file of this project."); - } - - _documentIdToDynamicFileInfoProvider.Remove(documentId); - - RemoveFileInternal(documentId, fullPath); - - return fileInfoProvider; - } - - public void RemoveFile(string fullPath) - { - if (string.IsNullOrEmpty(fullPath)) - { - throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); - } - - using (_project._gate.DisposableWait()) - { - if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId)) - { - throw new ArgumentException($"'{fullPath}' is not a source file of this project."); - } - - _project._documentFileChangeContext.StopWatchingFile(_project._documentFileWatchingTokens[documentId]); - _project._documentFileWatchingTokens.Remove(documentId); - - RemoveFileInternal(documentId, fullPath); - } - } - - private void RemoveFileInternal(DocumentId documentId, string fullPath) - { - _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Remove(documentId); - _documentPathsToDocumentIds.Remove(fullPath); - - // There are two cases: - // - // 1. This file is actually been pushed to the workspace, and we need to remove it (either - // as a part of the active batch or immediately) - // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch - if (_documentAlreadyInWorkspace(_project._workspace.CurrentSolution, documentId)) - { - if (_project._activeBatchScopes > 0) - { - _documentsRemovedInBatch.Add(documentId); - } - else - { - _project._workspace.ApplyChangeToWorkspace(w => _documentRemoveAction(w, documentId)); - } - } - else - { - for (var i = 0; i < _documentsAddedInBatch.Count; i++) - { - if (_documentsAddedInBatch[i].Id == documentId) - { - _documentsAddedInBatch.RemoveAt(i); - break; - } - } - } - } - - public void RemoveTextContainer(SourceTextContainer textContainer) - { - if (textContainer == null) - { - throw new ArgumentNullException(nameof(textContainer)); - } - - using (_project._gate.DisposableWait()) - { - if (!_sourceTextContainersToDocumentIds.TryGetValue(textContainer, out var documentId)) - { - throw new ArgumentException($"{nameof(textContainer)} is not a text container added to this project."); - } - - _sourceTextContainersToDocumentIds = _sourceTextContainersToDocumentIds.RemoveKey(textContainer); - - // if the TextContainer had a full path provided, remove it from the map. - var entry = _documentPathsToDocumentIds.Where(kv => kv.Value == documentId).FirstOrDefault(); - if (entry.Key != null) - { - _documentPathsToDocumentIds.Remove(entry.Key); - } - - // There are two cases: - // - // 1. This file is actually been pushed to the workspace, and we need to remove it (either - // as a part of the active batch or immediately) - // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch - if (_project._workspace.CurrentSolution.GetDocument(documentId) != null) - { - if (_project._activeBatchScopes > 0) - { - _documentsRemovedInBatch.Add(documentId); - } - else - { - _project._workspace.ApplyChangeToWorkspace(w => - { - // Just pass null for the filePath, since this document is immediately being removed - // anyways -- whatever we set won't really be read since the next change will - // come through. - // TODO: Can't we just remove the document without closing it? - w.OnDocumentClosed(documentId, new SourceTextLoader(textContainer, filePath: null)); - _documentRemoveAction(w, documentId); - _project._workspace.RemoveDocumentToDocumentsNotFromFiles_NoLock(documentId); - }); - } - } - else - { - for (var i = 0; i < _documentsAddedInBatch.Count; i++) - { - if (_documentsAddedInBatch[i].Id == documentId) - { - _documentsAddedInBatch.RemoveAt(i); - break; - } - } - } - } - } - - public bool ContainsFile(string fullPath) - { - if (string.IsNullOrEmpty(fullPath)) - { - throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); - } - - using (_project._gate.DisposableWait()) - { - return _documentPathsToDocumentIds.ContainsKey(fullPath); - } - } - - public async ValueTask ProcessRegularFileChangesAsync(ImmutableArray filePaths) - { - using (await _project._gate.DisposableWaitAsync().ConfigureAwait(false)) - { - // If our project has already been removed, this is a stale notification, and we can disregard. - if (_project.HasBeenRemoved) - { - return; - } - - var documentsToChange = ArrayBuilder<(DocumentId, TextLoader)>.GetInstance(filePaths.Length); - - foreach (var filePath in filePaths) - { - if (_documentPathsToDocumentIds.TryGetValue(filePath, out var documentId)) - { - // We create file watching prior to pushing the file to the workspace in batching, so it's - // possible we might see a file change notification early. In this case, toss it out. Since - // all adds/removals of documents for this project happen under our lock, it's safe to do this - // check without taking the main workspace lock. We don't have to check for documents removed in - // the batch, since those have already been removed out of _documentPathsToDocumentIds. - if (!_documentsAddedInBatch.Any(d => d.Id == documentId)) - { - documentsToChange.Add((documentId, new FileTextLoader(filePath, defaultEncoding: null))); - } - } - } - - // Nothing actually matched, so we're done - if (documentsToChange.Count == 0) - { - return; - } - - await _project._workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: true, s => - { - var accumulator = new SolutionChangeAccumulator(s); - - foreach (var (documentId, textLoader) in documentsToChange) - { - if (!s.Workspace.IsDocumentOpen(documentId)) - { - accumulator.UpdateSolutionForDocumentAction( - _documentTextLoaderChangedAction(accumulator.Solution, documentId, textLoader), - _documentChangedWorkspaceKind, - SpecializedCollections.SingletonEnumerable(documentId)); - } - } - - return accumulator; - }).ConfigureAwait(false); - - documentsToChange.Free(); - } - } - - /// - /// Process file content changes - /// - /// filepath given from project system - /// filepath used in workspace. it might be different than projectSystemFilePath - public void ProcessDynamicFileChange(string projectSystemFilePath, string workspaceFilePath) - { - using (_project._gate.DisposableWait()) - { - // If our project has already been removed, this is a stale notification, and we can disregard. - if (_project.HasBeenRemoved) - { - return; - } - - if (_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out var documentId)) - { - // We create file watching prior to pushing the file to the workspace in batching, so it's - // possible we might see a file change notification early. In this case, toss it out. Since - // all adds/removals of documents for this project happen under our lock, it's safe to do this - // check without taking the main workspace lock. We don't have to check for documents removed in - // the batch, since those have already been removed out of _documentPathsToDocumentIds. - if (_documentsAddedInBatch.Any(d => d.Id == documentId)) - { - return; - } - - Contract.ThrowIfFalse(_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)); - - _project._workspace.ApplyChangeToWorkspace(w => - { - if (w.IsDocumentOpen(documentId)) - { - return; - } - - // we do not expect JTF to be used around this code path. and contract of fileInfoProvider is it being real free-threaded - // meaning it can't use JTF to go back to UI thread. - // so, it is okay for us to call regular ".Result" on a task here. - var fileInfo = fileInfoProvider.GetDynamicFileInfoAsync( - _project.Id, _project._filePath, projectSystemFilePath, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); - - // Right now we're only supporting dynamic files as actual source files, so it's OK to call GetDocument here - var document = w.CurrentSolution.GetRequiredDocument(documentId); - - var documentInfo = DocumentInfo.Create( - document.Id, - document.Name, - document.Folders, - document.SourceCodeKind, - loader: fileInfo.TextLoader, - document.FilePath, - document.State.Attributes.IsGenerated, - document.State.Attributes.DesignTimeOnly, - documentServiceProvider: fileInfo.DocumentServiceProvider); - - w.OnDocumentReloaded(documentInfo); - }); - } - } - } - - public void ReorderFiles(ImmutableArray filePaths) - { - if (filePaths.IsEmpty) - { - throw new ArgumentOutOfRangeException("The specified files are empty.", nameof(filePaths)); - } - - using (_project._gate.DisposableWait()) - { - if (_documentPathsToDocumentIds.Count != filePaths.Length) - { - throw new ArgumentException("The specified files do not equal the project document count.", nameof(filePaths)); - } - - var documentIds = ImmutableList.CreateBuilder(); - - foreach (var filePath in filePaths) - { - if (_documentPathsToDocumentIds.TryGetValue(filePath, out var documentId)) - { - documentIds.Add(documentId); - } - else - { - throw new InvalidOperationException($"The file '{filePath}' does not exist in the project."); - } - } - - if (_project._activeBatchScopes > 0) - { - _orderedDocumentsInBatch = documentIds.ToImmutable(); - } - else - { - _project._workspace.ApplyChangeToWorkspace(_project.Id, solution => solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); - } - } - } - - internal void UpdateSolutionForBatch( - SolutionChangeAccumulator solutionChanges, - ImmutableArray.Builder documentFileNamesAdded, - List<(DocumentId documentId, SourceTextContainer textContainer)> documentsToOpen, - Func, Solution> addDocuments, - WorkspaceChangeKind addDocumentChangeKind, - Func, Solution> removeDocuments, - WorkspaceChangeKind removeDocumentChangeKind) - { - // Document adding... - solutionChanges.UpdateSolutionForDocumentAction( - newSolution: addDocuments(solutionChanges.Solution, _documentsAddedInBatch.ToImmutable()), - changeKind: addDocumentChangeKind, - documentIds: _documentsAddedInBatch.Select(d => d.Id)); - - foreach (var documentInfo in _documentsAddedInBatch) - { - Contract.ThrowIfNull(documentInfo.FilePath, "We shouldn't be adding documents without file paths."); - documentFileNamesAdded.Add(documentInfo.FilePath); - - if (_sourceTextContainersToDocumentIds.TryGetKey(documentInfo.Id, out var textContainer)) - { - documentsToOpen.Add((documentInfo.Id, textContainer)); - } - } - - ClearAndZeroCapacity(_documentsAddedInBatch); - - // Document removing... - solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()), - removeDocumentChangeKind, - _documentsRemovedInBatch); - - ClearAndZeroCapacity(_documentsRemovedInBatch); - - // Update project's order of documents. - if (_orderedDocumentsInBatch != null) - { - solutionChanges.UpdateSolutionForProjectAction( - _project.Id, - solutionChanges.Solution.WithProjectDocumentsOrder(_project.Id, _orderedDocumentsInBatch)); - _orderedDocumentsInBatch = null; - } - } - - private DocumentInfo CreateDocumentInfoFromFileInfo(DynamicFileInfo fileInfo, ImmutableArray folders) - { - Contract.ThrowIfTrue(folders.IsDefault); - - // we use this file path for editorconfig. - var filePath = fileInfo.FilePath; - - var name = FileNameUtilities.GetFileName(filePath); - var documentId = DocumentId.CreateNewId(_project.Id, filePath); - - var textLoader = fileInfo.TextLoader; - var documentServiceProvider = fileInfo.DocumentServiceProvider; - - return DocumentInfo.Create( - documentId, - name, - folders: folders, - sourceCodeKind: fileInfo.SourceCodeKind, - loader: textLoader, - filePath: filePath, - isGenerated: false, - designTimeOnly: true, - documentServiceProvider: documentServiceProvider); - } - - private sealed class SourceTextLoader : TextLoader - { - private readonly SourceTextContainer _textContainer; - private readonly string? _filePath; - - public SourceTextLoader(SourceTextContainer textContainer, string? filePath) - { - _textContainer = textContainer; - _filePath = filePath; - } - - public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - => Task.FromResult(TextAndVersion.Create(_textContainer.CurrentText, VersionStamp.Create(), _filePath)); - } - } } } From 07b1ca289d2da49ae2f01360b49216f2a3c2ce21 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 16 Feb 2022 14:26:15 -0800 Subject: [PATCH 12/15] Update breaking changes document with 17.0 and 17.1 fixes (#59245) --- .../Compiler Breaking Changes - DotNet 6.md | 38 +++++++++++++++---- .../Compiler Breaking Changes - DotNet 7.md | 31 ++++++++++++--- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md index 478fca0892c81..c58cc754a93ff 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md @@ -1,6 +1,7 @@ ## This document lists known breaking changes in Roslyn in C# 10.0 which will be introduced with .NET 6. -1. Beginning with C# 10.0, null suppression operator is no longer allowed in patterns. +1. Beginning with C# 10.0, null suppression operator is no longer allowed in patterns. + ```csharp void M(object o) { @@ -8,7 +9,7 @@ } ``` -2. In C# 10, lambda expressions and method groups with inferred type are implicitly convertible to `System.MulticastDelegate`, and bases classes and interfaces of `System.MulticastDelegate` including `object`, +2. In C# 10, lambda expressions and method groups with inferred type are implicitly convertible to `System.MulticastDelegate`, and bases classes and interfaces of `System.MulticastDelegate` including `object`, and lambda expressions and method groups are implicitly convertible to `System.Linq.Expressions.Expression` and `System.Linq.Expressions.LambdaExpression`. These are _function_type_conversions_. @@ -92,7 +93,7 @@ These are _function_type_conversions_. } ``` -3. In C#10, a lambda expression with inferred type may contribute an argument type that affects overload resolution. +3. In C#10, a lambda expression with inferred type may contribute an argument type that affects overload resolution. ```csharp using System; @@ -109,18 +110,38 @@ These are _function_type_conversions_. } ``` -4. In Visual Studio 17.1, `struct` type declarations with field initializers must include an explicitly declared constructor. Additionally, all fields must be definitely assigned in `struct` instance constructors that do not have a `: this()` initializer so any previously unassigned fields must be assigned from the added constructor or from field initializers. +4. In Visual Studio 17.0 servicing, an error is reported in a `record struct` with a primary constructor if an explicit constructor has a `this()` initializer that invokes the implicit parameterless constructor. See [roslyn#58339](https://github.com/dotnet/roslyn/pull/58339). - For instance, the following results in an error in 17.1: + For instance, the following results in an error: ```csharp - struct S + record struct R(int X, int Y) + { + // error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' + // initializer that calls the primary constructor or an explicitly declared constructor. + public R(int x) : this() { X = x; Y = 0; } + } + ``` + + The error could be resolved by invoking the primary constructor (as below) from the `this()` initializer, or by declaring a parameterless constructor that invokes the primary constructor. + ```csharp + record struct R(int X, int Y) { - int X = 1; // error: struct with field initializers must include an explicitly declared constructor + public R(int x) : this(x, 0) { } // ok + } + ``` + +5. In Visual Studio 17.0 servicing, if a `struct` type declaration with no constructors includes initializers for some but not all fields, the compiler will report an error that all fields must be assigned. See [roslyn#57925](https://github.com/dotnet/roslyn/pull/57925). + + For instance, the following results in an error: + ```csharp + struct S // error CS0171: Field 'S.Y' must be fully assigned before control is returned to the caller + { + int X = 1; int Y; } ``` - The error could be resolved by adding a constructor and assigning the other field. + For compatibility with 17.1 (see [#6](#6)), the error should be resolved by adding a constructor and assigning the other field. ```csharp struct S { @@ -129,3 +150,4 @@ These are _function_type_conversions_. public S() { Y = 0; } // ok } ``` + diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index acfba8bddc569..c152a61951ad2 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -1,6 +1,6 @@ ## This document lists known breaking changes in Roslyn after .NET 6 all the way to .NET 7. -1. In Visual Studio 17.1, the contextual keyword `var` cannot be used as an explicit lambda return type. +1. In Visual Studio 17.1, the contextual keyword `var` cannot be used as an explicit lambda return type. ```csharp using System; @@ -13,7 +13,7 @@ class var { } ``` -2. In Visual Studio 17.1, indexers that take an interpolated string handler and require the receiver as an input for the constructor cannot be used in an object initializer. +2. In Visual Studio 17.1, indexers that take an interpolated string handler and require the receiver as an input for the constructor cannot be used in an object initializer. ```cs using System.Runtime.CompilerServices; @@ -35,7 +35,7 @@ } ``` -3. In Visual Studio 17.1, `ref`/`ref readonly`/`in`/`out` are not allowed to be used on return/parameters of a method attributed with `UnmanagedCallersOnly`. +3. In Visual Studio 17.1, `ref`/`ref readonly`/`in`/`out` are not allowed to be used on return/parameters of a method attributed with `UnmanagedCallersOnly`. https://github.com/dotnet/roslyn/issues/57025 ```cs @@ -56,7 +56,7 @@ https://github.com/dotnet/roslyn/issues/57025 static void M5(out int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. ``` -4. Beginning with C# 11.0, `Length` and `Count` properties on countable and indexable types +4. Beginning with C# 11.0, `Length` and `Count` properties on countable and indexable types are assumed to be non-negative for purpose of subsumption and exhaustiveness analysis of patterns and switches. Those types can be used with implicit Index indexer and list patterns. @@ -67,7 +67,7 @@ Those types can be used with implicit Index indexer and list patterns. } ``` -5. Starting with Visual Studio 17.1, format specifiers in interpolated strings can not contain curly braces (either `{` or `}`). In previous versions `{{` was interpreted as an escaped `{` and `}}` was interpreted as an escaped `}` char in the format specifier. Now the first `}` char in a format specifier ends the interpolation, and any `{` char is an error. +5. Starting with Visual Studio 17.1, format specifiers in interpolated strings can not contain curly braces (either `{` or `}`). In previous versions `{{` was interpreted as an escaped `{` and `}}` was interpreted as an escaped `}` char in the format specifier. Now the first `}` char in a format specifier ends the interpolation, and any `{` char is an error. https://github.com/dotnet/roslyn/issues/57750 ```csharp @@ -77,3 +77,24 @@ https://github.com/dotnet/roslyn/issues/57750 //prints now: "{C}" - not "{X}}" ``` + +6. In Visual Studio 17.1, `struct` type declarations with field initializers must include an explicitly declared constructor. Additionally, all fields must be definitely assigned in `struct` instance constructors that do not have a `: this()` initializer so any previously unassigned fields must be assigned from the added constructor or from field initializers. See [csharplang#5552](https://github.com/dotnet/csharplang/issues/5552), [roslyn#58581](https://github.com/dotnet/roslyn/pull/58581). + + For instance, the following results in an error in 17.1: + ```csharp + struct S + { + int X = 1; // error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. + int Y; + } + ``` + + The error could be resolved by adding a constructor and assigning the other field. + ```csharp + struct S + { + int X = 1; + int Y; + public S() { Y = 0; } // ok + } + ``` From b452e2713ffa5df79059a97f53b0f453fc30e864 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 16 Feb 2022 15:17:10 -0800 Subject: [PATCH 13/15] Fix typos (#59237) --- .../Declarations/SingleTypeDeclaration.cs | 2 +- .../Portable/Parser/Lexer_StringLiteral.cs | 2 +- .../CSharp/Test/CommandLine/CommandLineTests.cs | 2 +- .../Emit/EditAndContinue/EditAndContinueTests.cs | 2 +- .../CodeGenMethodGroupConversionCachingTests.cs | 4 ++-- .../Semantic/FlowAnalysis/RegionAnalysisTests.cs | 4 ++-- .../Test/Semantic/Semantics/DelegateTypeTests.cs | 16 ++++++++-------- .../Test/Semantic/Semantics/RecordStructTests.cs | 2 +- .../AdditionalSourcesCollectionTests.cs | 2 +- .../Core/Portable/Compilation/Compilation.cs | 2 +- .../RebuildTest/DeterministicKeyBuilderTests.cs | 2 +- src/Compilers/Shared/BuildServerConnection.cs | 2 +- .../IOperation/IOperationTests_IArgument.vb | 2 +- 13 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs index c7ec229bc4ba1..3c2a209adf7e8 100644 --- a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs @@ -26,7 +26,7 @@ internal sealed class SingleTypeDeclaration : SingleNamespaceOrTypeDeclaration /// through a using alias in the file. For example /// using X = System.Runtime.CompilerServices.TypeForwardedToAttribute or /// [TypeForwardedToAttribute]. Can be used to avoid having to go back to source - /// to retrieve attributes whtn there is no chance they would bind to attribute of interest. + /// to retrieve attributes when there is no chance they would bind to attribute of interest. /// public QuickAttributes QuickAttributes { get; } diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs index 92ff794273d83..c3955e0eb6a55 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs @@ -708,7 +708,7 @@ private bool IsAtEndOfMultiLineRawLiteral(InterpolatedStringKind kind, int start /// /// Returns if the quote was an end delimiter and lexing of the contents of the - /// interpolated string literal should stop. If it was an end delimeter it will not be consumed. If it is + /// interpolated string literal should stop. If it was an end delimiter it will not be consumed. If it is /// content and should not terminate the string then it will be consumed by this method. /// private bool IsEndDelimiterOtherwiseConsume(InterpolatedStringKind kind, int startingQuoteCount) diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 206f936c3a5ff..906f531d66a1e 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -1411,7 +1411,7 @@ public void ModuleManifest() } // The following test is failing in the Linux Debug test leg of CI. - // This issus is being tracked by https://github.com/dotnet/roslyn/issues/58077 + // This issue is being tracked by https://github.com/dotnet/roslyn/issues/58077 [ConditionalFact(typeof(WindowsOrMacOSOnly))] public void ArgumentParsing() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index cd7b7e55ad9f2..efd4dea68e945 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -1622,7 +1622,7 @@ static unsafe void F() where U : unmanaged } [Fact] - public void Lambda_SynthesizedDeletage_06() + public void Lambda_SynthesizedDelegate_06() { var source0 = MarkedSource( @"class C diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenMethodGroupConversionCachingTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenMethodGroupConversionCachingTests.cs index 74c56ba932660..3952de8208ec4 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenMethodGroupConversionCachingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenMethodGroupConversionCachingTests.cs @@ -252,7 +252,7 @@ .maxstack 4 } [Fact] - public void Not_InExpressionLamba0() + public void Not_InExpressionLambda0() { var source = @" using System; @@ -329,7 +329,7 @@ .locals init (System.Linq.Expressions.ParameterExpression V_0) } [Fact] - public void Not_InExpressionLamba1() + public void Not_InExpressionLambda1() { var source = @" using System; diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index e2423f12d9a39..4d78afa4814d8 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -2618,7 +2618,7 @@ public static void M(C? c) #endregion - #region "constructor initalizer" + #region "constructor initializer" [Fact] public void TestDataFlowsInCtorInitPublicApi() { @@ -2757,7 +2757,7 @@ class C } #endregion - #region "primary constructor initalizer" + #region "primary constructor initializer" [Fact] public void TestDataFlowsInPrimaryCtorInitPublicApi() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index a8145b8774058..e9ad802ad31d1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -3264,9 +3264,9 @@ interface IRouteBuilder } static class AppBuilderExtensions { - public static IAppBuilder Map(this IAppBuilder app, PathSring path, Action callback) + public static IAppBuilder Map(this IAppBuilder app, PathString path, Action callback) { - Console.WriteLine(""AppBuilderExtensions.Map(this IAppBuilder app, PathSring path, Action callback)""); + Console.WriteLine(""AppBuilderExtensions.Map(this IAppBuilder app, PathString path, Action callback)""); return app; } } @@ -3278,20 +3278,20 @@ public static IRouteBuilder Map(this IRouteBuilder routes, string path, Delegate return routes; } } -struct PathSring +struct PathString { - public PathSring(string? path) + public PathString(string? path) { Path = path; } public string? Path { get; } - public static implicit operator PathSring(string? s) => new PathSring(s); - public static implicit operator string?(PathSring path) => path.Path; + public static implicit operator PathString(string? s) => new PathString(s); + public static implicit operator string?(PathString path) => path.Path; }"; var expectedOutput = -@"AppBuilderExtensions.Map(this IAppBuilder app, PathSring path, Action callback) -AppBuilderExtensions.Map(this IAppBuilder app, PathSring path, Action callback) +@"AppBuilderExtensions.Map(this IAppBuilder app, PathString path, Action callback) +AppBuilderExtensions.Map(this IAppBuilder app, PathString path, Action callback) "; CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: expectedOutput); CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: expectedOutput); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index 1597eacea2b99..cd06e44641f70 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -3766,7 +3766,7 @@ record struct A(int I, string S); } [Fact] - public void Deconstruct_WihtNonReadOnlyGetter_GeneratedAsNonReadOnly() + public void Deconstruct_WithNonReadOnlyGetter_GeneratedAsNonReadOnly() { var src = @" record struct A(int I, string S) diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/AdditionalSourcesCollectionTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/AdditionalSourcesCollectionTests.cs index e081af04cf4ed..33bb3d667ad8a 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/AdditionalSourcesCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/AdditionalSourcesCollectionTests.cs @@ -143,7 +143,7 @@ public void Hint_Name_Must_Be_Unique(string hintName1, string hintName2) } [Fact] - public void Hint_Name_Must_Be_Unique_When_Combining_Soruces() + public void Hint_Name_Must_Be_Unique_When_Combining_Sources() { AdditionalSourcesCollection asc = new AdditionalSourcesCollection(".cs"); asc.Add("hintName1", SourceText.From("", Encoding.UTF8)); diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index c19334290734e..98a6a4ba981c1 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -175,7 +175,7 @@ protected static IReadOnlyDictionary SyntaxTreeCommonFeatures(IE /// /// /// Manipulation of strong name keys: strong name keys are read "on demand" by the compiler - /// and both normal compilation and this key can have non-determinstic output if they are + /// and both normal compilation and this key can have non-deterministic output if they are /// manipulated at the correct point in program execution. That is an existing limitation /// of compilation that is tracked by https://github.com/dotnet/roslyn/issues/57940 /// diff --git a/src/Compilers/Core/RebuildTest/DeterministicKeyBuilderTests.cs b/src/Compilers/Core/RebuildTest/DeterministicKeyBuilderTests.cs index b32004b10968a..cba991d69f205 100644 --- a/src/Compilers/Core/RebuildTest/DeterministicKeyBuilderTests.cs +++ b/src/Compilers/Core/RebuildTest/DeterministicKeyBuilderTests.cs @@ -342,7 +342,7 @@ JObject getValue(bool deterministic) } /// - /// Disabling determinism should mean all calls to GetDeteriministicKey return different values. + /// Disabling determinism should mean all calls to GetDeterministicKey return different values. /// [Fact] public void CompilationOptionsDeterministicOff() diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index 692156a5775bb..5ca8f328d0c77 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -723,7 +723,7 @@ internal bool TryLockFile() // file here, then the other thread unlocks and deletes the file, and then we // acquire the lock on our file handle - but the actual file is already deleted. // To close this race, we verify that the file does in fact still exist now that - // we have successfull acquired the locked FileStream. (Note that this check is + // we have successfully acquired the locked FileStream. (Note that this check is // safe because we cannot race with an other attempt to create the file since we // hold the guard, and after the FileStream constructor returned we can no race // with file deletion because we hold the lock.) diff --git a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IArgument.vb b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IArgument.vb index f0688530ba51f..133910c9098f5 100644 --- a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IArgument.vb +++ b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IArgument.vb @@ -873,7 +873,7 @@ BC30587: Named argument cannot match a ParamArray parameter. - Public Sub Error_NamedArgumenNotExist() + Public Sub Error_NamedArgumentNotExist() Dim source = Date: Wed, 16 Feb 2022 15:33:54 -0800 Subject: [PATCH 14/15] Handle binary AdditionalTexts in the AdditionalTextComparer. (#59578) --- .../AdditionalTextComparer.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs b/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs index fe7ed89f21174..ee7f130986333 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using System.IO; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -32,8 +32,15 @@ public bool Equals(AdditionalText? x, AdditionalText? y) return false; } - var xText = x.GetText(); - var yText = y.GetText(); + var xText = GetTextOrNullIfBinary(x); + var yText = GetTextOrNullIfBinary(y); + + // If xText and yText are both null, then the additional text is observably not changed + // and can be treated as equal. + if (xText is null && yText is null) + { + return true; + } if (xText is null || yText is null || xText.Length != yText.Length) { @@ -48,5 +55,18 @@ public int GetHashCode(AdditionalText obj) return Hash.Combine(PathUtilities.Comparer.GetHashCode(obj.Path), ByteSequenceComparer.GetHashCode(obj.GetText()?.GetChecksum() ?? ImmutableArray.Empty)); } + + private static SourceText? GetTextOrNullIfBinary(AdditionalText text) + { + try + { + return text.GetText(); + } + catch (InvalidDataException) + { + // InvalidDataException is thrown when the underlying text is binary + return null; + } + } } } From 83389eef1d10753fc0453791e28336f33ff56859 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 16 Feb 2022 15:47:32 -0800 Subject: [PATCH 15/15] [LSP] Use runtime type arguments for streamjsonrpc request handling (#59510) * Allow LSP request handlers to provide their type arguments for serialization at runtime and modify tests to use streamjsonrpc instead of directly accessing the queue. --- .../AbstractInProcLanguageClient.cs | 3 +- .../CodeActions/CodeActionResolveHandler.cs | 3 +- .../CodeActions/CodeActionsHandler.cs | 3 +- .../CodeActions/CodeActionsHandlerProvider.cs | 6 +- .../CodeActions/RunCodeActionHandler.cs | 1 + .../Handlers/Completion/CompletionHandler.cs | 3 +- .../Completion/CompletionHandlerProvider.cs | 5 +- .../Completion/CompletionResolveHandler.cs | 3 +- .../Handlers/Hover/HoverHandler.cs | 6 +- .../References/FindAllReferencesHandler.cs | 6 +- .../References/FindImplementationsHandler.cs | 6 +- .../Handlers/Rename/RenameHandler.cs | 6 +- .../Symbols/DocumentSymbolsHandler.cs | 6 +- .../Symbols/WorkspaceSymbolsHandler.cs | 6 +- .../VisualStudioInProcLanguageServer.cs | 105 ----- .../AbstractLanguageServerProtocolTests.cs | 168 +++++--- .../AbstractRequestDispatcherFactory.cs | 4 +- .../AbstractStatelessRequestHandler.cs | 2 - .../AbstractExecuteWorkspaceCommandHandler.cs | 2 - .../Commands/ProvidesCommandAttribute.cs | 4 +- .../Definitions/GoToDefinitionHandler.cs | 6 +- .../Definitions/GoToTypeDefinitionHandler.cs | 6 +- .../AbstractPullDiagnosticHandler.cs | 2 - .../DocumentPullDiagnosticHandler.cs | 3 +- .../DocumentPullDiagnosticHandlerProvider.cs | 4 +- ...alDocumentPullDiagnosticHandlerProvider.cs | 3 +- ...erimentalDocumentPullDiagnosticsHandler.cs | 3 +- ...lWorkspacePullDiagnosticHandlerProvider.cs | 3 +- ...rimentalWorkspacePullDiagnosticsHandler.cs | 3 +- .../WorkspacePullDiagnosticHandler.cs | 3 +- .../WorkspacePullDiagnosticHandlerProvider.cs | 4 +- .../DocumentChanges/DidChangeHandler.cs | 6 +- .../DocumentChanges/DidCloseHandler.cs | 6 +- .../Handler/DocumentChanges/DidOpenHandler.cs | 6 +- ...xportLspRequestHandlerProviderAttribute.cs | 30 +- .../FoldingRanges/FoldingRangesHandler.cs | 6 +- .../Formatting/FormatDocumentHandler.cs | 6 +- .../Formatting/FormatDocumentOnTypeHandler.cs | 6 +- .../Formatting/FormatDocumentRangeHandler.cs | 6 +- .../Highlights/DocumentHighlightHandler.cs | 6 +- .../Protocol/Handler/IRequestHandler.cs | 5 - .../InlineCompletionsHandler.cs | 6 +- ...sMethodAttribute.cs => MethodAttribute.cs} | 9 +- .../OnAutoInsert/OnAutoInsertHandler.cs | 6 +- .../GetTextDocumentWithContextHandler.cs | 6 +- .../RequestHandlerProviderMetadataView.cs | 32 +- .../SemanticTokensRangeHandler.cs | 8 +- .../SignatureHelp/SignatureHelpHandler.cs | 6 +- .../Protocol/LanguageServerTarget.cs | 378 ++++++------------ .../Protocol/ProtocolConstants.cs | 2 + .../Protocol/RequestDispatcher.cs | 105 +++-- .../Protocol/RequestDispatcherFactory.cs | 2 +- .../CodeActions/CodeActionResolveTests.cs | 5 +- .../CodeActions/CodeActionsTests.cs | 8 +- .../CodeActions/RunCodeActionsTests.cs | 2 +- .../Completion/CompletionResolveTests.cs | 86 ++-- .../Completion/CompletionTests.cs | 87 ++-- .../Definitions/GoToDefinitionTests.cs | 2 +- .../Definitions/GoToTypeDefinitionTests.cs | 2 +- .../Diagnostics/PullDiagnosticTests.cs | 104 ++--- .../DocumentChangesTests.LinkedDocuments.cs | 49 +-- .../DocumentChanges/DocumentChangesTests.cs | 8 +- .../FoldingRanges/FoldingRangesTests.cs | 2 +- .../Formatting/FormatDocumentOnTypeTests.cs | 2 +- .../Formatting/FormatDocumentRangeTests.cs | 2 - .../Formatting/FormatDocumentTests.cs | 2 +- .../Highlights/DocumentHighlightTests.cs | 2 +- .../ProtocolUnitTests/Hover/HoverTests.cs | 43 +- .../InlineCompletionsTests.cs | 4 +- .../LanguageServerTargetTests.cs | 91 ++--- .../LspMiscellaneousFilesWorkspaceTests.cs | 2 +- .../OnAutoInsert/OnAutoInsertTests.cs | 2 +- .../Ordering/FailingMutatingRequestHandler.cs | 34 +- .../Ordering/FailingRequestHandler.cs | 33 +- .../LongRunningNonMutatingRequestHandler.cs | 28 +- .../Ordering/MutatingRequestHandler.cs | 34 +- .../NonLSPSolutionRequestHandlerProvider.cs | 31 +- .../Ordering/NonMutatingRequestHandler.cs | 35 +- .../Ordering/RequestOrderingTests.cs | 35 +- .../Ordering/TestResponse.cs | 2 +- .../GetTextDocumentWithContextHandlerTests.cs | 2 +- .../FindAllReferencesHandlerTests.cs | 30 +- .../References/FindImplementationsTests.cs | 2 +- .../ProtocolUnitTests/Rename/RenameTests.cs | 3 +- .../AbstractSemanticTokensTests.cs | 13 +- .../SignatureHelp/SignatureHelpTests.cs | 2 +- .../Symbols/DocumentSymbolsTests.cs | 37 +- .../Symbols/WorkspaceSymbolsTests.cs | 2 +- .../Workspaces/LspWorkspaceManagerTests.cs | 4 +- .../CodeActions/CodeActionsHandlerProvider.cs | 7 +- .../Commands/CreateEventCommandHandler.cs | 1 + .../CreateEventCommandHandlerProvider.cs | 4 +- .../Handler/Completion/CompletionHandler.cs | 5 +- .../Completion/CompletionResolveHandler.cs | 6 +- .../Definitions/GoToDefinitionHandler.cs | 7 +- .../DocumentPullDiagnosticHandler.cs | 7 +- .../WorkspacePullDiagnosticHandler.cs | 7 +- .../FoldingRanges/FoldingRangesHandler.cs | 6 +- .../Formatting/FormatDocumentHandler.cs | 6 +- .../Formatting/FormatDocumentOnTypeHandler.cs | 6 +- .../Formatting/FormatDocumentRangeHandler.cs | 6 +- .../Handler/Hover/HoverHandler.cs | 6 +- .../OnAutoInsert/OnAutoInsertHandler.cs | 6 +- .../OnTypeRename/OnTypeRenameHandler.cs | 6 +- .../XamlRequestDispatcherFactory.cs | 28 +- src/VisualStudio/Xaml/Impl/StringConstants.cs | 2 + 106 files changed, 793 insertions(+), 1186 deletions(-) delete mode 100644 src/EditorFeatures/Core/Implementation/LanguageServer/VisualStudioInProcLanguageServer.cs rename src/Features/LanguageServer/Protocol/Handler/{ProvidesMethodAttribute.cs => MethodAttribute.cs} (61%) diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/AbstractInProcLanguageClient.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/AbstractInProcLanguageClient.cs index 07451e43cb3e0..a9c711ae3a3c0 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/AbstractInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/AbstractInProcLanguageClient.cs @@ -198,11 +198,12 @@ public ILanguageServerTarget Create( ICapabilitiesProvider capabilitiesProvider, ILspLogger logger) { - return new VisualStudioInProcLanguageServer( + return new LanguageServerTarget( _requestDispatcherFactory, jsonRpc, capabilitiesProvider, _lspWorkspaceRegistrationService, + lspMiscellaneousFilesWorkspace: null, GlobalOptions, _listenerProvider, logger, diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs index c1f6506a0e1d8..66a3f2f7d29e2 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionResolveHandler.cs @@ -32,6 +32,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// EditorFeatures references in are removed. /// See https://github.com/dotnet/roslyn/issues/55142 /// + [Method(LSP.Methods.CodeActionResolveName)] internal class CodeActionResolveHandler : IRequestHandler { private readonly CodeActionsCache _codeActionsCache; @@ -51,8 +52,6 @@ public CodeActionResolveHandler( _globalOptions = globalOptions; } - public string Method => LSP.Methods.CodeActionResolveName; - public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs index 4ee968b6db505..1553c5424a8ac 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandler.cs @@ -25,6 +25,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// EditorFeatures references in are removed. /// See https://github.com/dotnet/roslyn/issues/55142 /// + [Method(LSP.Methods.TextDocumentCodeActionName)] internal class CodeActionsHandler : IRequestHandler { private readonly CodeActionsCache _codeActionsCache; @@ -34,8 +35,6 @@ internal class CodeActionsHandler : IRequestHandler LSP.Methods.TextDocumentCodeActionName; - public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandlerProvider.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandlerProvider.cs index 1c5a2bd63f08a..b090b667351c1 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandlerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/CodeActionsHandlerProvider.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; using Microsoft.CodeAnalysis.Options; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { @@ -20,10 +19,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// Exports all the code action handlers together to ensure they /// share the same code actions cache state. /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentCodeActionName)] - [ProvidesMethod(LSP.Methods.CodeActionResolveName)] - [ProvidesCommand(CodeActionsHandler.RunCodeActionCommandName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(CodeActionsHandler), typeof(CodeActionResolveHandler), typeof(RunCodeActionHandler)), Shared] internal class CodeActionsHandlerProvider : AbstractRequestHandlerProvider { private readonly ICodeFixService _codeFixService; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs index 60412280af531..4a0224579340c 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/CodeActions/RunCodeActionHandler.cs @@ -30,6 +30,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// UI thread dependencies are resolved and references are removed. /// See https://github.com/dotnet/roslyn/issues/55142 /// + [Command(CodeActionsHandler.RunCodeActionCommandName)] internal class RunCodeActionHandler : AbstractExecuteWorkspaceCommandHandler { private readonly CodeActionsCache _codeActionsCache; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs index 1555f67c15d53..e313f23fb32d8 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs @@ -31,6 +31,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// references to VS Icon types are removed. /// See https://github.com/dotnet/roslyn/issues/55142 /// + [Method(LSP.Methods.TextDocumentCompletionName)] internal class CompletionHandler : IRequestHandler { private readonly IGlobalOptionService _globalOptions; @@ -39,8 +40,6 @@ internal class CompletionHandler : IRequestHandler LSP.Methods.TextDocumentCompletionName; - public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandlerProvider.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandlerProvider.cs index aafc5da24a766..8ca41b394a45b 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandlerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandlerProvider.cs @@ -11,13 +11,10 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.Options; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentCompletionName)] - [ProvidesMethod(LSP.Methods.TextDocumentCompletionResolveName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(CompletionHandler), typeof(CompletionResolveHandler)), Shared] internal class CompletionHandlerProvider : AbstractRequestHandlerProvider { private readonly IEnumerable> _completionProviders; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionResolveHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionResolveHandler.cs index 5918dc976d3a4..99fd0edef7d2c 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionResolveHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionResolveHandler.cs @@ -25,13 +25,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// references to VS icon types and classified text runs are removed. /// See https://github.com/dotnet/roslyn/issues/55142 /// + [Method(LSP.Methods.TextDocumentCompletionResolveName)] internal sealed class CompletionResolveHandler : IRequestHandler { private readonly CompletionListCache _completionListCache; private readonly IGlobalOptionService _globalOptions; - public string Method => LSP.Methods.TextDocumentCompletionResolveName; - public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Hover/HoverHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Hover/HoverHandler.cs index 388a2f00d07a3..e0974a32da07b 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Hover/HoverHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Hover/HoverHandler.cs @@ -27,8 +27,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// no longer references VS icon or classified text run types. /// See https://github.com/dotnet/roslyn/issues/55142 /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentHoverName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(HoverHandler)), Shared] + [Method(Methods.TextDocumentHoverName)] internal sealed class HoverHandler : AbstractStatelessRequestHandler { private readonly IGlobalOptionService _globalOptions; @@ -40,8 +40,6 @@ public HoverHandler(IGlobalOptionService globalOptions) _globalOptions = globalOptions; } - public override string Method => Methods.TextDocumentHoverName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindAllReferencesHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindAllReferencesHandler.cs index 858a994a40d2e..f9b8ee6bffc5f 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindAllReferencesHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindAllReferencesHandler.cs @@ -26,8 +26,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// we no longer reference VS classified text runs. /// See https://github.com/dotnet/roslyn/issues/55142 /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentReferencesName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FindAllReferencesHandler)), Shared] + [Method(LSP.Methods.TextDocumentReferencesName)] internal class FindAllReferencesHandler : AbstractStatelessRequestHandler { private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; @@ -46,8 +46,6 @@ public FindAllReferencesHandler( _globalOptions = globalOptions; } - public override string Method => LSP.Methods.TextDocumentReferencesName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindImplementationsHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindImplementationsHandler.cs index f999ac967aa35..47c812558a823 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindImplementationsHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/References/FindImplementationsHandler.cs @@ -15,8 +15,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentImplementationName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FindImplementationsHandler)), Shared] + [Method(LSP.Methods.TextDocumentImplementationName)] internal sealed class FindImplementationsHandler : AbstractStatelessRequestHandler { private readonly IGlobalOptionService _globalOptions; @@ -28,8 +28,6 @@ public FindImplementationsHandler(IGlobalOptionService globalOptions) _globalOptions = globalOptions; } - public override string Method => LSP.Methods.TextDocumentImplementationName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Rename/RenameHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Rename/RenameHandler.cs index 64ef0497596c5..1fe3f331c93ac 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Rename/RenameHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Rename/RenameHandler.cs @@ -22,8 +22,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// we no longer reference the /// See https://github.com/dotnet/roslyn/issues/55142 /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentRenameName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(RenameHandler)), Shared] + [Method(LSP.Methods.TextDocumentRenameName)] internal class RenameHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -32,8 +32,6 @@ public RenameHandler() { } - public override string Method => LSP.Methods.TextDocumentRenameName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/DocumentSymbolsHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/DocumentSymbolsHandler.cs index 16f75e7f2c34e..73e4395b05d32 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/DocumentSymbolsHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/DocumentSymbolsHandler.cs @@ -23,12 +23,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once /// we no longer reference VS icon types. /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentDocumentSymbolName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DocumentSymbolsHandler)), Shared] + [Method(Methods.TextDocumentDocumentSymbolName)] internal class DocumentSymbolsHandler : AbstractStatelessRequestHandler { - public override string Method => Methods.TextDocumentDocumentSymbolName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs index 70c84edb1d134..e40947f5bb1f4 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs @@ -20,8 +20,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once /// we no longer reference VS icon types. /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.WorkspaceSymbolName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(WorkspaceSymbolsHandler)), Shared] + [Method(Methods.WorkspaceSymbolName)] internal class WorkspaceSymbolsHandler : AbstractStatelessRequestHandler { private static readonly IImmutableSet s_supportedKinds = @@ -52,8 +52,6 @@ public WorkspaceSymbolsHandler( _threadingContext = threadingContext; } - public override string Method => Methods.WorkspaceSymbolName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/VisualStudioInProcLanguageServer.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/VisualStudioInProcLanguageServer.cs deleted file mode 100644 index 15b75347b2645..0000000000000 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/VisualStudioInProcLanguageServer.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Roslyn.Utilities; -using StreamJsonRpc; - -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.LanguageClient -{ - /// - /// Implementation of that also supports - /// VS LSP extension methods. - /// - internal class VisualStudioInProcLanguageServer : LanguageServerTarget - { - private readonly ImmutableArray _supportedLanguages; - - internal VisualStudioInProcLanguageServer( - AbstractRequestDispatcherFactory requestDispatcherFactory, - JsonRpc jsonRpc, - ICapabilitiesProvider capabilitiesProvider, - LspWorkspaceRegistrationService workspaceRegistrationService, - IGlobalOptionService globalOptions, - IAsynchronousOperationListenerProvider listenerProvider, - ILspLogger logger, - ImmutableArray supportedLanguages, - string? clientName, - WellKnownLspServerKinds serverKind) - : base(requestDispatcherFactory, jsonRpc, capabilitiesProvider, workspaceRegistrationService, lspMiscellaneousFilesWorkspace: null, globalOptions, listenerProvider, logger, supportedLanguages, clientName, serverKind) - { - _supportedLanguages = supportedLanguages; - } - - public override Task InitializedAsync() - { - return Task.CompletedTask; - } - - [JsonRpcMethod(VSInternalMethods.DocumentPullDiagnosticName, UseSingleObjectParameterDeserialization = true)] - public Task GetDocumentPullDiagnosticsAsync(VSInternalDocumentDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync( - Queue, VSInternalMethods.DocumentPullDiagnosticName, - diagnosticsParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(VSInternalMethods.WorkspacePullDiagnosticName, UseSingleObjectParameterDeserialization = true)] - public Task GetWorkspacePullDiagnosticsAsync(VSInternalWorkspaceDiagnosticsParams diagnosticsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync( - Queue, VSInternalMethods.WorkspacePullDiagnosticName, - diagnosticsParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(VSMethods.GetProjectContextsName, UseSingleObjectParameterDeserialization = true)] - public Task GetProjectContextsAsync(VSGetProjectContextsParams textDocumentWithContextParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, VSMethods.GetProjectContextsName, - textDocumentWithContextParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(VSInternalMethods.OnAutoInsertName, UseSingleObjectParameterDeserialization = true)] - public Task GetDocumentOnAutoInsertAsync(VSInternalDocumentOnAutoInsertParams autoInsertParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, VSInternalMethods.OnAutoInsertName, - autoInsertParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentLinkedEditingRangeName, UseSingleObjectParameterDeserialization = true)] - public Task GetLinkedEditingRangesAsync(LinkedEditingRangeParams renameParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentLinkedEditingRangeName, - renameParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(VSInternalMethods.TextDocumentInlineCompletionName, UseSingleObjectParameterDeserialization = true)] - public Task GetInlineCompletionsAsync(VSInternalInlineCompletionRequest request, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, VSInternalMethods.TextDocumentInlineCompletionName, - request, _clientCapabilities, ClientName, cancellationToken); - } - } -} diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index bacaddd1f8a5d..10e916934d8a3 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -5,12 +5,14 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Implementation.LanguageClient; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; @@ -25,9 +27,11 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Nerdbank.Streams; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Roslyn.Utilities; +using StreamJsonRpc; using Xunit; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; @@ -93,6 +97,8 @@ protected class OrderLocations : Comparer protected virtual TestComposition Composition => s_composition; + protected static LSP.ClientCapabilities CapabilitiesWithVSExtensions => new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; + /// /// Asserts two objects are equivalent by converting to JSON and ignoring whitespace. /// @@ -179,7 +185,7 @@ protected static LSP.TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri if (projectContext != null) { documentIdentifier.ProjectContext = - new LSP.VSProjectContext { Id = ProtocolConversions.ProjectIdToProjectContextId(projectContext) }; + new LSP.VSProjectContext { Id = ProtocolConversions.ProjectIdToProjectContextId(projectContext), Label = projectContext.DebugName!, Kind = LSP.VSProjectKind.CSharp }; } return documentIdentifier; @@ -239,9 +245,9 @@ protected static LSP.CompletionParams CreateCompletionParams( { TextEdit = textEdit, InsertText = insertText, - FilterText = filterText ?? label, + FilterText = filterText, Label = label, - SortText = sortText ?? label, + SortText = sortText, InsertTextFormat = LSP.InsertTextFormat.Plaintext, Kind = kind, Data = JObject.FromObject(new CompletionResolveData() @@ -277,25 +283,25 @@ private protected static CodeActionResolveData CreateCodeActionResolveData(strin /// /// Creates an LSP server backed by a workspace instance with a solution containing the markup. /// - protected Task CreateTestLspServerAsync(string markup) - => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp); + protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, clientCapabilities); - protected Task CreateVisualBasicTestLspServerAsync(string markup) - => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.VisualBasic); + protected Task CreateVisualBasicTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.VisualBasic, clientCapabilities); - protected Task CreateMultiProjectLspServerAsync(string xmlMarkup) - => CreateTestLspServerAsync(TestWorkspace.Create(xmlMarkup, composition: Composition), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + protected Task CreateMultiProjectLspServerAsync(string xmlMarkup, LSP.ClientCapabilities? clientCapabilities = null) + => CreateTestLspServerAsync(TestWorkspace.Create(xmlMarkup, composition: Composition), clientCapabilities, WellKnownLspServerKinds.AlwaysActiveVSLspServer); /// /// Creates an LSP server backed by a workspace instance with a solution containing the specified documents. /// - protected Task CreateTestLspServerAsync(string[] markups) - => CreateTestLspServerAsync(markups, LanguageNames.CSharp); + protected Task CreateTestLspServerAsync(string[] markups, LSP.ClientCapabilities? clientCapabilities = null) + => CreateTestLspServerAsync(markups, LanguageNames.CSharp, clientCapabilities); - private protected Task CreateTestLspServerAsync(string markup, WellKnownLspServerKinds serverKind) - => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, serverKind); + private protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, clientCapabilities, serverKind); - private Task CreateTestLspServerAsync(string[] markups, string languageName, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + private Task CreateTestLspServerAsync(string[] markups, string languageName, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) { var workspace = languageName switch { @@ -304,10 +310,10 @@ private Task CreateTestLspServerAsync(string[] markups, string la _ => throw new ArgumentException($"language name {languageName} is not valid for a test workspace"), }; - return CreateTestLspServerAsync(workspace, serverKind); + return CreateTestLspServerAsync(workspace, clientCapabilities, serverKind); } - private static async Task CreateTestLspServerAsync(TestWorkspace workspace, WellKnownLspServerKinds serverKind) + private static async Task CreateTestLspServerAsync(TestWorkspace workspace, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind) { var solution = workspace.CurrentSolution; @@ -323,10 +329,14 @@ private static async Task CreateTestLspServerAsync(TestWorkspace // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return new TestLspServer(workspace, serverKind); + return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); } - protected async Task CreateXmlTestLspServerAsync(string xmlContent, string? workspaceKind = null) + private protected async Task CreateXmlTestLspServerAsync( + string xmlContent, + string? workspaceKind = null, + LSP.ClientCapabilities? clientCapabilities = null, + WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) { var workspace = TestWorkspace.Create(XElement.Parse(xmlContent), openDocuments: false, composition: Composition, workspaceKind: workspaceKind); @@ -334,7 +344,7 @@ protected async Task CreateXmlTestLspServerAsync(string xmlConten // Otherwise we could have a race where workspace change events triggered by creation are changing the state // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return new TestLspServer(workspace); + return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); } /// @@ -396,20 +406,6 @@ static LSP.Location ConvertTextSpanWithTextToLocation(TextSpan span, SourceText } } - private static RequestDispatcher CreateRequestDispatcher(TestWorkspace workspace, WellKnownLspServerKinds serverKind) - { - var factory = workspace.ExportProvider.GetExportedValue(); - return factory.CreateRequestDispatcher(ProtocolConstants.RoslynLspLanguages, serverKind); - } - - private static RequestExecutionQueue CreateRequestQueue(TestWorkspace workspace, WellKnownLspServerKinds serverKind) - { - var registrationService = workspace.GetService(); - var globalOptions = workspace.GetService(); - var lspMiscFilesWorkspace = new LspMiscellaneousFilesWorkspace(NoOpLspLogger.Instance); - return new RequestExecutionQueue(NoOpLspLogger.Instance, registrationService, lspMiscFilesWorkspace, globalOptions, ProtocolConstants.RoslynLspLanguages, serverKind); - } - private static string GetDocumentFilePathFromName(string documentName) => "C:\\" + documentName; @@ -455,16 +451,27 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U public sealed class TestLspServer : IDisposable { public readonly TestWorkspace TestWorkspace; - private readonly RequestDispatcher _requestDispatcher; - private readonly RequestExecutionQueue _executionQueue; private readonly Dictionary> _locations; + private readonly LanguageServerTarget _languageServer; + private readonly JsonRpc _clientRpc; - internal TestLspServer(TestWorkspace testWorkspace, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + public LSP.ClientCapabilities ClientCapabilities { get; } + + private TestLspServer(TestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) { TestWorkspace = testWorkspace; + ClientCapabilities = clientCapabilities; _locations = GetAnnotatedLocations(testWorkspace, testWorkspace.CurrentSolution); - _requestDispatcher = CreateRequestDispatcher(testWorkspace, serverKind); - _executionQueue = CreateRequestQueue(testWorkspace, serverKind); + + var (clientStream, serverStream) = FullDuplexStream.CreatePair(); + _languageServer = CreateLanguageServer(serverStream, serverStream, TestWorkspace, serverKind); + + _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, CreateJsonMessageFormatter())) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + _clientRpc.StartListening(); var workspaceWaiter = GetWorkspaceWaiter(testWorkspace); Assert.False(workspaceWaiter.HasPendingWork); @@ -474,11 +481,60 @@ internal TestLspServer(TestWorkspace testWorkspace, WellKnownLspServerKinds serv GetManagerAccessor().ResetLspSolutions(); } - public Task ExecuteRequestAsync(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities, - string? clientName, CancellationToken cancellationToken) where RequestType : class + private static JsonMessageFormatter CreateJsonMessageFormatter() { - return _requestDispatcher.ExecuteRequestAsync( - _executionQueue, methodName, request, clientCapabilities, clientName, cancellationToken); + var messageFormatter = new JsonMessageFormatter(); + LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(messageFormatter.JsonSerializer); + return messageFormatter; + } + + internal static async Task CreateAsync(TestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) + { + var server = new TestLspServer(testWorkspace, clientCapabilities, serverKind); + + await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams + { + Capabilities = clientCapabilities, + }, CancellationToken.None); + + return server; + } + + private static LanguageServerTarget CreateLanguageServer(Stream inputStream, Stream outputStream, TestWorkspace workspace, WellKnownLspServerKinds serverKind) + { + var dispatcherFactory = workspace.ExportProvider.GetExportedValue(); + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var lspWorkspaceRegistrationService = workspace.ExportProvider.GetExportedValue(); + var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); + + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, CreateJsonMessageFormatter())) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + var globalOptions = workspace.GetService(); + + var languageServer = new LanguageServerTarget( + dispatcherFactory, + jsonRpc, + capabilitiesProvider, + lspWorkspaceRegistrationService, + new LspMiscellaneousFilesWorkspace(NoOpLspLogger.Instance), + globalOptions, + listenerProvider, + NoOpLspLogger.Instance, + ProtocolConstants.RoslynLspLanguages, + clientName: null, + serverKind); + + jsonRpc.StartListening(); + return languageServer; + } + + public async Task ExecuteRequestAsync(string methodName, RequestType request, CancellationToken cancellationToken) where RequestType : class + { + var result = await _clientRpc.InvokeWithParameterObjectAsync(methodName, request, cancellationToken: cancellationToken).ConfigureAwait(false); + return result; } public async Task OpenDocumentAsync(Uri documentUri, string? text = null) @@ -492,8 +548,7 @@ public async Task OpenDocumentAsync(Uri documentUri, string? text = null) } var didOpenParams = CreateDidOpenTextDocumentParams(documentUri, text.ToString()); - await ExecuteRequestAsync(LSP.Methods.TextDocumentDidOpenName, - didOpenParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + await ExecuteRequestAsync(LSP.Methods.TextDocumentDidOpenName, didOpenParams, CancellationToken.None); } public Task InsertTextAsync(Uri documentUri, params (int Line, int Column, string Text)[] changes) @@ -510,8 +565,7 @@ public Task ReplaceTextAsync(Uri documentUri, params (LSP.Range Range, string Te var didChangeParams = CreateDidChangeTextDocumentParams( documentUri, changes.Select(change => (change.Range, change.Text)).ToImmutableArray()); - return ExecuteRequestAsync(LSP.Methods.TextDocumentDidChangeName, - didChangeParams, new LSP.ClientCapabilities(), clientName: null, CancellationToken.None); + return ExecuteRequestAsync(LSP.Methods.TextDocumentDidChangeName, didChangeParams, CancellationToken.None); } public Task DeleteTextAsync(Uri documentUri, params (int StartLine, int StartColumn, int EndLine, int EndColumn)[] changes) @@ -526,26 +580,34 @@ public Task DeleteTextAsync(Uri documentUri, params (int StartLine, int StartCol public Task CloseDocumentAsync(Uri documentUri) { var didCloseParams = CreateDidCloseTextDocumentParams(documentUri); - return ExecuteRequestAsync(LSP.Methods.TextDocumentDidCloseName, - didCloseParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + return ExecuteRequestAsync(LSP.Methods.TextDocumentDidCloseName, didCloseParams, CancellationToken.None); } public IList GetLocations(string locationName) => _locations[locationName]; public Solution GetCurrentSolution() => TestWorkspace.CurrentSolution; - internal RequestExecutionQueue.TestAccessor GetQueueAccessor() => _executionQueue.GetTestAccessor(); + internal RequestExecutionQueue.TestAccessor GetQueueAccessor() => _languageServer.GetTestAccessor().GetQueueAccessor(); - internal RequestDispatcher.TestAccessor GetDispatcherAccessor() => _requestDispatcher.GetTestAccessor(); + internal RequestDispatcher.TestAccessor GetDispatcherAccessor() => _languageServer.GetTestAccessor().GetDispatcherAccessor(); - internal LspWorkspaceManager.TestAccessor GetManagerAccessor() => _executionQueue.GetTestAccessor().GetLspWorkspaceManager().GetTestAccessor(); + internal LspWorkspaceManager.TestAccessor GetManagerAccessor() => _languageServer.GetTestAccessor().GetManagerAccessor(); - internal LspWorkspaceManager GetManager() => _executionQueue.GetTestAccessor().GetLspWorkspaceManager(); + internal LspWorkspaceManager GetManager() => _languageServer.GetTestAccessor().GetQueueAccessor().GetLspWorkspaceManager(); + + internal LanguageServerTarget.TestAccessor GetServerAccessor() => _languageServer.GetTestAccessor(); public void Dispose() { + // Some tests manually call shutdown, so avoid calling shutdown twice if already called. + if (!_languageServer.HasShutdownStarted) + { + _languageServer.GetTestAccessor().ShutdownServer(); + } + + _languageServer.GetTestAccessor().ExitServer(); TestWorkspace.Dispose(); - _executionQueue.Shutdown(); + _clientRpc.Dispose(); } } } diff --git a/src/Features/LanguageServer/Protocol/AbstractRequestDispatcherFactory.cs b/src/Features/LanguageServer/Protocol/AbstractRequestDispatcherFactory.cs index 28a2cc4d88769..a99d9fb90c1cf 100644 --- a/src/Features/LanguageServer/Protocol/AbstractRequestDispatcherFactory.cs +++ b/src/Features/LanguageServer/Protocol/AbstractRequestDispatcherFactory.cs @@ -25,9 +25,9 @@ protected AbstractRequestDispatcherFactory(IEnumerable - public virtual RequestDispatcher CreateRequestDispatcher(ImmutableArray supportedLanguages, WellKnownLspServerKinds serverKind) + public virtual RequestDispatcher CreateRequestDispatcher(WellKnownLspServerKinds serverKind) { - return new RequestDispatcher(_requestHandlerProviders, supportedLanguages, serverKind); + return new RequestDispatcher(_requestHandlerProviders, serverKind); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/AbstractStatelessRequestHandler.cs b/src/Features/LanguageServer/Protocol/Handler/AbstractStatelessRequestHandler.cs index f387fe0c853cd..0a6a28e50855f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/AbstractStatelessRequestHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/AbstractStatelessRequestHandler.cs @@ -16,8 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// internal abstract class AbstractStatelessRequestHandler : AbstractRequestHandlerProvider, IRequestHandler { - public abstract string Method { get; } - public abstract bool MutatesSolutionState { get; } public abstract bool RequiresLSPSolution { get; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Commands/AbstractExecuteWorkspaceCommandHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Commands/AbstractExecuteWorkspaceCommandHandler.cs index bbdd98ada1af9..cb3b812c1dd0f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Commands/AbstractExecuteWorkspaceCommandHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Commands/AbstractExecuteWorkspaceCommandHandler.cs @@ -10,8 +10,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Commands { internal abstract class AbstractExecuteWorkspaceCommandHandler : IRequestHandler { - public string Method => GetRequestNameForCommandName(Command); - public abstract string Command { get; } public abstract bool MutatesSolutionState { get; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Commands/ProvidesCommandAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/Commands/ProvidesCommandAttribute.cs index cac027f582700..807fae56d473a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Commands/ProvidesCommandAttribute.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Commands/ProvidesCommandAttribute.cs @@ -9,9 +9,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Commands { [MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal class ProvidesCommandAttribute : ProvidesMethodAttribute + internal class CommandAttribute : MethodAttribute { - public ProvidesCommandAttribute(string command) : base(AbstractExecuteWorkspaceCommandHandler.GetRequestNameForCommandName(command)) + public CommandAttribute(string command) : base(AbstractExecuteWorkspaceCommandHandler.GetRequestNameForCommandName(command)) { } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs index 619c6841f2928..48d8bdc01505e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs @@ -12,8 +12,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentDefinitionName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(GoToDefinitionHandler)), Shared] + [Method(LSP.Methods.TextDocumentDefinitionName)] internal class GoToDefinitionHandler : AbstractGoToDefinitionHandler { [ImportingConstructor] @@ -22,8 +22,6 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe { } - public override string Method => LSP.Methods.TextDocumentDefinitionName; - public override Task HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) => GetDefinitionAsync(request, typeOnly: false, context, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs index 4b41a555f22ee..15270054a4d05 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs @@ -12,8 +12,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentTypeDefinitionName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(GoToTypeDefinitionHandler)), Shared] + [Method(LSP.Methods.TextDocumentTypeDefinitionName)] internal class GoToTypeDefinitionHandler : AbstractGoToDefinitionHandler { [ImportingConstructor] @@ -22,8 +22,6 @@ public GoToTypeDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFi { } - public override string Method => LSP.Methods.TextDocumentTypeDefinitionName; - public override Task HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) => GetDefinitionAsync(request, typeOnly: true, context, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 897e27f637198..e20349a85299a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -83,8 +83,6 @@ protected record struct PreviousResult(string PreviousResultId, TextDocumentIden /// private long _nextDocumentResultId; - public abstract string Method { get; } - public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 013c475be2abc..7b0305c504e59 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -13,12 +13,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { + [Method(VSInternalMethods.DocumentPullDiagnosticName)] internal class DocumentPullDiagnosticHandler : AbstractPullDiagnosticHandler { private readonly IDiagnosticAnalyzerService _analyzerService; - public override string Method => VSInternalMethods.DocumentPullDiagnosticName; - public DocumentPullDiagnosticHandler( WellKnownLspServerKinds serverKind, IDiagnosticService diagnosticService, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerProvider.cs index e6bb96df420cd..1515c2ece2561 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerProvider.cs @@ -7,12 +7,10 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(VSInternalMethods.DocumentPullDiagnosticName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DocumentPullDiagnosticHandler)), Shared] internal class DocumentPullDiagnosticHandlerProvider : AbstractRequestHandlerProvider { private readonly IDiagnosticService _diagnosticService; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticHandlerProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticHandlerProvider.cs index f0773bd99cfb1..c85ed44ae7475 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticHandlerProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticHandlerProvider.cs @@ -10,8 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental; -[ExportRoslynLanguagesLspRequestHandlerProvider, Shared] -[ProvidesMethod(ExperimentalMethods.TextDocumentDiagnostic)] +[ExportRoslynLanguagesLspRequestHandlerProvider(typeof(ExperimentalDocumentPullDiagnosticsHandler)), Shared] internal class ExperimentalDocumentPullDiagnosticHandlerProvider : AbstractRequestHandlerProvider { private readonly IDiagnosticService _diagnosticService; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs index b7d48945e5727..7cfa1da7c5d21 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs @@ -20,6 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType, DocumentDiagnosticPartialResult>; +[Method(ExperimentalMethods.TextDocumentDiagnostic)] internal class ExperimentalDocumentPullDiagnosticsHandler : AbstractPullDiagnosticHandler { private readonly IDiagnosticAnalyzerService _analyzerService; @@ -33,8 +34,6 @@ public ExperimentalDocumentPullDiagnosticsHandler( _analyzerService = analyzerService; } - public override string Method => ExperimentalMethods.TextDocumentDiagnostic; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticHandlerProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticHandlerProvider.cs index df64fc655442d..eec7567c41db3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticHandlerProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticHandlerProvider.cs @@ -10,8 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental; -[ExportRoslynLanguagesLspRequestHandlerProvider, Shared] -[ProvidesMethod(ExperimentalMethods.WorkspaceDiagnostic)] +[ExportRoslynLanguagesLspRequestHandlerProvider(typeof(ExperimentalWorkspacePullDiagnosticsHandler)), Shared] internal class ExperimentalWorkspacePullDiagnosticHandlerProvider : AbstractRequestHandlerProvider { private readonly IDiagnosticService _diagnosticService; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs index 312352a779b1c..fca7b5194834f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs @@ -16,6 +16,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental using WorkspaceDocumentDiagnosticReport = SumType; +[Method(ExperimentalMethods.WorkspaceDiagnostic)] internal class ExperimentalWorkspacePullDiagnosticsHandler : AbstractPullDiagnosticHandler { private readonly IDiagnosticAnalyzerService _analyzerService; @@ -29,8 +30,6 @@ public ExperimentalWorkspacePullDiagnosticsHandler( _analyzerService = analyzerService; } - public override string Method => ExperimentalMethods.WorkspaceDiagnostic; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(WorkspaceDiagnosticParams diagnosticsParams) => null; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index dda4dcee6b9ff..eca33b4f9e7a7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -16,10 +16,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { + [Method(VSInternalMethods.WorkspacePullDiagnosticName)] internal class WorkspacePullDiagnosticHandler : AbstractPullDiagnosticHandler { - public override string Method => VSInternalMethods.WorkspacePullDiagnosticName; - public WorkspacePullDiagnosticHandler(WellKnownLspServerKinds serverKind, IDiagnosticService diagnosticService) : base(serverKind, diagnosticService) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerProvider.cs index 92502896f43bd..14eb6d9170703 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerProvider.cs @@ -7,12 +7,10 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - [ExportRoslynLanguagesLspRequestHandlerProvider(), Shared] - [ProvidesMethod(VSInternalMethods.WorkspacePullDiagnosticName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(WorkspacePullDiagnosticHandler)), Shared] internal class WorkspacePullDiagnosticHandlerProvider : AbstractRequestHandlerProvider { private readonly IDiagnosticService _diagnosticService; diff --git a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs index e3a125802546c..edd50acb7fcc2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs @@ -13,8 +13,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.DocumentChanges { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentDidChangeName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DidChangeHandler)), Shared] + [Method(LSP.Methods.TextDocumentDidChangeName)] internal class DidChangeHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -23,8 +23,6 @@ public DidChangeHandler() { } - public override string Method => LSP.Methods.TextDocumentDidChangeName; - public override bool MutatesSolutionState => true; public override bool RequiresLSPSolution => false; diff --git a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidCloseHandler.cs b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidCloseHandler.cs index eb179bc93bc94..c9b073e199aad 100644 --- a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidCloseHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidCloseHandler.cs @@ -12,8 +12,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.DocumentChanges { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentDidCloseName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DidCloseHandler)), Shared] + [Method(LSP.Methods.TextDocumentDidCloseName)] internal class DidCloseHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -22,8 +22,6 @@ public DidCloseHandler() { } - public override string Method => LSP.Methods.TextDocumentDidCloseName; - public override bool MutatesSolutionState => true; public override bool RequiresLSPSolution => false; diff --git a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs index 376e4cadd0670..540c79b492e63 100644 --- a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs @@ -13,8 +13,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.DocumentChanges { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentDidOpenName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DidOpenHandler)), Shared] + [Method(LSP.Methods.TextDocumentDidOpenName)] internal class DidOpenHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -23,8 +23,6 @@ public DidOpenHandler() { } - public override string Method => LSP.Methods.TextDocumentDidOpenName; - public override bool MutatesSolutionState => true; public override bool RequiresLSPSolution => false; diff --git a/src/Features/LanguageServer/Protocol/Handler/ExportLspRequestHandlerProviderAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/ExportLspRequestHandlerProviderAttribute.cs index 515c9188c6a4b..190d6e6c63a0b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ExportLspRequestHandlerProviderAttribute.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ExportLspRequestHandlerProviderAttribute.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { @@ -15,25 +16,38 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler [AttributeUsage(AttributeTargets.Class), MetadataAttribute] internal class ExportLspRequestHandlerProviderAttribute : ExportAttribute { + public Type[] HandlerTypes { get; } + /// - /// The document languages that this handler supports. + /// Exports an and specifies the contract and + /// types this provider is associated with. /// - public string[] LanguageNames { get; } - - public ExportLspRequestHandlerProviderAttribute(params string[] languageNames) : base(typeof(AbstractRequestHandlerProvider)) + /// + /// The contract name this provider is exported. Used by + /// when importing handlers to ensure that it only imports handlers that match this contract. + /// This is important to ensure that we only load relevant providers (e.g. don't load Xaml providers when creating the c# server), + /// otherwise we will get dll load RPS regressions for the + /// + /// + /// The concrete type of the provided in + /// + /// + /// Additional if + /// provides more than one handler at once. + /// + public ExportLspRequestHandlerProviderAttribute(string contractName, Type firstHandlerType, params Type[] additionalHandlerTypes) : base(contractName, typeof(AbstractRequestHandlerProvider)) { - LanguageNames = languageNames; + HandlerTypes = additionalHandlerTypes.Concat(new[] { firstHandlerType }).ToArray(); } } /// - /// Defines an easy to use subclass for ExportLspRequestHandlerProviderAttribute that contains - /// all the language names that the default Roslyn servers support. + /// Defines an easy to use subclass for ExportLspRequestHandlerProviderAttribute with the roslyn languages contract name. /// [AttributeUsage(AttributeTargets.Class), MetadataAttribute] internal class ExportRoslynLanguagesLspRequestHandlerProviderAttribute : ExportLspRequestHandlerProviderAttribute { - public ExportRoslynLanguagesLspRequestHandlerProviderAttribute() : base(ProtocolConstants.RoslynLspLanguages.ToArray()) + public ExportRoslynLanguagesLspRequestHandlerProviderAttribute(Type firstHandlerType, params Type[] additionalHandlerTypes) : base(ProtocolConstants.RoslynLspLanguagesContract, firstHandlerType, additionalHandlerTypes) { } } diff --git a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs index c35f190587167..8a128c73832cf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -15,12 +15,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentFoldingRangeName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FoldingRangesHandler)), Shared] + [Method(Methods.TextDocumentFoldingRangeName)] internal sealed class FoldingRangesHandler : AbstractStatelessRequestHandler { - public override string Method => Methods.TextDocumentFoldingRangeName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs index e6ac3d4095140..4c4d5f3f3bc40 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentHandler.cs @@ -11,8 +11,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentFormattingName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FormatDocumentHandler)), Shared] + [Method(LSP.Methods.TextDocumentFormattingName)] internal class FormatDocumentHandler : AbstractFormatDocumentHandlerBase { [ImportingConstructor] @@ -21,8 +21,6 @@ public FormatDocumentHandler() { } - public override string Method => LSP.Methods.TextDocumentFormattingName; - public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync( diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 50fd055f82a37..003d8462a93d2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -19,12 +19,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentOnTypeFormattingName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FormatDocumentOnTypeHandler)), Shared] + [Method(Methods.TextDocumentOnTypeFormattingName)] internal class FormatDocumentOnTypeHandler : AbstractStatelessRequestHandler { - public override string Method => Methods.TextDocumentOnTypeFormattingName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs index 0d6a573e01360..a83f246b48926 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -11,8 +11,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentRangeFormattingName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FormatDocumentRangeHandler)), Shared] + [Method(Methods.TextDocumentRangeFormattingName)] internal class FormatDocumentRangeHandler : AbstractFormatDocumentHandlerBase { [ImportingConstructor] @@ -21,8 +21,6 @@ public FormatDocumentRangeHandler() { } - public override string Method => Methods.TextDocumentRangeFormattingName; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentRangeFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync( diff --git a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs index b4ec6691eb9e2..0f5719c1f35f1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs @@ -19,8 +19,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentDocumentHighlightName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(DocumentHighlightsHandler)), Shared] + [Method(Methods.TextDocumentDocumentHighlightName)] internal class DocumentHighlightsHandler : AbstractStatelessRequestHandler { private readonly IHighlightingService _highlightingService; @@ -32,8 +32,6 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService) _highlightingService = highlightingService; } - public override string Method => Methods.TextDocumentDocumentHighlightName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/IRequestHandler.cs b/src/Features/LanguageServer/Protocol/Handler/IRequestHandler.cs index 12d6d9eb004ef..987800742759b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/IRequestHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/IRequestHandler.cs @@ -13,11 +13,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// internal interface IRequestHandler { - /// - /// The LSP method that this implements. - /// - string Method { get; } - /// /// Whether or not the solution state on the server is modified /// as a part of handling this request. diff --git a/src/Features/LanguageServer/Protocol/Handler/InlineCompletions/InlineCompletionsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlineCompletions/InlineCompletionsHandler.cs index ffa5eb61b7eb7..4066e54f1a6b4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlineCompletions/InlineCompletionsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlineCompletions/InlineCompletionsHandler.cs @@ -28,8 +28,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlineCompletions; /// /// Supports built in legacy snippets for razor scenarios. /// -[ExportRoslynLanguagesLspRequestHandlerProvider, Shared] -[ProvidesMethod(VSInternalMethods.TextDocumentInlineCompletionName)] +[ExportRoslynLanguagesLspRequestHandlerProvider(typeof(InlineCompletionsHandler)), Shared] +[Method(VSInternalMethods.TextDocumentInlineCompletionName)] internal partial class InlineCompletionsHandler : AbstractStatelessRequestHandler { /// @@ -48,8 +48,6 @@ public InlineCompletionsHandler() { } - public override string Method => VSInternalMethods.TextDocumentInlineCompletionName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/ProvidesMethodAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/MethodAttribute.cs similarity index 61% rename from src/Features/LanguageServer/Protocol/Handler/ProvidesMethodAttribute.cs rename to src/Features/LanguageServer/Protocol/Handler/MethodAttribute.cs index e85e042cb6990..091829e570413 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProvidesMethodAttribute.cs +++ b/src/Features/LanguageServer/Protocol/Handler/MethodAttribute.cs @@ -8,12 +8,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { [MetadataAttribute] - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal class ProvidesMethodAttribute : Attribute + [AttributeUsage(AttributeTargets.Class)] + internal class MethodAttribute : Attribute { + /// + /// Contains the method that this implements. + /// public string Method { get; } - public ProvidesMethodAttribute(string method) + public MethodAttribute(string method) { Method = method; } diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index c536ad0e84e08..78655e0c7274c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -24,15 +24,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.VSInternalMethods.OnAutoInsertName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(OnAutoInsertHandler)), Shared] + [Method(LSP.VSInternalMethods.OnAutoInsertName)] internal class OnAutoInsertHandler : AbstractStatelessRequestHandler { private readonly ImmutableArray _csharpBraceCompletionServices; private readonly ImmutableArray _visualBasicBraceCompletionServices; - public override string Method => LSP.VSInternalMethods.OnAutoInsertName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs index 6e72c9b341e8d..eb14e29a7de66 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs @@ -15,8 +15,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(VSMethods.GetProjectContextsName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(GetTextDocumentWithContextHandler)), Shared] + [Method(VSMethods.GetProjectContextsName)] internal class GetTextDocumentWithContextHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -25,8 +25,6 @@ public GetTextDocumentWithContextHandler() { } - public override string Method => VSMethods.GetProjectContextsName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestHandlerProviderMetadataView.cs b/src/Features/LanguageServer/Protocol/Handler/RequestHandlerProviderMetadataView.cs index 6970602870e36..430844dab7173 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestHandlerProviderMetadataView.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestHandlerProviderMetadataView.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { @@ -13,36 +15,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// internal class RequestHandlerProviderMetadataView { - public string[] LanguageNames { get; set; } - - public string[] Methods { get; set; } + public ImmutableArray HandlerTypes { get; set; } public RequestHandlerProviderMetadataView(IDictionary metadata) { - var methodMetadata = metadata["Method"]; - Methods = ConvertMetadataToArray(methodMetadata); - - var languageMetadata = metadata["LanguageNames"]; - LanguageNames = ConvertMetadataToArray(languageMetadata); - } - - /// - /// When multiple of the same attribute are defined on a class, the metadata - /// is aggregated into an array. However, when just one of the same attribute is defined, - /// the metadata is not aggregated and is just a string. - /// MEF cannot construct the metadata object when it sees just the string type with AllowMultiple = true, - /// so we override and construct it ourselves here. - /// - private static string[] ConvertMetadataToArray(object metadata) - { - if (metadata is string[] arrayData) - { - return arrayData; - } - else - { - return new string[] { (string)metadata }; - } + var handlerMetadata = (Type[])metadata[nameof(ExportLspRequestHandlerProviderAttribute.HandlerTypes)]; + HandlerTypes = handlerMetadata.ToImmutableArray(); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs index 2f9513c825157..bdd4c460a84e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs @@ -18,14 +18,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens /// /// Computes the semantic tokens for a given range. /// - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(Methods.TextDocumentSemanticTokensRangeName)] - internal sealed class SemanticTokensRangeHandler : AbstractStatelessRequestHandler + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(SemanticTokensRangeHandler)), Shared] + [Method(Methods.TextDocumentSemanticTokensRangeName)] + internal class SemanticTokensRangeHandler : AbstractStatelessRequestHandler { private readonly IGlobalOptionService _globalOptions; - public override string Method => LSP.Methods.TextDocumentSemanticTokensRangeName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/Handler/SignatureHelp/SignatureHelpHandler.cs b/src/Features/LanguageServer/Protocol/Handler/SignatureHelp/SignatureHelpHandler.cs index e11f9612b3cd1..87008c6b60184 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SignatureHelp/SignatureHelpHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SignatureHelp/SignatureHelpHandler.cs @@ -18,8 +18,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler { - [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] - [ProvidesMethod(LSP.Methods.TextDocumentSignatureHelpName)] + [ExportRoslynLanguagesLspRequestHandlerProvider(typeof(SignatureHelpHandler)), Shared] + [Method(LSP.Methods.TextDocumentSignatureHelpName)] internal class SignatureHelpHandler : AbstractStatelessRequestHandler { private readonly IEnumerable> _allProviders; @@ -35,8 +35,6 @@ public SignatureHelpHandler( _globalOptions = globalOptions; } - public override string Method => LSP.Methods.TextDocumentSignatureHelpName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs b/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs index da4a8a891fcbe..3b7ae888b05b8 100644 --- a/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs +++ b/src/Features/LanguageServer/Protocol/LanguageServerTarget.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -23,17 +23,15 @@ internal class LanguageServerTarget : ILanguageServerTarget { private readonly ICapabilitiesProvider _capabilitiesProvider; - protected readonly IGlobalOptionService GlobalOptions; - protected readonly JsonRpc JsonRpc; - protected readonly RequestDispatcher RequestDispatcher; - protected readonly RequestExecutionQueue Queue; - protected readonly LspWorkspaceRegistrationService WorkspaceRegistrationService; - protected readonly IAsynchronousOperationListener Listener; - protected readonly ILspLogger Logger; - protected readonly string? ClientName; + private readonly JsonRpc _jsonRpc; + private readonly RequestDispatcher _requestDispatcher; + private readonly RequestExecutionQueue _queue; + private readonly IAsynchronousOperationListener _listener; + private readonly ILspLogger _logger; + private readonly string? _clientName; // Set on first LSP initialize request. - protected ClientCapabilities? _clientCapabilities; + private ClientCapabilities? _clientCapabilities; // Fields used during shutdown. private bool _shuttingDown; @@ -54,28 +52,74 @@ internal LanguageServerTarget( string? clientName, WellKnownLspServerKinds serverKind) { - GlobalOptions = globalOptions; - RequestDispatcher = requestDispatcherFactory.CreateRequestDispatcher(supportedLanguages, serverKind); + _requestDispatcher = requestDispatcherFactory.CreateRequestDispatcher(serverKind); _capabilitiesProvider = capabilitiesProvider; - WorkspaceRegistrationService = workspaceRegistrationService; - Logger = logger; + _logger = logger; - JsonRpc = jsonRpc; - JsonRpc.AddLocalRpcTarget(this); - JsonRpc.Disconnected += JsonRpc_Disconnected; + _jsonRpc = jsonRpc; + _jsonRpc.AddLocalRpcTarget(this); + _jsonRpc.Disconnected += JsonRpc_Disconnected; - Listener = listenerProvider.GetListener(FeatureAttribute.LanguageServer); - ClientName = clientName; + _listener = listenerProvider.GetListener(FeatureAttribute.LanguageServer); + _clientName = clientName; - Queue = new RequestExecutionQueue( + _queue = new RequestExecutionQueue( logger, workspaceRegistrationService, lspMiscellaneousFilesWorkspace, globalOptions, supportedLanguages, serverKind); - Queue.RequestServerShutdown += RequestExecutionQueue_Errored; + _queue.RequestServerShutdown += RequestExecutionQueue_Errored; + + var entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.EntryPointAsync)); + Contract.ThrowIfNull(entryPointMethod, $"{typeof(DelegatingEntryPoint).FullName} is missing method {nameof(DelegatingEntryPoint.EntryPointAsync)}"); + + foreach (var metadata in _requestDispatcher.GetRegisteredMethods()) + { + // Instead of concretely defining methods for each LSP method, we instead dynamically construct + // the generic method info from the exported handler types. This allows us to define multiple handlers for the same method + // but different type parameters. This is a key functionality to support TS external access as we do not want to couple + // our LSP protocol version dll to theirs. + // + // We also do not use the StreamJsonRpc support for JToken as the rpc method parameters because we want + // StreamJsonRpc to do the deserialization to handle streaming requests using IProgress. + var delegatingEntryPoint = new DelegatingEntryPoint(metadata.MethodName, this); + + var genericEntryPointMethod = entryPointMethod.MakeGenericMethod(metadata.RequestType, metadata.ResponseType); + + _jsonRpc.AddLocalRpcMethod(genericEntryPointMethod, delegatingEntryPoint, new JsonRpcMethodAttribute(metadata.MethodName) { UseSingleObjectParameterDeserialization = true }); + } + } + + /// + /// Wrapper class to hold the method and properties from the + /// that the method info passed to streamjsonrpc is created from. + /// + private class DelegatingEntryPoint + { + private readonly string _method; + private readonly LanguageServerTarget _target; + + public DelegatingEntryPoint(string method, LanguageServerTarget target) + { + _method = method; + _target = target; + } + + public async Task EntryPointAsync(TRequestType requestType, CancellationToken cancellationToken) where TRequestType : class + { + Contract.ThrowIfNull(_target._clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + var result = await _target._requestDispatcher.ExecuteRequestAsync( + _method, + requestType, + _target._clientCapabilities, + _target._clientName, + _target._queue, + cancellationToken).ConfigureAwait(false); + return result; + } } /// @@ -87,7 +131,7 @@ public Task InitializeAsync(InitializeParams initializeParams, { try { - Logger?.TraceStart("Initialize"); + _logger?.TraceStart("Initialize"); Contract.ThrowIfTrue(_clientCapabilities != null, $"{nameof(InitializeAsync)} called multiple times"); _clientCapabilities = initializeParams.Capabilities; @@ -98,7 +142,7 @@ public Task InitializeAsync(InitializeParams initializeParams, } finally { - Logger?.TraceStop("Initialize"); + _logger?.TraceStop("Initialize"); } } @@ -113,7 +157,7 @@ public Task ShutdownAsync(CancellationToken _) { try { - Logger?.TraceStart("Shutdown"); + _logger?.TraceStart("Shutdown"); ShutdownImpl(); @@ -121,11 +165,11 @@ public Task ShutdownAsync(CancellationToken _) } finally { - Logger?.TraceStop("Shutdown"); + _logger?.TraceStop("Shutdown"); } } - protected void ShutdownImpl() + private void ShutdownImpl() { Contract.ThrowIfTrue(_shuttingDown, "Shutdown has already been called."); @@ -139,7 +183,7 @@ public Task ExitAsync(CancellationToken _) { try { - Logger?.TraceStart("Exit"); + _logger?.TraceStart("Exit"); ExitImpl(); @@ -147,7 +191,7 @@ public Task ExitAsync(CancellationToken _) } finally { - Logger?.TraceStop("Exit"); + _logger?.TraceStop("Exit"); } } @@ -156,8 +200,8 @@ private void ExitImpl() try { ShutdownRequestQueue(); - JsonRpc.Disconnected -= JsonRpc_Disconnected; - JsonRpc.Dispose(); + _jsonRpc.Disconnected -= JsonRpc_Disconnected; + _jsonRpc.Dispose(); } catch (Exception e) when (FatalError.ReportAndCatch(e)) { @@ -166,239 +210,38 @@ private void ExitImpl() } } - [JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDefinitionName, - textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentRenameName, - renameParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentReferencesName, - referencesParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentCodeActionName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentCodeActionsAsync(CodeActionParams codeActionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCodeActionName, codeActionParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.CodeActionResolveName, UseSingleObjectParameterDeserialization = true)] - public Task ResolveCodeActionAsync(CodeAction codeAction, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.CodeActionResolveName, - codeAction, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)] - public async Task> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - // Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698 - return await RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCompletionName, - completionParams, _clientCapabilities, ClientName, cancellationToken).ConfigureAwait(false); - } - - [JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)] - public Task ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentCompletionResolveName, completionItem, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentFoldingRangeName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFoldingRangeAsync(FoldingRangeParams textDocumentFoldingRangeParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentFoldingRangeName, textDocumentFoldingRangeParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDocumentHighlightName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDocumentSymbolName, documentSymbolParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentFormattingName, documentFormattingParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentOnTypeFormattingName, documentOnTypeFormattingParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentRangeFormattingName, documentRangeFormattingParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken); - } - + /// + /// Specially handle the execute workspace command method as we have to deserialize the request + /// to figure out which actually handles it. + /// [JsonRpcMethod(Methods.WorkspaceExecuteCommandName, UseSingleObjectParameterDeserialization = true)] - public Task ExecuteWorkspaceCommandAsync(ExecuteCommandParams executeCommandParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.WorkspaceExecuteCommandName, executeCommandParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)] - public Task GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.WorkspaceSymbolName, workspaceSymbolParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentSemanticTokensFullName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSemanticTokensAsync(SemanticTokensParams semanticTokensParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentSemanticTokensFullName, - semanticTokensParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentSemanticTokensFullDeltaName, UseSingleObjectParameterDeserialization = true)] - public Task> GetTextDocumentSemanticTokensEditsAsync(SemanticTokensDeltaParams semanticTokensEditsParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync>(Queue, Methods.TextDocumentSemanticTokensFullDeltaName, - semanticTokensEditsParams, _clientCapabilities, ClientName, cancellationToken); - } - - // Note: Since a range request is always received in conjunction with a whole document request, we don't need to cache range results. - [JsonRpcMethod(Methods.TextDocumentSemanticTokensRangeName, UseSingleObjectParameterDeserialization = true)] - public Task GetTextDocumentSemanticTokensRangeAsync(SemanticTokensRangeParams semanticTokensRangeParams, CancellationToken cancellationToken) + public async Task ExecuteWorkspaceCommandAsync(LSP.ExecuteCommandParams request, CancellationToken cancellationToken) { Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); + var requestMethod = AbstractExecuteWorkspaceCommandHandler.GetRequestNameForCommandName(request.Command); - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentSemanticTokensRangeName, - semanticTokensRangeParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidChangeName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidChangeAsync(DidChangeTextDocumentParams didChangeParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidChangeName, - didChangeParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidOpenName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidOpenAsync(DidOpenTextDocumentParams didOpenParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidOpenName, - didOpenParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(Methods.TextDocumentDidCloseName, UseSingleObjectParameterDeserialization = true)] - public Task HandleDocumentDidCloseAsync(DidCloseTextDocumentParams didCloseParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - - return RequestDispatcher.ExecuteRequestAsync(Queue, Methods.TextDocumentDidCloseName, - didCloseParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(ExperimentalMethods.TextDocumentDiagnostic, UseSingleObjectParameterDeserialization = true)] - public Task?> HandleDocumentDiagnosticsAsync(DocumentDiagnosticParams documentDiagnosticParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - return RequestDispatcher.ExecuteRequestAsync?>(Queue, ExperimentalMethods.TextDocumentDiagnostic, - documentDiagnosticParams, _clientCapabilities, ClientName, cancellationToken); - } - - [JsonRpcMethod(ExperimentalMethods.WorkspaceDiagnostic, UseSingleObjectParameterDeserialization = true)] - public Task HandleWorkspaceDiagnosticsAsync(WorkspaceDiagnosticParams workspaceDiagnosticParams, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); - return RequestDispatcher.ExecuteRequestAsync(Queue, ExperimentalMethods.WorkspaceDiagnostic, - workspaceDiagnosticParams, _clientCapabilities, ClientName, cancellationToken); + var result = await _requestDispatcher.ExecuteRequestAsync( + requestMethod, + request, + _clientCapabilities, + _clientName, + _queue, + cancellationToken).ConfigureAwait(false); + return result; } private void ShutdownRequestQueue() { - Queue.RequestServerShutdown -= RequestExecutionQueue_Errored; + _queue.RequestServerShutdown -= RequestExecutionQueue_Errored; // if the queue requested shutdown via its event, it will have already shut itself down, but this // won't cause any problems calling it again - Queue.Shutdown(); + _queue.Shutdown(); } private void RequestExecutionQueue_Errored(object? sender, RequestShutdownEventArgs e) { // log message and shut down - Logger?.TraceWarning($"Request queue is requesting shutdown due to error: {e.Message}"); + _logger?.TraceWarning($"Request queue is requesting shutdown due to error: {e.Message}"); var message = new LogMessageParams() { @@ -406,12 +249,12 @@ private void RequestExecutionQueue_Errored(object? sender, RequestShutdownEventA Message = e.Message }; - var asyncToken = Listener.BeginAsyncOperation(nameof(RequestExecutionQueue_Errored)); + var asyncToken = _listener.BeginAsyncOperation(nameof(RequestExecutionQueue_Errored)); _errorShutdownTask = Task.Run(async () => { - Logger?.TraceInformation("Shutting down language server."); + _logger?.TraceInformation("Shutting down language server."); - await JsonRpc.NotifyWithParameterObjectAsync(Methods.WindowLogMessageName, message).ConfigureAwait(false); + await _jsonRpc.NotifyWithParameterObjectAsync(Methods.WindowLogMessageName, message).ConfigureAwait(false); ShutdownImpl(); ExitImpl(); @@ -429,7 +272,7 @@ private void JsonRpc_Disconnected(object? sender, JsonRpcDisconnectedEventArgs e return; } - Logger?.TraceWarning($"Encountered unexpected jsonrpc disconnect, Reason={e.Reason}, Description={e.Description}, Exception={e.Exception}"); + _logger?.TraceWarning($"Encountered unexpected jsonrpc disconnect, Reason={e.Reason}, Description={e.Description}, Exception={e.Exception}"); ShutdownImpl(); ExitImpl(); @@ -441,22 +284,37 @@ public async ValueTask DisposeAsync() if (_errorShutdownTask is not null) await _errorShutdownTask.ConfigureAwait(false); - if (Logger is IDisposable disposableLogger) + if (_logger is IDisposable disposableLogger) disposableLogger.Dispose(); } - internal TestAccessor GetTestAccessor() - => new TestAccessor(this.Queue); + internal TestAccessor GetTestAccessor() => new(this); internal readonly struct TestAccessor { - private readonly RequestExecutionQueue _queue; + private readonly LanguageServerTarget _server; + + internal TestAccessor(LanguageServerTarget server) + { + _server = server; + } + + internal RequestExecutionQueue.TestAccessor GetQueueAccessor() + => _server._queue.GetTestAccessor(); + + internal LspWorkspaceManager.TestAccessor GetManagerAccessor() + => _server._queue.GetTestAccessor().GetLspWorkspaceManager().GetTestAccessor(); + + internal RequestDispatcher.TestAccessor GetDispatcherAccessor() + => _server._requestDispatcher.GetTestAccessor(); + + internal JsonRpc GetServerRpc() => _server._jsonRpc; + + internal bool HasShutdownStarted() => _server.HasShutdownStarted; - public TestAccessor(RequestExecutionQueue queue) - => _queue = queue; + internal void ShutdownServer() => _server.ShutdownImpl(); - public RequestExecutionQueue.TestAccessor GetQueueAccessor() - => _queue.GetTestAccessor(); + internal void ExitServer() => _server.ExitImpl(); } } } diff --git a/src/Features/LanguageServer/Protocol/ProtocolConstants.cs b/src/Features/LanguageServer/Protocol/ProtocolConstants.cs index 9eb2b53b9981c..aa29f366061b0 100644 --- a/src/Features/LanguageServer/Protocol/ProtocolConstants.cs +++ b/src/Features/LanguageServer/Protocol/ProtocolConstants.cs @@ -11,5 +11,7 @@ internal class ProtocolConstants public const string RazorCSharp = "RazorCSharp"; public static ImmutableArray RoslynLspLanguages = ImmutableArray.Create(LanguageNames.CSharp, LanguageNames.VisualBasic, LanguageNames.FSharp); + + public const string RoslynLspLanguagesContract = "RoslynLspLanguages"; } } diff --git a/src/Features/LanguageServer/Protocol/RequestDispatcher.cs b/src/Features/LanguageServer/Protocol/RequestDispatcher.cs index 7a0f6047c2155..618c6ab65e78d 100644 --- a/src/Features/LanguageServer/Protocol/RequestDispatcher.cs +++ b/src/Features/LanguageServer/Protocol/RequestDispatcher.cs @@ -9,91 +9,126 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer { + internal readonly record struct RequestHandlerMetadata(string MethodName, Type RequestType, Type ResponseType); + /// /// Aggregates handlers for the specified languages and dispatches LSP requests /// to the appropriate handler for the request. /// internal class RequestDispatcher { - private readonly ImmutableDictionary> _requestHandlers; + private readonly ImmutableDictionary> _requestHandlers; public RequestDispatcher( + // Lazily imported handler providers to avoid instantiating providers until they are directly needed. ImmutableArray> requestHandlerProviders, - ImmutableArray languageNames, WellKnownLspServerKinds serverKind) { - _requestHandlers = CreateMethodToHandlerMap(requestHandlerProviders.Where(rh => languageNames.All(languageName => rh.Metadata.LanguageNames.Contains(languageName))), serverKind); + _requestHandlers = CreateMethodToHandlerMap(requestHandlerProviders, serverKind); } - private static ImmutableDictionary> CreateMethodToHandlerMap( - IEnumerable> requestHandlerProviders, - WellKnownLspServerKinds serverKind) + private static ImmutableDictionary> CreateMethodToHandlerMap( + IEnumerable> requestHandlerProviders, WellKnownLspServerKinds serverKind) { - var requestHandlerDictionary = ImmutableDictionary.CreateBuilder>(StringComparer.OrdinalIgnoreCase); + var requestHandlerDictionary = ImmutableDictionary.CreateBuilder>(); // Store the request handlers in a dictionary from request name to handler instance. foreach (var handlerProvider in requestHandlerProviders) { - var methods = handlerProvider.Metadata.Methods; + var handlerTypes = handlerProvider.Metadata.HandlerTypes; // Instantiate all the providers as one lazy object and re-use it for all methods that the provider provides handlers for. // This ensures 2 things: // 1. That the handler provider is not instantiated (and therefore its dependencies are not) until a handler it provides is needed. // 2. That the handler provider's CreateRequestHandlers is only called once and always returns the same handler instances. - var lazyProviders = new Lazy>(() => handlerProvider.Value.CreateRequestHandlers(serverKind).ToImmutableDictionary(p => p.Method, p => p, StringComparer.OrdinalIgnoreCase)); + var lazyProviders = new Lazy>(() => handlerProvider.Value.CreateRequestHandlers(serverKind) + .ToImmutableDictionary(p => GetRequestHandlerMethod(p.GetType()), p => p, StringComparer.OrdinalIgnoreCase)); - foreach (var method in methods) + foreach (var handlerType in handlerTypes) { + var (requestType, responseType) = ConvertHandlerTypeToRequestResponseTypes(handlerType); + var method = GetRequestHandlerMethod(handlerType); + // Using the lazy set of handlers, create a lazy instance that will resolve the set of handlers for the provider // and then lookup the correct handler for the specified method. - requestHandlerDictionary.Add(method, new Lazy(() => lazyProviders.Value[method])); + requestHandlerDictionary.Add(new RequestHandlerMetadata(method, requestType, responseType), new Lazy(() => lazyProviders.Value[method])); } } return requestHandlerDictionary.ToImmutable(); + + static string GetRequestHandlerMethod(Type handlerType) + { + // Get the LSP method name from the handler's method name attribute. + var methodAttribute = Attribute.GetCustomAttribute(handlerType, typeof(MethodAttribute)) as MethodAttribute; + Contract.ThrowIfNull(methodAttribute, $"{handlerType.FullName} is missing Method attribute"); + + return methodAttribute.Method; + } } - public Task ExecuteRequestAsync( - RequestExecutionQueue queue, + /// + /// Retrieves the generic argument information from the request handler type without instantiating it. + /// + private static (Type requestType, Type responseType) ConvertHandlerTypeToRequestResponseTypes(Type handlerType) + { + var requestHandlerGenericType = handlerType.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>)).SingleOrDefault(); + Contract.ThrowIfNull(requestHandlerGenericType, $"Provided handler type {handlerType.FullName} does not implement IRequestHandler<,>"); + + var genericArguments = requestHandlerGenericType.GetGenericArguments(); + Contract.ThrowIfFalse(genericArguments.Length == 2, $"Provided handler type {handlerType.FullName} does not have exactly two generic arguments"); + var requestType = genericArguments[0]; + var responseType = genericArguments[1]; + + return (requestType, responseType); + } + + public async Task ExecuteRequestAsync( string methodName, - RequestType request, + TRequestType request, LSP.ClientCapabilities clientCapabilities, string? clientName, - CancellationToken cancellationToken) where RequestType : class + RequestExecutionQueue queue, + CancellationToken cancellationToken) where TRequestType : class { - Contract.ThrowIfNull(request); - Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name"); - - if (request is ExecuteCommandParams executeCommandRequest) - { - // If we have a workspace/executeCommand request, get the request name - // from the command name. - methodName = AbstractExecuteWorkspaceCommandHandler.GetRequestNameForCommandName(executeCommandRequest.Command); - } + // Get the handler matching the requested method. + var requestHandlerMetadata = new RequestHandlerMetadata(methodName, typeof(TRequestType), typeof(TResponseType)); - var handlerEntry = _requestHandlers[methodName]; - Contract.ThrowIfNull(handlerEntry, string.Format("Request handler entry not found for method {0}", methodName)); + var handler = _requestHandlers[requestHandlerMetadata].Value; - var mutatesSolutionState = handlerEntry.Value.MutatesSolutionState; - var requiresLSPSolution = handlerEntry.Value.RequiresLSPSolution; + var mutatesSolutionState = handler.MutatesSolutionState; + var requiresLspSolution = handler.RequiresLSPSolution; - var handler = (IRequestHandler?)handlerEntry.Value; - Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName)); + var strongHandler = (IRequestHandler?)handler; + Contract.ThrowIfNull(strongHandler, string.Format("Request handler not found for method {0}", methodName)); - return ExecuteRequestAsync(queue, request, clientCapabilities, clientName, methodName, mutatesSolutionState, requiresLSPSolution, handler, cancellationToken); + var result = await ExecuteRequestAsync(queue, mutatesSolutionState, requiresLspSolution, strongHandler, request, clientCapabilities, clientName, methodName, cancellationToken).ConfigureAwait(false); + return result; } - protected virtual Task ExecuteRequestAsync(RequestExecutionQueue queue, RequestType request, ClientCapabilities clientCapabilities, string? clientName, string methodName, bool mutatesSolutionState, bool requiresLSPSolution, IRequestHandler handler, CancellationToken cancellationToken) where RequestType : class + protected virtual Task ExecuteRequestAsync( + RequestExecutionQueue queue, + bool mutatesSolutionState, + bool requiresLSPSolution, + IRequestHandler handler, + TRequestType request, + LSP.ClientCapabilities clientCapabilities, + string? clientName, + string methodName, + CancellationToken cancellationToken) where TRequestType : class { return queue.ExecuteAsync(mutatesSolutionState, requiresLSPSolution, handler, request, clientCapabilities, clientName, methodName, cancellationToken); } + public ImmutableArray GetRegisteredMethods() + { + return _requestHandlers.Keys.ToImmutableArray(); + } + internal TestAccessor GetTestAccessor() => new TestAccessor(this); @@ -105,7 +140,7 @@ public TestAccessor(RequestDispatcher requestDispatcher) => _requestDispatcher = requestDispatcher; public IRequestHandler GetHandler(string methodName) - => (IRequestHandler)_requestDispatcher._requestHandlers[methodName].Value; + => (IRequestHandler)_requestDispatcher._requestHandlers.Single(handler => handler.Key.MethodName == methodName).Value.Value; } } } diff --git a/src/Features/LanguageServer/Protocol/RequestDispatcherFactory.cs b/src/Features/LanguageServer/Protocol/RequestDispatcherFactory.cs index e4829d282094e..4896ff6a330fc 100644 --- a/src/Features/LanguageServer/Protocol/RequestDispatcherFactory.cs +++ b/src/Features/LanguageServer/Protocol/RequestDispatcherFactory.cs @@ -16,7 +16,7 @@ internal sealed class RequestDispatcherFactory : AbstractRequestDispatcherFactor { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RequestDispatcherFactory([ImportMany] IEnumerable> requestHandlerProviders) + public RequestDispatcherFactory([ImportMany(ProtocolConstants.RoslynLspLanguagesContract)] IEnumerable> requestHandlerProviders) : base(requestHandlerProviders) { } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index 536bbdca169b8..773c05c3077c3 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -132,11 +132,10 @@ void M() private static async Task RunGetCodeActionResolveAsync( TestLspServer testLspServer, - VSInternalCodeAction unresolvedCodeAction, - LSP.ClientCapabilities clientCapabilities = null) + VSInternalCodeAction unresolvedCodeAction) { var result = (VSInternalCodeAction)await testLspServer.ExecuteRequestAsync( - LSP.Methods.CodeActionResolveName, unresolvedCodeAction, clientCapabilities, null, CancellationToken.None); + LSP.Methods.CodeActionResolveName, unresolvedCodeAction, CancellationToken.None); return result; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index abe6e07cc3952..61b186b32e74a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Roslyn.Test.Utilities; using Xunit; @@ -85,7 +86,7 @@ void M() var results = await RunGetCodeActionsAsync(testLspServer, caretLocation); var introduceConstant = results[0].Children.FirstOrDefault( - r => ((CodeActionResolveData)r.Data).UniqueIdentifier == FeaturesResources.Introduce_constant + r => ((JObject)r.Data).ToObject().UniqueIdentifier == FeaturesResources.Introduce_constant + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1")); AssertJsonEquals(expected, introduceConstant); @@ -204,11 +205,10 @@ private static void AssertRangeAndDocEqual( private static async Task RunGetCodeActionsAsync( TestLspServer testLspServer, - LSP.Location caret, - LSP.ClientCapabilities clientCapabilities = null) + LSP.Location caret) { var result = await testLspServer.ExecuteRequestAsync( - LSP.Methods.TextDocumentCodeActionName, CreateCodeActionParams(caret), clientCapabilities, null, CancellationToken.None); + LSP.Methods.TextDocumentCodeActionName, CreateCodeActionParams(caret), CancellationToken.None); return result.Cast().ToArray(); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs index d922c457d22df..dd6d69153b211 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs @@ -67,7 +67,7 @@ private static async Task ExecuteRunCodeActionCommandAsync( }; var result = await testLspServer.ExecuteRequestAsync( - LSP.Methods.WorkspaceExecuteCommandName, command, new LSP.ClientCapabilities(), null, CancellationToken.None); + LSP.Methods.WorkspaceExecuteCommandName, command, CancellationToken.None); Contract.ThrowIfNull(result); return (bool)result; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs index ca05efafd8712..915764c77bbf9 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs @@ -37,7 +37,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, @@ -52,16 +52,17 @@ void M() } } }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); + var clientCompletionItem = await GetCompletionItemToResolveAsync( testLspServer, - label: "A", - clientCapabilities).ConfigureAwait(false); + label: "A").ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); var expected = CreateResolvedCompletionItem(clientCompletionItem, description, "class A", null); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( - testLspServer, clientCompletionItem, clientCapabilities).ConfigureAwait(false); + testLspServer, clientCompletionItem).ConfigureAwait(false); AssertJsonEquals(expected, results); } @@ -76,7 +77,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, label: "A").ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); @@ -101,7 +102,7 @@ class B : A { override {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, label: "M()").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); @@ -128,7 +129,7 @@ class B : A { override {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + // Explicitly enable snippets. This allows us to set the cursor with $0. Currently only applies to C# in Razor docs. var clientCapabilities = new LSP.VSInternalClientCapabilities { @@ -144,13 +145,13 @@ class B : A } } }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var clientCompletionItem = await GetCompletionItemToResolveAsync( testLspServer, - label: "M()", - clientCapabilities).ConfigureAwait(false); + label: "M()").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( - testLspServer, clientCompletionItem, clientCapabilities).ConfigureAwait(false); + testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.NotNull(results.TextEdit); Assert.Null(results.InsertText); @@ -224,11 +225,23 @@ void M() AMet{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + var clientCapabilities = new ClientCapabilities + { + TextDocument = new TextDocumentClientCapabilities + { + Completion = new CompletionSetting + { + CompletionItem = new CompletionItemSetting + { + DocumentationFormat = new MarkupKind[] { MarkupKind.Markdown } + } + } + } + }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var clientCompletionItem = await GetCompletionItemToResolveAsync( testLspServer, - label: "AMethod", - new ClientCapabilities()).ConfigureAwait(false); + label: "AMethod").ConfigureAwait(false); Assert.True(clientCompletionItem is not VSInternalCompletionItem); var expected = @"```csharp @@ -247,20 +260,7 @@ void A.AMethod(int i) var results = await RunResolveCompletionItemAsync( testLspServer, - clientCompletionItem, - new ClientCapabilities - { - TextDocument = new TextDocumentClientCapabilities - { - Completion = new CompletionSetting - { - CompletionItem = new CompletionItemSetting - { - DocumentationFormat = new MarkupKind[] { MarkupKind.Markdown } - } - } - } - }).ConfigureAwait(false); + clientCompletionItem).ConfigureAwait(false); Assert.Equal(expected, results.Documentation.Value.Second.Value); } @@ -303,8 +303,7 @@ void M() using var testLspServer = await CreateTestLspServerAsync(markup); var clientCompletionItem = await GetCompletionItemToResolveAsync( testLspServer, - label: "AMethod", - new ClientCapabilities()).ConfigureAwait(false); + label: "AMethod").ConfigureAwait(false); Assert.True(clientCompletionItem is not VSInternalCompletionItem); var expected = @"void A.AMethod(int i) @@ -320,8 +319,7 @@ underline text var results = await RunResolveCompletionItemAsync( testLspServer, - clientCompletionItem, - new ClientCapabilities()).ConfigureAwait(false); + clientCompletionItem).ConfigureAwait(false); Assert.Equal(expected, results.Documentation.Value.Second.Value); } @@ -337,7 +335,7 @@ void M() a.{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync(testLspServer, label: "(byte)").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( @@ -346,11 +344,10 @@ void M() Assert.NotNull(results.Description); } - private static async Task RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem, LSP.ClientCapabilities clientCapabilities = null) + private static async Task RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem) { - clientCapabilities ??= new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionResolveName, - completionItem, clientCapabilities, null, CancellationToken.None); + completionItem, CancellationToken.None); } private static LSP.VSInternalCompletionItem CreateResolvedCompletionItem( @@ -376,23 +373,23 @@ private static LSP.VSInternalCompletionItem CreateResolvedCompletionItem( private static ClassifiedTextRun[] CreateClassifiedTextRunForClass(string className) => new ClassifiedTextRun[] { + new ClassifiedTextRun("whitespace", string.Empty), new ClassifiedTextRun("keyword", "class"), new ClassifiedTextRun("whitespace", " "), - new ClassifiedTextRun("class name", className) + new ClassifiedTextRun("class name", className), + new ClassifiedTextRun("whitespace", string.Empty), }; private static async Task GetCompletionItemToResolveAsync( TestLspServer testLspServer, - string label, - LSP.ClientCapabilities clientCapabilities = null) where T : LSP.CompletionItem + string label) where T : LSP.CompletionItem { var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), LSP.VSInternalCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); - clientCapabilities ??= new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; - var completionList = await RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities); + var completionList = await RunGetCompletionsAsync(testLspServer, completionParams); - if (clientCapabilities.HasCompletionListDataCapability()) + if (testLspServer.ClientCapabilities.HasCompletionListDataCapability()) { var vsCompletionList = Assert.IsAssignableFrom(completionList); Assert.NotNull(vsCompletionList.Data); @@ -405,14 +402,13 @@ private static async Task GetCompletionItemToResolveAsync( private static async Task RunGetCompletionsAsync( TestLspServer testLspServer, - LSP.CompletionParams completionParams, - LSP.ClientCapabilities clientCapabilities) + LSP.CompletionParams completionParams) { var completionList = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, - completionParams, clientCapabilities, null, CancellationToken.None); + completionParams, CancellationToken.None); // Emulate client behavior of promoting "Data" completion list properties onto completion items. - if (clientCapabilities.HasCompletionListDataCapability() && + if (testLspServer.ClientCapabilities.HasCompletionListDataCapability() && completionList is VSInternalCompletionList vsCompletionList && vsCompletionList.Data != null) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index 67bcb053fd123..64acc0383b2d2 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -47,7 +47,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -57,13 +57,13 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: new string[] { "Class", "Internal" }, - request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters, insertText: "A").ConfigureAwait(false); + request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); var expectedCommitCharacters = expected.CommitCharacters; // Null out the commit characters since we're expecting the commit characters will be lifted onto the completion list. expected.CommitCharacters = null; - var results = await RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities).ConfigureAwait(false); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); var vsCompletionList = Assert.IsAssignableFrom(results); Assert.Equal(expectedCommitCharacters, vsCompletionList.CommitCharacters.Value.First); @@ -90,7 +90,7 @@ public async Task TestGetCompletions_PromotesNothingWhenNoCommitCharactersAsync( @"namespace M {{|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -106,7 +106,7 @@ public async Task TestGetCompletions_PromotesNothingWhenNoCommitCharactersAsync( // Null out the commit characters since we're expecting the commit characters will be lifted onto the completion list. expected.CommitCharacters = null; - var results = await RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities).ConfigureAwait(false); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.All(results.Items, item => Assert.Null(item.CommitCharacters)); var vsCompletionList = Assert.IsAssignableFrom(results); Assert.Equal(expectedCommitCharacters, vsCompletionList.CommitCharacters.Value.First); @@ -123,7 +123,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -133,7 +133,7 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: new string[] { "Class", "Internal" }, - request: completionParams, document: document, commitCharacters: null, insertText: "A").ConfigureAwait(false); + request: completionParams, document: document, commitCharacters: null).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -150,7 +150,7 @@ void M() A{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, @@ -160,7 +160,7 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: new string[] { "Class", "Internal" }, - request: completionParams, document: document, commitCharacters: null, insertText: "A").ConfigureAwait(false); + request: completionParams, document: document, commitCharacters: null).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -177,7 +177,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var solution = testLspServer.TestWorkspace.CurrentSolution; // Make sure the unimported types option is on by default. @@ -201,7 +201,8 @@ public async Task TestGetCompletionsUsesSnippetOptionAsync() { {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption( new OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp), SnippetsRule.NeverInclude); @@ -227,7 +228,7 @@ void M() A classA = new {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -237,8 +238,7 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync("A", LSP.CompletionItemKind.Class, new string[] { "Class", "Internal" }, - completionParams, document, preselect: true, commitCharacters: ImmutableArray.Create(' ', '(', '[', '{', ';', '.'), - insertText: "A").ConfigureAwait(false); + completionParams, document, preselect: true, commitCharacters: ImmutableArray.Create(' ', '(', '[', '{', ';', '.')).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -262,7 +262,7 @@ void M() } } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, @@ -286,7 +286,7 @@ void M() DateTime.Now.ToString(""{|caret:|}); } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, @@ -296,7 +296,7 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync( - label: "d", kind: LSP.CompletionItemKind.Text, tags: new string[] { "Text" }, request: completionParams, document: document, insertText: "d", sortText: "0000").ConfigureAwait(false); + label: "d", kind: LSP.CompletionItemKind.Text, tags: new string[] { "Text" }, request: completionParams, document: document, sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -315,7 +315,7 @@ void M() new Regex(""{|caret:|}""); } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -352,7 +352,7 @@ void M() new Regex(@""\{|caret:|}""); } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -389,7 +389,7 @@ void M() Regex r = new(""\\{|caret:|}""); } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, @@ -425,7 +425,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var cache = GetCompletionListCache(testLspServer); Assert.NotNull(cache); @@ -488,7 +488,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Deletion, @@ -519,7 +519,7 @@ class B : A { override {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -546,7 +546,7 @@ partial class C { partial {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -581,7 +581,7 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var results = await RunGetCompletionsAsync(testLspServer, completionParams, new LSP.VSInternalClientCapabilities()).ConfigureAwait(false); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.NotNull(results); Assert.NotEmpty(results.Items); Assert.All(results.Items, (item) => Assert.NotNull(item.CommitCharacters)); @@ -608,10 +608,10 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var results = await RunGetCompletionsAsync(testLspServer, completionParams, new LSP.VSInternalClientCapabilities()).ConfigureAwait(false); + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.NotNull(results); Assert.NotEmpty(results.Items); - Assert.All(results.Items, (item) => Assert.True(item.CommitCharacters.Length == 0)); + Assert.All(results.Items, (item) => Assert.Null(item.CommitCharacters)); } [Fact] @@ -651,7 +651,7 @@ void M() T{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, @@ -703,7 +703,7 @@ void M() W someW = new {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); var completionParams = CreateCompletionParams( @@ -756,7 +756,7 @@ void M() T{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.Uri); @@ -822,7 +822,7 @@ void M() T{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.Uri); @@ -888,7 +888,7 @@ void M() T{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.Uri); @@ -983,7 +983,7 @@ void M2() Console.W{|secondCaret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var firstCaret = testLspServer.GetLocations("firstCaret").Single(); await testLspServer.OpenDocumentAsync(firstCaret.Uri); @@ -1047,7 +1047,7 @@ void M() Ta{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); var completionParams = CreateCompletionParams( @@ -1106,7 +1106,7 @@ void M2() {|secondCaret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var firstCaretLocation = testLspServer.GetLocations("firstCaret").Single(); await testLspServer.OpenDocumentAsync(firstCaretLocation.Uri); @@ -1178,7 +1178,7 @@ void M() {|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, @@ -1232,7 +1232,7 @@ void M() T{|caret:|} } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.Uri); @@ -1262,17 +1262,8 @@ void M() internal static Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { - var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; - return RunGetCompletionsAsync(testLspServer, completionParams, clientCapabilities); - } - - private static async Task RunGetCompletionsAsync( - TestLspServer testLspServer, - LSP.CompletionParams completionParams, - LSP.VSInternalClientCapabilities clientCapabilities) - { - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, - completionParams, clientCapabilities, null, CancellationToken.None); + return testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, + completionParams, CancellationToken.None); } private static CompletionListCache GetCompletionListCache(TestLspServer testLspServer) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs index ee27ce34b44ef..5273013540e74 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs @@ -150,7 +150,7 @@ End Class private static async Task RunGotoDefinitionAsync(TestLspServer testLspServer, LSP.Location caret) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToTypeDefinitionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToTypeDefinitionTests.cs index b68d5c7646a87..9176786d78648 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToTypeDefinitionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToTypeDefinitionTests.cs @@ -79,7 +79,7 @@ class B private static async Task RunGotoTypeDefinitionAsync(TestLspServer testLspServer, LSP.Location caret) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentTypeDefinitionName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 3b61a7839ecb7..88714713717a8 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -37,7 +37,7 @@ public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff(bool useVSDi { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -51,7 +51,7 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -72,7 +72,7 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesWithFSAOffIfInPushMode(bo { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, pullDiagnostics: false); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, pullDiagnostics: false); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -91,7 +91,7 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOf { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -110,7 +110,7 @@ public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(b { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -128,7 +128,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var workspace = testLspServer.TestWorkspace; // Calling GetTextBuffer will effectively open the file. @@ -160,7 +160,7 @@ public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnos { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -186,7 +186,7 @@ public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiag { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -209,7 +209,7 @@ public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDi { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -238,7 +238,7 @@ public async Task TestDocumentDiagnosticsAreNotMapped(bool useVSDiagnostics) var markup = @"#line 1 ""test.txt"" class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -275,7 +275,7 @@ public async Task TestStreamingDocumentDiagnostics(bool useVSDiagnostics) { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -307,7 +307,7 @@ class B {"; "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.OpenFiles); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -322,15 +322,23 @@ class B {"; testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI(), useVSDiagnostics); Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); - Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); + if (useVSDiagnostics) + { + // Only VSDiagnostics will have the project. + var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); + } // Set CSProj1 as the active context and get diagnostics. testLspServer.TestWorkspace.SetDocumentContext(csproj1Document.Id); results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); Assert.Equal(2, results.Single().Diagnostics!.Length); Assert.All(results.Single().Diagnostics, d => Assert.Equal("CS1513", d.Code)); - Assert.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + + if (useVSDiagnostics) + { + Assert.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + } } [Theory, CombinatorialData] @@ -358,7 +366,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -404,7 +412,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -434,7 +442,7 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics) @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, serverKind: WellKnownLspServerKinds.RazorLspServer); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.RazorLspServer); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -458,7 +466,7 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, serverKind: WellKnownLspServerKinds.LiveShareLspServer); + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.LiveShareLspServer); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -486,7 +494,7 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOff(bool useVSD @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.OpenFiles); + new[] { markup1, markup2 }, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -500,7 +508,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiag @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -516,7 +524,7 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOnAndInPushMode @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, pullDiagnostics: false); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics, pullDiagnostics: false); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -542,11 +550,11 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrec "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.True(results.All(r => r.TextDocument!.Uri.OriginalString == "C:\\C.cs")); + Assert.True(results.All(r => r.TextDocument!.Uri.LocalPath == "C:\\C.cs")); } [Theory, CombinatorialData] @@ -556,7 +564,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -590,7 +598,7 @@ public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagno @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -615,7 +623,7 @@ public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDia @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -645,7 +653,7 @@ public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSD @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -682,7 +690,7 @@ public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics) @"class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -703,7 +711,7 @@ public async Task TestWorkspaceDiagnosticsAreNotMapped(bool useVSDiagnostics) class A {"; var markup2 = ""; using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution); + new[] { markup1, markup2 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(2, results.Length); @@ -738,7 +746,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); // Verify we a diagnostic in A.cs since B does not exist @@ -810,7 +818,7 @@ public class {|caret:|} "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj3Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj3").Single().Documents.First(); // Verify we have a diagnostic in C.cs initially. @@ -869,7 +877,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); // Verify we a diagnostic in A.cs since B does not exist @@ -926,7 +934,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); // Verify we a diagnostic in A.cs since B does not exist @@ -981,7 +989,7 @@ public class {|caret:|} { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); // Verify we a diagnostic in A.cs since B does not exist @@ -1037,7 +1045,7 @@ class A : B { } "; - using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution).ConfigureAwait(false); + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); // Verify we a diagnostic in A.cs since B does not exist @@ -1094,8 +1102,6 @@ private static async Task> RunGetDocumentPu var diagnostics = await testLspServer.ExecuteRequestAsync( VSInternalMethods.DocumentPullDiagnosticName, CreateDocumentDiagnosticParams(uri, previousResultId, progress), - new LSP.VSInternalClientCapabilities() { SupportsVisualStudioExtensions = true }, - clientName: null, CancellationToken.None).ConfigureAwait(false); if (useProgress) @@ -1113,8 +1119,6 @@ private static async Task> RunGetDocumentPu var diagnostics = await testLspServer.ExecuteRequestAsync?>( ExperimentalMethods.TextDocumentDiagnostic, CreateProposedDocumentDiagnosticParams(uri, previousResultId, progress), - new LSP.VSInternalClientCapabilities() { SupportsVisualStudioExtensions = true }, - clientName: null, CancellationToken.None).ConfigureAwait(false); if (useProgress) { @@ -1161,8 +1165,6 @@ private static async Task> RunGetWorkspaceP var diagnostics = await testLspServer.ExecuteRequestAsync( VSInternalMethods.WorkspacePullDiagnosticName, CreateWorkspaceDiagnosticParams(previousResults, progress), - new LSP.ClientCapabilities(), - clientName: null, CancellationToken.None).ConfigureAwait(false); if (useProgress) @@ -1180,8 +1182,6 @@ private static async Task> RunGetWorkspaceP var returnedResult = await testLspServer.ExecuteRequestAsync( ExperimentalMethods.WorkspaceDiagnostic, CreateProposedWorkspaceDiagnosticParams(previousResults, progress), - new LSP.VSInternalClientCapabilities() { SupportsVisualStudioExtensions = true }, - clientName: null, CancellationToken.None).ConfigureAwait(false); if (useProgress) @@ -1252,26 +1252,26 @@ private static VSInternalWorkspaceDiagnosticsParams CreateWorkspaceDiagnosticPar }; } - private Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, bool pullDiagnostics = true) - => CreateTestWorkspaceWithDiagnosticsAsync(markup, scope, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); + private Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) + => CreateTestWorkspaceWithDiagnosticsAsync(markup, scope, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push, useVSDiagnostics); - private async Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, DiagnosticMode mode, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + private async Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, DiagnosticMode mode, bool useVSDiagnostics, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) { - var testLspServer = await CreateTestLspServerAsync(markup, serverKind); + var testLspServer = await CreateTestLspServerAsync(markup, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), serverKind); InitializeDiagnostics(scope, testLspServer.TestWorkspace, mode); return testLspServer; } - private async Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool pullDiagnostics = true) + private async Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) { - var testLspServer = await CreateXmlTestLspServerAsync(xmlMarkup); + var testLspServer = await CreateXmlTestLspServerAsync(xmlMarkup, clientCapabilities: useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); return testLspServer; } - private async Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, BackgroundAnalysisScope scope, bool pullDiagnostics = true) + private async Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) { - var testLspServer = await CreateTestLspServerAsync(markups); + var testLspServer = await CreateTestLspServerAsync(markups, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); InitializeDiagnostics(scope, testLspServer.TestWorkspace, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); return testLspServer; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs index f6ba6080e5dca..ca9b74b622a83 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs @@ -3,14 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; using Xunit; @@ -19,9 +14,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.DocumentChanges { public partial class DocumentChangesTests { - protected override TestComposition Composition => base.Composition - .AddParts(typeof(GetLspSolutionHandlerProvider)); - [Fact] public async Task LinkedDocuments_AllTracked() { @@ -44,7 +36,7 @@ public async Task LinkedDocuments_AllTracked() var trackedDocuments = testLspServer.GetQueueAccessor().GetTrackedTexts(); Assert.Equal(1, trackedDocuments.Length); - var solution = await GetLSPSolution(testLspServer, caretLocation.Uri); + var solution = GetLSPSolution(testLspServer, caretLocation.Uri); foreach (var document in solution.Projects.First().Documents) { @@ -95,7 +87,7 @@ void M() await DidChange(testLspServer, caretLocation.Uri, (4, 8, "// hi there")); - var solution = await GetLSPSolution(testLspServer, caretLocation.Uri); + var solution = GetLSPSolution(testLspServer, caretLocation.Uri); foreach (var document in solution.Projects.First().Documents) { @@ -107,40 +99,11 @@ void M() Assert.Empty(testLspServer.GetQueueAccessor().GetTrackedTexts()); } - private static async Task GetLSPSolution(TestLspServer testLspServer, Uri uri) - { - var result = await testLspServer.ExecuteRequestAsync(nameof(GetLSPSolutionHandler), uri, new ClientCapabilities(), null, CancellationToken.None); - Contract.ThrowIfNull(result); - return result; - } - - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(GetLSPSolutionHandler.MethodName)] - private class GetLspSolutionHandlerProvider : AbstractRequestHandlerProvider - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public GetLspSolutionHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) => ImmutableArray.Create(new GetLSPSolutionHandler()); - } - - private class GetLSPSolutionHandler : IRequestHandler + private static Solution GetLSPSolution(TestLspServer testLspServer, Uri uri) { - public const string MethodName = nameof(GetLSPSolutionHandler); - - public string Method => MethodName; - - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; - - public TextDocumentIdentifier? GetTextDocumentIdentifier(Uri request) - => new TextDocumentIdentifier { Uri = request }; - - public Task HandleRequestAsync(Uri request, RequestContext context, CancellationToken cancellationToken) - => Task.FromResult(context.Solution!); + var lspDocument = testLspServer.GetManager().GetLspDocument(new TextDocumentIdentifier { Uri = uri }, null); + Contract.ThrowIfNull(lspDocument); + return lspDocument.Project.Solution; } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs index df2c3eb44f08d..6f7945652507d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs @@ -102,7 +102,7 @@ void M() { await DidOpen(testLspServer, locationTyped.Uri); - await Assert.ThrowsAsync(() => DidOpen(testLspServer, locationTyped.Uri)); + await Assert.ThrowsAsync(() => DidOpen(testLspServer, locationTyped.Uri)); } } @@ -121,7 +121,7 @@ void M() using (testLspServer) { - await Assert.ThrowsAsync(() => DidClose(testLspServer, locationTyped.Uri)); + await Assert.ThrowsAsync(() => DidClose(testLspServer, locationTyped.Uri)); } } @@ -140,7 +140,7 @@ void M() using (testLspServer) { - await Assert.ThrowsAsync(() => DidChange(testLspServer, locationTyped.Uri, (0, 0, "goo"))); + await Assert.ThrowsAsync(() => DidChange(testLspServer, locationTyped.Uri, (0, 0, "goo"))); } } @@ -317,7 +317,7 @@ void M() private async Task<(TestLspServer, LSP.Location, string)> GetTestLspServerAndLocationAsync(string source) { - var testLspServer = await CreateTestLspServerAsync(source); + var testLspServer = await CreateTestLspServerAsync(source, CapabilitiesWithVSExtensions); var locationTyped = testLspServer.GetLocations("type").Single(); var documentText = await testLspServer.GetCurrentSolution().GetDocuments(locationTyped.Uri).Single().GetTextAsync(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs index 321ed11e85d18..8f133cd53ea5f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs @@ -72,7 +72,7 @@ public async Task TestGetFoldingRangeAsync_Regions() }; return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentFoldingRangeName, - request, new LSP.ClientCapabilities(), null, CancellationToken.None); + request, CancellationToken.None); } private static LSP.FoldingRange CreateFoldingRange(LSP.FoldingRangeKind kind, LSP.Range range) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs index 1702121bb6618..20f9e9461f55f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentOnTypeTests.cs @@ -86,7 +86,7 @@ void M() { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentOnTypeFormattingName, CreateDocumentOnTypeFormattingParams( - characterTyped, locationTyped, insertSpaces, tabSize), new LSP.ClientCapabilities(), null, CancellationToken.None); + characterTyped, locationTyped, insertSpaces, tabSize), CancellationToken.None); } private static LSP.DocumentOnTypeFormattingParams CreateDocumentOnTypeFormattingParams( diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs index 8480e9d27cdc1..514f4504ee7ff 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentRangeTests.cs @@ -80,8 +80,6 @@ void M() return await testLspServer.ExecuteRequestAsync( LSP.Methods.TextDocumentRangeFormattingName, CreateDocumentRangeFormattingParams(location, insertSpaces, tabSize), - new LSP.ClientCapabilities(), - clientName: null, CancellationToken.None); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs index 275cbf026edbb..1f41b900316c2 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Formatting/FormatDocumentTests.cs @@ -107,7 +107,7 @@ void M() int tabSize = 4) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentFormattingName, - CreateDocumentFormattingParams(uri, insertSpaces, tabSize), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateDocumentFormattingParams(uri, insertSpaces, tabSize), CancellationToken.None); } private static LSP.DocumentFormattingParams CreateDocumentFormattingParams(Uri uri, bool insertSpaces, int tabSize) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Highlights/DocumentHighlightTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Highlights/DocumentHighlightTests.cs index e0fe7ff9c44a7..fbb668f7aa5c7 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Highlights/DocumentHighlightTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Highlights/DocumentHighlightTests.cs @@ -91,7 +91,7 @@ void M() private static async Task RunGetDocumentHighlightAsync(TestLspServer testLspServer, LSP.Location caret) { var results = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDocumentHighlightName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); Array.Sort(results, (h1, h2) => { var compareKind = h1.Kind.CompareTo(h2.Kind); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index b37d23ebc8661..c2fa4a2157de5 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -32,7 +32,7 @@ public async Task TestGetHoverAsync() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var expectedLocation = testLspServer.GetLocations("caret").Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); @@ -56,7 +56,7 @@ public async Task TestGetHoverAsync_WithExceptions() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var expectedLocation = testLspServer.GetLocations("caret").Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); @@ -79,7 +79,7 @@ public async Task TestGetHoverAsync_WithRemarks() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var expectedLocation = testLspServer.GetLocations("caret").Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); @@ -107,7 +107,7 @@ public async Task TestGetHoverAsync_WithList() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var expectedLocation = testLspServer.GetLocations("caret").Single(); var results = await RunGetHoverAsync(testLspServer, expectedLocation).ConfigureAwait(false); @@ -179,7 +179,7 @@ static void Main(string[] args) "; - using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml); + using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, clientCapabilities: CapabilitiesWithVSExtensions); var location = testLspServer.GetLocations("caret").Single(); foreach (var project in testLspServer.GetCurrentSolution().Projects) @@ -229,7 +229,11 @@ public async Task TestGetHoverAsync_UsingMarkupContent() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + var clientCapabilities = new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } + }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var expectedLocation = testLspServer.GetLocations("caret").Single(); var expectedMarkdown = @$"```csharp @@ -257,11 +261,7 @@ [link text](https://google.com) var results = await RunGetHoverAsync( testLspServer, - expectedLocation, - clientCapabilities: new LSP.ClientCapabilities - { - TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } - }).ConfigureAwait(false); + expectedLocation).ConfigureAwait(false); Assert.Equal(expectedMarkdown, results.Contents.Third.Value); } @@ -328,8 +328,7 @@ a string var results = await RunGetHoverAsync( testLspServer, - expectedLocation, - clientCapabilities: new LSP.ClientCapabilities()).ConfigureAwait(false); + expectedLocation).ConfigureAwait(false); Assert.Equal(expectedText, results.Contents.Third.Value); } @@ -360,7 +359,11 @@ public async Task TestGetHoverAsync_UsingMarkupContentProperlyEscapes() { } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + var clientCapabilities = new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } + }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var expectedLocation = testLspServer.GetLocations("caret").Single(); var expectedMarkdown = @"```csharp @@ -379,23 +382,17 @@ void A.AMethod(int i) var results = await RunGetHoverAsync( testLspServer, - expectedLocation, - clientCapabilities: new LSP.ClientCapabilities - { - TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = new LSP.MarkupKind[] { LSP.MarkupKind.Markdown } } } - }).ConfigureAwait(false); + expectedLocation).ConfigureAwait(false); Assert.Equal(expectedMarkdown, results.Contents.Third.Value); } private static async Task RunGetHoverAsync( TestLspServer testLspServer, LSP.Location caret, - ProjectId projectContext = null, - LSP.ClientCapabilities clientCapabilities = null) + ProjectId projectContext = null) { - clientCapabilities ??= new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentHoverName, - CreateTextDocumentPositionParams(caret, projectContext), clientCapabilities, null, CancellationToken.None); + CreateTextDocumentPositionParams(caret, projectContext), CancellationToken.None); } private void VerifyVSContent(LSP.Hover hover, string expectedContent) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/InlineCompletions/InlineCompletionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/InlineCompletions/InlineCompletionsTests.cs index 3e4fb32e12d21..d643c86e39c7c 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/InlineCompletions/InlineCompletionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/InlineCompletions/InlineCompletionsTests.cs @@ -261,8 +261,8 @@ private async Task VerifyMarkupAndExpected(string markup, string expected, LSP.F Options = options }; - var response = await testLspServer.ExecuteRequestAsync(LSP.VSInternalMethods.TextDocumentInlineCompletionName, - request, new LSP.ClientCapabilities(), null, CancellationToken.None); + var response = await testLspServer.ExecuteRequestAsync( + LSP.VSInternalMethods.TextDocumentInlineCompletionName, request, CancellationToken.None); Contract.ThrowIfNull(response); return response; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs index 9556f4a82ad74..6e1cdf2e47be1 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs @@ -2,21 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Nerdbank.Streams; using Roslyn.Test.Utilities; -using StreamJsonRpc; using Xunit; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests @@ -27,81 +15,48 @@ public class LanguageServerTargetTests : AbstractLanguageServerProtocolTests [Fact] public async Task LanguageServerQueueEmptyOnShutdownMessage() { - await using var languageServerTarget = CreateLanguageServer(out var jsonRpc); - AssertServerAlive(languageServerTarget); + var server = await CreateTestLspServerAsync(""); + AssertServerAlive(server); - await languageServerTarget.ShutdownAsync(CancellationToken.None).ConfigureAwait(false); - await AssertServerQueueClosed(languageServerTarget).ConfigureAwait(false); - Assert.False(jsonRpc.IsDisposed); + server.GetServerAccessor().ShutdownServer(); + await AssertServerQueueClosed(server).ConfigureAwait(false); + Assert.False(server.GetServerAccessor().GetServerRpc().IsDisposed); } [Fact] public async Task LanguageServerCleansUpOnExitMessage() { - await using var languageServerTarget = CreateLanguageServer(out var jsonRpc); - AssertServerAlive(languageServerTarget); + var server = await CreateTestLspServerAsync(""); + AssertServerAlive(server); - await languageServerTarget.ShutdownAsync(CancellationToken.None).ConfigureAwait(false); - await languageServerTarget.ExitAsync(CancellationToken.None).ConfigureAwait(false); - await AssertServerQueueClosed(languageServerTarget).ConfigureAwait(false); - Assert.True(jsonRpc.IsDisposed); + server.GetServerAccessor().ShutdownServer(); + server.GetServerAccessor().ExitServer(); + await AssertServerQueueClosed(server).ConfigureAwait(false); + Assert.True(server.GetServerAccessor().GetServerRpc().IsDisposed); } [Fact] public async Task LanguageServerCleansUpOnUnexpectedJsonRpcDisconnectAsync() { - await using var languageServerTarget = CreateLanguageServer(out var jsonRpc); - AssertServerAlive(languageServerTarget); + using var server = await CreateTestLspServerAsync(""); + AssertServerAlive(server); - jsonRpc.Dispose(); - await AssertServerQueueClosed(languageServerTarget).ConfigureAwait(false); - Assert.True(jsonRpc.IsDisposed); + server.GetServerAccessor().GetServerRpc().Dispose(); + await AssertServerQueueClosed(server).ConfigureAwait(false); + Assert.True(server.GetServerAccessor().GetServerRpc().IsDisposed); } - private static void AssertServerAlive(LanguageServerTarget server) + private static void AssertServerAlive(TestLspServer server) { - Assert.False(server.HasShutdownStarted); - Assert.False(server.GetTestAccessor().GetQueueAccessor().IsComplete()); + Assert.False(server.GetServerAccessor().HasShutdownStarted()); + Assert.False(server.GetQueueAccessor().IsComplete()); } - private static async Task AssertServerQueueClosed(LanguageServerTarget server) + private static async Task AssertServerQueueClosed(TestLspServer server) { - await server.GetTestAccessor().GetQueueAccessor().WaitForProcessingToStopAsync().ConfigureAwait(false); - Assert.True(server.HasShutdownStarted); - Assert.True(server.GetTestAccessor().GetQueueAccessor().IsComplete()); - } - - private LanguageServerTarget CreateLanguageServer(out JsonRpc serverJsonRpc) - { - using var workspace = TestWorkspace.CreateCSharp("", composition: Composition); - - var (_, serverStream) = FullDuplexStream.CreatePair(); - var dispatcherFactory = workspace.GetService(); - var lspWorkspaceRegistrationService = workspace.GetService(); - var capabilitiesProvider = workspace.GetService(); - var globalOptions = workspace.GetService(); - var listenerProvider = workspace.GetService(); - - serverJsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream)) - { - ExceptionStrategy = ExceptionProcessing.ISerializable, - }; - - var languageServer = new LanguageServerTarget( - dispatcherFactory, - serverJsonRpc, - capabilitiesProvider, - lspWorkspaceRegistrationService, - new LspMiscellaneousFilesWorkspace(NoOpLspLogger.Instance), - globalOptions, - listenerProvider, - NoOpLspLogger.Instance, - ProtocolConstants.RoslynLspLanguages, - clientName: null, - WellKnownLspServerKinds.AlwaysActiveVSLspServer); - - serverJsonRpc.StartListening(); - return languageServer; + await server.GetQueueAccessor().WaitForProcessingToStopAsync().ConfigureAwait(false); + Assert.True(server.GetServerAccessor().HasShutdownStarted()); + Assert.True(server.GetQueueAccessor().IsComplete()); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index f1fbc5e02a56c..9eb067f713948 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -189,7 +189,7 @@ void M() private static async Task RunGetHoverAsync(TestLspServer testLspServer, LSP.Location caret) { var result = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentHoverName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); Contract.ThrowIfNull(result); return result; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs index 84d70dedba389..b1e29203a2d43 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs @@ -339,7 +339,7 @@ private async Task VerifyNoResult(string characterTyped, string markup, bool ins int tabSize) { return await testLspServer.ExecuteRequestAsync(VSInternalMethods.OnAutoInsertName, - CreateDocumentOnAutoInsertParams(characterTyped, locationTyped, insertSpaces, tabSize), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateDocumentOnAutoInsertParams(characterTyped, locationTyped, insertSpaces, tabSize), CancellationToken.None); } private static LSP.VSInternalDocumentOnAutoInsertParams CreateDocumentOnAutoInsertParams( diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingMutatingRequestHandler.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingMutatingRequestHandler.cs index 95ffc9ac764e9..031c3fb6c644e 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingMutatingRequestHandler.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingMutatingRequestHandler.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable + using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -13,35 +13,25 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(FailingMutatingRequestHandler.MethodName)] - internal class FailingMutatingRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FailingMutatingRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class FailingMutatingRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(FailingMutatingRequestHandler); + private const int Delay = 100; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FailingMutatingRequestHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) + public FailingMutatingRequestHandler() { - return ImmutableArray.Create(new FailingMutatingRequestHandler()); } - } - - internal class FailingMutatingRequestHandler : IRequestHandler - { - public const string MethodName = nameof(FailingMutatingRequestHandler); - private const int Delay = 100; - - public string Method => MethodName; - public bool MutatesSolutionState => true; - public bool RequiresLSPSolution => true; + public override bool MutatesSolutionState => true; + public override bool RequiresLSPSolution => true; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { await Task.Delay(Delay, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingRequestHandler.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingRequestHandler.cs index b655d3aa53504..ca787d4129f3e 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingRequestHandler.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/FailingRequestHandler.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -15,35 +14,25 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(FailingRequestHandler.MethodName)] - internal class FailingRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(FailingRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class FailingRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(FailingRequestHandler); + private const int Delay = 100; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FailingRequestHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) + public FailingRequestHandler() { - return ImmutableArray.Create(new FailingRequestHandler()); } - } - - internal class FailingRequestHandler : IRequestHandler - { - public const string MethodName = nameof(FailingRequestHandler); - private const int Delay = 100; - - public string Method => MethodName; - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; + public override bool MutatesSolutionState => false; + public override bool RequiresLSPSolution => true; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { await Task.Delay(Delay, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/LongRunningNonMutatingRequestHandler.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/LongRunningNonMutatingRequestHandler.cs index 112aecd50dee0..5853e12dab00d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/LongRunningNonMutatingRequestHandler.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/LongRunningNonMutatingRequestHandler.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -16,32 +15,25 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(LongRunningNonMutatingRequestHandler.MethodName)] - internal class LongRunningNonMutatingRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(LongRunningNonMutatingRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class LongRunningNonMutatingRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(LongRunningNonMutatingRequestHandler); + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LongRunningNonMutatingRequestHandlerProvider() + public LongRunningNonMutatingRequestHandler() { } - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) => ImmutableArray.Create(new LongRunningNonMutatingRequestHandler()); - } - - internal class LongRunningNonMutatingRequestHandler : IRequestHandler - { - public const string MethodName = nameof(LongRunningNonMutatingRequestHandler); - - public string Method => MethodName; - - public bool MutatesSolutionState => false; + public override bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; + public override bool RequiresLSPSolution => true; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { do { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/MutatingRequestHandler.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/MutatingRequestHandler.cs index f2dfeed623a38..f1a9f84b1ae17 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/MutatingRequestHandler.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/MutatingRequestHandler.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -14,39 +13,28 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(MutatingRequestHandler.MethodName)] - internal class MutatingRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(MutatingRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class MutatingRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(MutatingRequestHandler); + private const int Delay = 100; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public MutatingRequestHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) + public MutatingRequestHandler() { - return ImmutableArray.Create(new MutatingRequestHandler()); } - } - - internal class MutatingRequestHandler : IRequestHandler - { - public const string MethodName = nameof(MutatingRequestHandler); - private const int Delay = 100; - - public string Method => MethodName; - public bool MutatesSolutionState => true; - public bool RequiresLSPSolution => true; + public override bool MutatesSolutionState => true; + public override bool RequiresLSPSolution => true; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { var response = new TestResponse { - Solution = context.Solution, StartTime = DateTime.UtcNow }; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonLSPSolutionRequestHandlerProvider.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonLSPSolutionRequestHandlerProvider.cs index 74b4e67e6b70e..b9cdc5b5cfc3f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonLSPSolutionRequestHandlerProvider.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonLSPSolutionRequestHandlerProvider.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -16,34 +15,24 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(NonLSPSolutionRequestHandler.MethodName)] - internal class NonLSPSolutionRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(NonLSPSolutionRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class NonLSPSolutionRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(NonLSPSolutionRequestHandler); + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public NonLSPSolutionRequestHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) + public NonLSPSolutionRequestHandler() { - return ImmutableArray.Create(new NonLSPSolutionRequestHandler()); } - } - - internal class NonLSPSolutionRequestHandler : IRequestHandler - { - public const string MethodName = nameof(NonLSPSolutionRequestHandler); - - public string Method => MethodName; - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => false; + public override bool MutatesSolutionState => false; + public override bool RequiresLSPSolution => false; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { Assert.Null(context.Solution); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonMutatingRequestHandler.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonMutatingRequestHandler.cs index 03bedb57afc54..4eadbfd198671 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonMutatingRequestHandler.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/NonMutatingRequestHandler.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -14,39 +13,29 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering { - [Shared, ExportRoslynLanguagesLspRequestHandlerProvider, PartNotDiscoverable] - [ProvidesMethod(NonMutatingRequestHandler.MethodName)] - internal class NonMutatingRequestHandlerProvider : AbstractRequestHandlerProvider + [Shared, ExportRoslynLanguagesLspRequestHandlerProvider(typeof(NonMutatingRequestHandler)), PartNotDiscoverable] + [Method(MethodName)] + internal class NonMutatingRequestHandler : AbstractStatelessRequestHandler { + public const string MethodName = nameof(NonMutatingRequestHandler); + private const int Delay = 100; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public NonMutatingRequestHandlerProvider() - { - } - - public override ImmutableArray CreateRequestHandlers(WellKnownLspServerKinds serverKind) + public NonMutatingRequestHandler() { - return ImmutableArray.Create(new NonMutatingRequestHandler()); } - } - - internal class NonMutatingRequestHandler : IRequestHandler - { - public const string MethodName = nameof(NonMutatingRequestHandler); - private const int Delay = 100; - - public string Method => nameof(NonMutatingRequestHandler); - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; + public override bool MutatesSolutionState => false; + public override bool RequiresLSPSolution => true; - public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; + public override TextDocumentIdentifier GetTextDocumentIdentifier(TestRequest request) => null; - public async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) + public override async Task HandleRequestAsync(TestRequest request, RequestContext context, CancellationToken cancellationToken) { var response = new TestResponse(); - response.Solution = context.Solution; + response.ContextHasSolution = context.Solution != null; response.StartTime = DateTime.UtcNow; await Task.Delay(Delay, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs index 30e66d9b0eddf..d62499f04d08a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs @@ -20,12 +20,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.RequestOrdering public partial class RequestOrderingTests : AbstractLanguageServerProtocolTests { protected override TestComposition Composition => base.Composition - .AddParts(typeof(MutatingRequestHandlerProvider)) - .AddParts(typeof(NonMutatingRequestHandlerProvider)) - .AddParts(typeof(FailingRequestHandlerProvider)) - .AddParts(typeof(FailingMutatingRequestHandlerProvider)) - .AddParts(typeof(NonLSPSolutionRequestHandlerProvider)) - .AddParts(typeof(LongRunningNonMutatingRequestHandlerProvider)); + .AddParts(typeof(MutatingRequestHandler)) + .AddParts(typeof(NonMutatingRequestHandler)) + .AddParts(typeof(FailingRequestHandler)) + .AddParts(typeof(FailingMutatingRequestHandler)) + .AddParts(typeof(NonLSPSolutionRequestHandler)) + .AddParts(typeof(LongRunningNonMutatingRequestHandler)); [Fact] public async Task MutatingRequestsDontOverlap() @@ -114,7 +114,7 @@ public async Task ThrowingTaskDoesntBringDownQueue() var waitables = StartTestRun(testLspServer, requests); // first task should fail - await Assert.ThrowsAsync(() => waitables[0]); + await Assert.ThrowsAsync(() => waitables[0]); // remaining tasks should have executed normally var responses = await Task.WhenAll(waitables.Skip(1)); @@ -169,7 +169,7 @@ public async Task FailingMutableTaskShutsDownQueue() var waitables = StartTestRun(testLspServer, requests); // first task should fail - await Assert.ThrowsAsync(() => waitables[0]); + await Assert.ThrowsAsync(() => waitables[0]); // The failed request returns to the client before the shutdown completes. // Wait for the queue to finish handling the failed request and shutdown. @@ -246,15 +246,22 @@ private static async Task ExecuteDidOpen(TestLspServer testLspServer, Uri docume Text = "// hi there" } }; - await testLspServer.ExecuteRequestAsync(Methods.TextDocumentDidOpenName, didOpenParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + await testLspServer.ExecuteRequestAsync(Methods.TextDocumentDidOpenName, didOpenParams, CancellationToken.None); } - private static async Task GetLSPSolution(TestLspServer testLspServer, string methodName) + private static async Task GetLSPSolution(TestLspServer testLspServer, string methodName) { var request = new TestRequest(methodName); - var response = await testLspServer.ExecuteRequestAsync(request.MethodName, request, new LSP.ClientCapabilities(), null, CancellationToken.None); + var response = await testLspServer.ExecuteRequestAsync(request.MethodName, request, CancellationToken.None); Contract.ThrowIfNull(response); - return response.Solution; + if (response.ContextHasSolution) + { + var solution = testLspServer.GetManager().TryGetHostLspSolution(); + Contract.ThrowIfNull(solution); + return solution; + } + + return null; } private static async Task TestAsync(TestLspServer testLspServer, TestRequest[] requests) @@ -273,11 +280,9 @@ private static async Task TestAsync(TestLspServer testLspServer, private static List> StartTestRun(TestLspServer testLspServer, TestRequest[] requests, CancellationToken cancellationToken = default) { - var clientCapabilities = new LSP.ClientCapabilities(); - var waitables = new List>(); foreach (var request in requests) - waitables.Add(testLspServer.ExecuteRequestAsync(request.MethodName, request, clientCapabilities, null, cancellationToken)); + waitables.Add(testLspServer.ExecuteRequestAsync(request.MethodName, request, cancellationToken)); return waitables; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/TestResponse.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/TestResponse.cs index 632a509342882..52537931e758e 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/TestResponse.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/TestResponse.cs @@ -11,6 +11,6 @@ internal class TestResponse public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } - public Solution Solution { get; set; } + public bool ContextHasSolution { get; set; } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs index 8b2ec34d7cbb7..60aa9c11ba000 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs @@ -97,7 +97,7 @@ public async Task SwitchingContextsChangesDefaultContext() internal static async Task RunGetProjectContext(TestLspServer testLspServer, Uri uri) { return await testLspServer.ExecuteRequestAsync(LSP.VSMethods.GetProjectContextsName, - CreateGetProjectContextParams(uri), new LSP.ClientCapabilities(), clientName: null, cancellationToken: CancellationToken.None); + CreateGetProjectContextParams(uri), cancellationToken: CancellationToken.None); } private static LSP.VSGetProjectContextsParams CreateGetProjectContextParams(Uri uri) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs index 3b6c51b208d5c..88d9d347be81c 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.VisualStudio.Text.Adornments; +using Newtonsoft.Json.Linq; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -41,7 +42,7 @@ void M2() var j = someInt + A.{|caret:|}{|reference:someInt|}; } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Select(result => result.Location)); @@ -77,7 +78,7 @@ void M2() var j = someInt + A.{|caret:|}{|reference:someInt|}; } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); using var progress = BufferedProgress.Create(null); @@ -87,7 +88,9 @@ void M2() // BufferedProgress wraps individual elements in an array, so when they are nested them like this, // with the test creating one, and the handler another, we have to unwrap. - results = progress.GetValues().Cast().ToArray(); + // Additionally, the VS LSP protocol specifies T from IProgress as an object and not as the actual VSInternalReferenceItem + // so we have to correctly convert the JObject into the expected type. + results = progress.GetValues().Select(reference => ((JObject)reference).ToObject()).ToArray(); Assert.NotNull(results); Assert.NotEmpty(results); @@ -125,7 +128,7 @@ void M2() var j = someInt + {|caret:|}{|reference:A|}.someInt; } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Select(result => result.Location)); @@ -168,7 +171,7 @@ void M2() }" }; - using var testLspServer = await CreateTestLspServerAsync(markups); + using var testLspServer = await CreateTestLspServerAsync(markups, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Select(result => result.Location)); @@ -192,7 +195,7 @@ public async Task TestFindAllReferencesAsync_InvalidLocation() { {|caret:|} }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); Assert.Empty(results); @@ -211,7 +214,7 @@ void M() Console.{|caret:|}{|reference:WriteLine|}(""text""); } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); Assert.NotNull(results[0].Location.Uri); @@ -233,7 +236,7 @@ void M() } } "; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); @@ -263,7 +266,7 @@ void M() } } "; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertHighlightCount(results, expectedDefinitionCount: 1, expectedWrittenReferenceCount: 1, expectedReferenceCount: 1); @@ -275,7 +278,7 @@ public async Task TestFindAllReferencesAsync_StaticClassification() var markup = @"static class {|caret:|}{|reference:C|} { } "; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); @@ -295,13 +298,8 @@ private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret, IPr internal static async Task RunFindAllReferencesAsync(TestLspServer testLspServer, LSP.Location caret, IProgress progress = null) { - var vsClientCapabilities = new LSP.VSInternalClientCapabilities - { - SupportsVisualStudioExtensions = true - }; - var results = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentReferencesName, - CreateReferenceParams(caret, progress), vsClientCapabilities, null, CancellationToken.None); + CreateReferenceParams(caret, progress), CancellationToken.None); return results?.Cast()?.ToArray(); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindImplementationsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindImplementationsTests.cs index b6fba47f373e2..0f145ac8beb1f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/References/FindImplementationsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindImplementationsTests.cs @@ -150,7 +150,7 @@ class C : IDisposable private static async Task RunFindImplementationAsync(TestLspServer testLspServer, LSP.Location caret) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentImplementationName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs index f0855f7f0d1b4..e00c7c239d33e 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs @@ -170,8 +170,7 @@ private static LSP.RenameParams CreateRenameParams(LSP.Location location, string private static async Task RunRenameAsync(TestLspServer testLspServer, LSP.RenameParams renameParams) { - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentRenameName, - renameParams, new LSP.ClientCapabilities(), null, CancellationToken.None); + return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentRenameName, renameParams, CancellationToken.None); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs index 3b8315a864103..e76778c1fa6af 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs @@ -7,6 +7,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -18,8 +21,8 @@ public abstract class AbstractSemanticTokensTests : AbstractLanguageServerProtoc { private protected static async Task RunGetSemanticTokensRangeAsync(TestLspServer testLspServer, LSP.Location caret, LSP.Range range) { - var result = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentSemanticTokensRangeName, - CreateSemanticTokensRangeParams(caret, range), new LSP.VSInternalClientCapabilities(), null, CancellationToken.None); + var result = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentSemanticTokensRangeName, + CreateSemanticTokensRangeParams(caret, range), CancellationToken.None); Contract.ThrowIfNull(result); return result; } @@ -31,6 +34,12 @@ private static LSP.SemanticTokensRangeParams CreateSemanticTokensRangeParams(LSP Range = range }; + protected static async Task UpdateDocumentTextAsync(string updatedText, Workspace workspace) + { + var docId = ((TestWorkspace)workspace).Documents.First().Id; + await ((TestWorkspace)workspace).ChangeDocumentAsync(docId, SourceText.From(updatedText)); + } + // VS doesn't currently support multi-line tokens, so we want to verify that we aren't // returning any in the tokens array. private protected static async Task VerifyNoMultiLineTokens(TestLspServer testLspServer, int[] tokens) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SignatureHelp/SignatureHelpTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SignatureHelp/SignatureHelpTests.cs index 64507fd85008f..b3262e025cc07 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SignatureHelp/SignatureHelpTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SignatureHelp/SignatureHelpTests.cs @@ -50,7 +50,7 @@ int M2(string a) { return await testLspServer.ExecuteRequestAsync( LSP.Methods.TextDocumentSignatureHelpName, - CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None); + CreateTextDocumentPositionParams(caret), CancellationToken.None); } private static LSP.SignatureInformation CreateSignatureInformation(string methodLabal, string methodDocumentation, string parameterLabel, string parameterDocumentation) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs index af86521433832..87a3d8e18b118 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs @@ -26,14 +26,24 @@ public async Task TestGetDocumentSymbolsAsync() { }|} }|}"; - using var testLspServer = await CreateTestLspServerAsync(markup); + var clientCapabilities = new LSP.ClientCapabilities() + { + TextDocument = new LSP.TextDocumentClientCapabilities() + { + DocumentSymbol = new LSP.DocumentSymbolSetting() + { + HierarchicalDocumentSymbolSupport = true + } + } + }; + using var testLspServer = await CreateTestLspServerAsync(markup, clientCapabilities); var expected = new LSP.DocumentSymbol[] { CreateDocumentSymbol(LSP.SymbolKind.Class, "A", "A", testLspServer.GetLocations("class").Single(), testLspServer.GetLocations("classSelection").Single()) }; CreateDocumentSymbol(LSP.SymbolKind.Method, "M", "M()", testLspServer.GetLocations("method").Single(), testLspServer.GetLocations("methodSelection").Single(), expected.First()); - var results = await RunGetDocumentSymbolsAsync(testLspServer, true); + var results = await RunGetDocumentSymbolsAsync(testLspServer); AssertJsonEquals(expected, results); } @@ -54,7 +64,7 @@ public async Task TestGetDocumentSymbolsAsync__WithoutHierarchicalSupport() CreateSymbolInformation(LSP.SymbolKind.Method, "M()", testLspServer.GetLocations("method").Single(), Glyph.MethodPrivate, "A") }; - var results = await RunGetDocumentSymbolsAsync(testLspServer, false); + var results = await RunGetDocumentSymbolsAsync(testLspServer); AssertJsonEquals(expected, results); } @@ -72,7 +82,7 @@ void Method() } }"; using var testLspServer = await CreateTestLspServerAsync(markup); - var results = await RunGetDocumentSymbolsAsync(testLspServer, false).ConfigureAwait(false); + var results = await RunGetDocumentSymbolsAsync(testLspServer).ConfigureAwait(false); Assert.Equal(3, results.Length); } @@ -81,11 +91,11 @@ public async Task TestGetDocumentSymbolsAsync__NoSymbols() { using var testLspServer = await CreateTestLspServerAsync(string.Empty); - var results = await RunGetDocumentSymbolsAsync(testLspServer, true); + var results = await RunGetDocumentSymbolsAsync(testLspServer); Assert.Empty(results); } - private static async Task RunGetDocumentSymbolsAsync(TestLspServer testLspServer, bool hierarchicalSupport) + private static async Task RunGetDocumentSymbolsAsync(TestLspServer testLspServer) { var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var request = new LSP.DocumentSymbolParams @@ -93,19 +103,8 @@ private static async Task RunGetDocumentSymbolsAsync(TestLspServer tes TextDocument = CreateTextDocumentIdentifier(new Uri(document.FilePath)) }; - var clientCapabilities = new LSP.ClientCapabilities() - { - TextDocument = new LSP.TextDocumentClientCapabilities() - { - DocumentSymbol = new LSP.DocumentSymbolSetting() - { - HierarchicalDocumentSymbolSupport = hierarchicalSupport - } - } - }; - - return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDocumentSymbolName, - request, clientCapabilities, null, CancellationToken.None); + return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDocumentSymbolName, + request, CancellationToken.None); } private static void AssertDocumentSymbolEquals(LSP.DocumentSymbol expected, LSP.DocumentSymbol actual) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs index d9275801b67d6..62ab70aadee6a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs @@ -210,7 +210,7 @@ End Sub }; return testLspServer.ExecuteRequestAsync(LSP.Methods.WorkspaceSymbolName, - request, new LSP.ClientCapabilities(), null, CancellationToken.None); + request, CancellationToken.None); } private static string GetContainerName(Solution solution, string? containingSymbolName = null) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index 85f8c12cd37fc..9ac86e8d92161 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -469,8 +469,8 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync() var documentUri = testWorkspace.CurrentSolution.Projects.First().Documents.First().GetURI(); - using var testLspServerOne = new TestLspServer(testWorkspace); - using var testLspServerTwo = new TestLspServer(testWorkspace); + using var testLspServerOne = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + using var testLspServerTwo = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); Assert.NotEqual(testLspServerOne.GetManager(), testLspServerTwo.GetManager()); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs index 881ce5a012c98..0537fa14c91fc 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/CodeActions/CodeActionsHandlerProvider.cs @@ -14,8 +14,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { @@ -26,10 +24,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler /// /// Same as C# and VB but for XAML. See also . /// - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(LSP.Methods.TextDocumentCodeActionName)] - [ProvidesMethod(LSP.Methods.CodeActionResolveName)] - [ProvidesCommand(CodeActionsHandler.RunCodeActionCommandName)] + [ExportXamlLspRequestHandlerProvider(typeof(CodeActionsHandler), typeof(CodeActionResolveHandler), typeof(RunCodeActionHandler)), Shared] internal class CodeActionsHandlerProvider : AbstractRequestHandlerProvider { private readonly ICodeFixService _codeFixService; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs index 42f6fe81d6a0d..b6cb52bf8cec3 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs @@ -19,6 +19,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageSe /// /// Handle the command that adds an event handler method in code /// + [Command(StringConstants.CreateEventHandlerCommand)] internal class CreateEventCommandHandler : AbstractExecuteWorkspaceCommandHandler { public override string Command => StringConstants.CreateEventHandlerCommand; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandlerProvider.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandlerProvider.cs index 6e93c43f612e2..586464ac8afa2 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandlerProvider.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandlerProvider.cs @@ -10,11 +10,11 @@ using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; +using Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer; namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageServer.Handler.Commands { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesCommand(StringConstants.CreateEventHandlerCommand)] + [ExportXamlLspRequestHandlerProvider(typeof(CreateEventCommandHandler)), Shared] internal class CreateEventCommandHandlerProvider : AbstractRequestHandlerProvider { [ImportingConstructor] diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs index a9311f8ffd7e4..452cd53d616b3 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs @@ -24,11 +24,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler /// /// Handle a completion request. /// - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentCompletionName)] + [ExportXamlLspRequestHandlerProvider(typeof(CompletionHandler)), Shared] + [Method(Methods.TextDocumentCompletionName)] internal class CompletionHandler : AbstractStatelessRequestHandler { - public override string Method => Methods.TextDocumentCompletionName; private const string CreateEventHandlerCommandTitle = "Create Event Handler"; private static readonly Command s_retriggerCompletionCommand = new Command() diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs index 4bbf661b4a744..d92ee3b29540b 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs @@ -27,14 +27,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler /// /// Handle a completion resolve request to add description. /// - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(LSP.Methods.TextDocumentCompletionResolveName)] + [ExportXamlLspRequestHandlerProvider(typeof(CompletionResolveHandler)), Shared] + [Method(LSP.Methods.TextDocumentCompletionResolveName)] internal class CompletionResolveHandler : AbstractStatelessRequestHandler { private readonly IGlobalOptionService _globalOptions; - public override string Method => LSP.Methods.TextDocumentCompletionResolveName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index 5eff03c391abd..73c91a2d8cd49 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -20,13 +20,14 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Definitions; +using Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer; using Roslyn.Utilities; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageServer.Handler.Definitions { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentDefinitionName)] + [ExportXamlLspRequestHandlerProvider(typeof(GoToDefinitionHandler)), Shared] + [Method(Methods.TextDocumentDefinitionName)] internal class GoToDefinitionHandler : AbstractStatelessRequestHandler { private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; @@ -38,8 +39,6 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe _metadataAsSourceFileService = metadataAsSourceFileService; } - public override string Method => Methods.TextDocumentDefinitionName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 81a655ca34e64..08ab143035859 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -11,11 +11,12 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Diagnostics; +using Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer; namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageServer.Handler.Diagnostics { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(VSInternalMethods.DocumentPullDiagnosticName)] + [ExportXamlLspRequestHandlerProvider(typeof(DocumentPullDiagnosticHandler)), Shared] + [Method(VSInternalMethods.DocumentPullDiagnosticName)] internal class DocumentPullDiagnosticHandler : AbstractPullDiagnosticHandler { [ImportingConstructor] @@ -25,8 +26,6 @@ public DocumentPullDiagnosticHandler( : base(xamlPullDiagnosticService) { } - public override string Method => VSInternalMethods.DocumentPullDiagnosticName; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams request) => request.TextDocument; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index f966b18ea3fe8..6502af8cbed76 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -14,12 +14,13 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageServer.Extensions; +using Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageServer.Handler.Diagnostics { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(VSInternalMethods.WorkspacePullDiagnosticName)] + [ExportXamlLspRequestHandlerProvider(typeof(WorkspacePullDiagnosticHandler)), Shared] + [Method(VSInternalMethods.WorkspacePullDiagnosticName)] internal class WorkspacePullDiagnosticHandler : AbstractPullDiagnosticHandler { [ImportingConstructor] @@ -29,8 +30,6 @@ public WorkspacePullDiagnosticHandler( : base(xamlPullDiagnosticService) { } - public override string Method => VSInternalMethods.WorkspacePullDiagnosticName; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalWorkspaceDiagnosticsParams request) => null; protected override VSInternalWorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier? identifier, VSDiagnostic[]? diagnostics, string? resultId) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/FoldingRanges/FoldingRangesHandler.cs index d936116e96db4..e7a5beab88d3f 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -15,8 +15,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentFoldingRangeName)] + [ExportXamlLspRequestHandlerProvider(typeof(FoldingRangesHandler)), Shared] + [Method(Methods.TextDocumentFoldingRangeName)] internal class FoldingRangesHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -25,8 +25,6 @@ public FoldingRangesHandler() { } - public override string Method => Methods.TextDocumentFoldingRangeName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs index 41008fd3fbba0..412636961380d 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs @@ -13,8 +13,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(LSP.Methods.TextDocumentFormattingName)] + [ExportXamlLspRequestHandlerProvider(typeof(FormatDocumentHandler)), Shared] + [Method(LSP.Methods.TextDocumentFormattingName)] internal class FormatDocumentHandler : AbstractFormatDocumentHandlerBase { [ImportingConstructor] @@ -23,8 +23,6 @@ public FormatDocumentHandler() { } - public override string Method => LSP.Methods.TextDocumentFormattingName; - public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(LSP.DocumentFormattingParams request, RequestContext context, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 8bb993af21e32..30d1a5f660369 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -19,8 +19,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentOnTypeFormattingName)] + [ExportXamlLspRequestHandlerProvider(typeof(FormatDocumentOnTypeHandler)), Shared] + [Method(Methods.TextDocumentOnTypeFormattingName)] internal class FormatDocumentOnTypeHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -29,8 +29,6 @@ public FormatDocumentOnTypeHandler() { } - public override string Method => Methods.TextDocumentOnTypeFormattingName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs index 504c4b5665a36..f67afcb105a91 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -13,8 +13,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentRangeFormattingName)] + [ExportXamlLspRequestHandlerProvider(typeof(FormatDocumentRangeHandler)), Shared] + [Method(Methods.TextDocumentRangeFormattingName)] internal class FormatDocumentRangeHandler : AbstractFormatDocumentHandlerBase { [ImportingConstructor] @@ -23,8 +23,6 @@ public FormatDocumentRangeHandler() { } - public override string Method => Methods.TextDocumentRangeFormattingName; - public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentRangeFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs index 23c0be4fd7e7d..f43439072400c 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Hover/HoverHandler.cs @@ -23,8 +23,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentHoverName)] + [ExportXamlLspRequestHandlerProvider(typeof(HoverHandler)), Shared] + [Method(Methods.TextDocumentHoverName)] internal sealed class HoverHandler : AbstractStatelessRequestHandler { private readonly IGlobalOptionService _globalOptions; @@ -36,8 +36,6 @@ public HoverHandler(IGlobalOptionService globalOptions) _globalOptions = globalOptions; } - public override string Method => Methods.TextDocumentHoverName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs index 7ceddc909b1a7..f964579828523 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -16,8 +16,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(VSInternalMethods.OnAutoInsertName)] + [ExportXamlLspRequestHandlerProvider(typeof(OnAutoInsertHandler)), Shared] + [Method(VSInternalMethods.OnAutoInsertName)] internal class OnAutoInsertHandler : AbstractStatelessRequestHandler { [ImportingConstructor] @@ -26,8 +26,6 @@ public OnAutoInsertHandler() { } - public override string Method => VSInternalMethods.OnAutoInsertName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs index e336cb6827928..3dd53b1372e5a 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs @@ -17,8 +17,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { - [ExportLspRequestHandlerProvider(StringConstants.XamlLanguageName), Shared] - [ProvidesMethod(Methods.TextDocumentLinkedEditingRangeName)] + [ExportXamlLspRequestHandlerProvider(typeof(OnTypeRenameHandler)), Shared] + [Method(Methods.TextDocumentLinkedEditingRangeName)] internal class OnTypeRenameHandler : AbstractStatelessRequestHandler { // From https://www.w3.org/TR/xml/#NT-NameStartChar @@ -58,8 +58,6 @@ public OnTypeRenameHandler() { } - public override string Method => Methods.TextDocumentLinkedEditingRangeName; - public override bool MutatesSolutionState => false; public override bool RequiresLSPSolution => true; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestDispatcherFactory.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestDispatcherFactory.cs index 8b8ce0b20458e..2c64957cae0f1 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestDispatcherFactory.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestDispatcherFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -30,7 +31,7 @@ internal sealed class XamlRequestDispatcherFactory : AbstractRequestDispatcherFa [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public XamlRequestDispatcherFactory( - [ImportMany] IEnumerable> requestHandlerProviders, + [ImportMany(StringConstants.XamlLspLanguagesContract)] IEnumerable> requestHandlerProviders, XamlProjectService projectService, [Import(AllowDefault = true)] IXamlLanguageServerFeedbackService? feedbackService) : base(requestHandlerProviders) @@ -39,9 +40,9 @@ public XamlRequestDispatcherFactory( _feedbackService = feedbackService; } - public override RequestDispatcher CreateRequestDispatcher(ImmutableArray supportedLanguages, WellKnownLspServerKinds serverKind) + public override RequestDispatcher CreateRequestDispatcher(WellKnownLspServerKinds serverKind) { - return new XamlRequestDispatcher(_projectService, _requestHandlerProviders, _feedbackService, supportedLanguages, serverKind); + return new XamlRequestDispatcher(_projectService, _requestHandlerProviders, _feedbackService, serverKind); } private class XamlRequestDispatcher : RequestDispatcher @@ -53,17 +54,16 @@ public XamlRequestDispatcher( XamlProjectService projectService, ImmutableArray> requestHandlerProviders, IXamlLanguageServerFeedbackService? feedbackService, - ImmutableArray languageNames, - WellKnownLspServerKinds serverKind) : base(requestHandlerProviders, languageNames, serverKind) + WellKnownLspServerKinds serverKind) : base(requestHandlerProviders, serverKind) { _projectService = projectService; _feedbackService = feedbackService; } - protected override async Task ExecuteRequestAsync( - RequestExecutionQueue queue, RequestType request, ClientCapabilities clientCapabilities, string? clientName, string methodName, bool mutatesSolutionState, bool requiresLSPSolution, IRequestHandler handler, CancellationToken cancellationToken) - where RequestType : class - where ResponseType : default + protected override async Task ExecuteRequestAsync( + RequestExecutionQueue queue, bool mutatesSolutionState, bool requiresLSPSolution, IRequestHandler handler, TRequestType request, ClientCapabilities clientCapabilities, string? clientName, string methodName, CancellationToken cancellationToken) + where TRequestType : class + where TResponseType : default { var textDocument = handler.GetTextDocumentIdentifier(request); @@ -77,7 +77,7 @@ public XamlRequestDispatcher( { try { - return await base.ExecuteRequestAsync(queue, request, clientCapabilities, clientName, methodName, mutatesSolutionState, requiresLSPSolution, handler, cancellationToken).ConfigureAwait(false); + return await base.ExecuteRequestAsync(queue, mutatesSolutionState, requiresLSPSolution, handler, request, clientCapabilities, clientName, methodName, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (e is not OperationCanceledException) { @@ -90,4 +90,12 @@ public XamlRequestDispatcher( } } } + + [AttributeUsage(AttributeTargets.Class), MetadataAttribute] + internal class ExportXamlLspRequestHandlerProviderAttribute : ExportLspRequestHandlerProviderAttribute + { + public ExportXamlLspRequestHandlerProviderAttribute(Type first, params Type[] handlerTypes) : base(StringConstants.XamlLspLanguagesContract, first, handlerTypes) + { + } + } } diff --git a/src/VisualStudio/Xaml/Impl/StringConstants.cs b/src/VisualStudio/Xaml/Impl/StringConstants.cs index 5f9fc444b0431..23b7f97e91ed5 100644 --- a/src/VisualStudio/Xaml/Impl/StringConstants.cs +++ b/src/VisualStudio/Xaml/Impl/StringConstants.cs @@ -13,5 +13,7 @@ internal static class StringConstants public const string CreateEventHandlerCommand = "Xaml.CreateEventHandler"; public const string RetriggerCompletionCommand = "editor.action.triggerSuggest"; + + public const string XamlLspLanguagesContract = "XamlLspLanguages"; } }