diff --git a/.github/workflows/main-merge.yml b/.github/workflows/main-merge.yml index 15711d10c9e5a..f25597ce1c37c 100644 --- a/.github/workflows/main-merge.yml +++ b/.github/workflows/main-merge.yml @@ -2,9 +2,9 @@ name: Inter-branch merge on: - # schedule: - # # Create a merge every 3 hours (works only for merges from `main`, others would need a `push` trigger). - # - cron: '0 */3 * * *' + schedule: + # Create a merge every 3 hours (works only for merges from `main`, others would need a `push` trigger). + - cron: '0 */3 * * *' workflow_dispatch: inputs: configuration_file_branch: @@ -21,5 +21,6 @@ jobs: if: github.repository == 'dotnet/roslyn' uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main with: - configuration_file_path: 'eng/config/branch-merge.jsonc' + configuration_file_path: 'eng/config/main-to-main-vs-deps-branch-merge.jsonc' configuration_file_branch: ${{ inputs.configuration_file_branch || 'main' }} + diff --git a/Roslyn.sln b/Roslyn.sln index 6fca77405900e..980009f1a1d57 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31319.15 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.10623.112 main MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynDeployment", "src\Deployment\RoslynDeployment.csproj", "{600AF682-E097-407B-AD85-EE3CED37E680}" EndProject @@ -2207,8 +2207,11 @@ Global src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{58969243-7f59-4236-93d0-c93b81f569b3}*SharedItemsImports = 13 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Extensions\Microsoft.CodeAnalysis.Extensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index ce4aac1b06c33..75ead14245059 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -66,9 +66,6 @@ - - - @@ -99,14 +96,12 @@ - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 14028e430ed09..b6f602b74c189 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + diff --git a/eng/build.ps1 b/eng/build.ps1 index b8d9f22be29fa..ea4c6ffd7c578 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -440,7 +440,7 @@ function TestUsingRunTests() { } } elseif ($testVsi) { - $args += " --timeout 110" + $args += " --timeout 220" $args += " --runtime both" $args += " --sequential" $args += " --include '\.IntegrationTests'" diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 1d494eeb3f501..e2ad4fac15ab1 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -176,6 +176,15 @@ "insertionCreateDraftPR": false }, "main": { + "nugetKind": [ + "Shipping", + "NonShipping" + ], + "vsBranch": "main", + "insertionCreateDraftPR": true, + "insertionTitlePrefix": "[Validation]" + }, + "main-vs-deps": { "nugetKind": [ "Shipping", "NonShipping" diff --git a/eng/config/branch-merge.jsonc b/eng/config/branch-merge.jsonc deleted file mode 100644 index 2c2a486a106ea..0000000000000 --- a/eng/config/branch-merge.jsonc +++ /dev/null @@ -1,9 +0,0 @@ -// Used by .github/workflows/main-merge.yml -{ - "merge-flow-configurations": { - // "main": { - // "MergeToBranch": "release/dev17.15", - // "ExtraSwitches": "-QuietComments" - // } - } -} diff --git a/eng/config/globalconfigs/Common.globalconfig b/eng/config/globalconfigs/Common.globalconfig index f073601eac363..acb1c8e4880ff 100644 --- a/eng/config/globalconfigs/Common.globalconfig +++ b/eng/config/globalconfigs/Common.globalconfig @@ -127,9 +127,6 @@ dotnet_diagnostic.VSTHRD010.severity = none # VSTHRD110: Observe result of async calls dotnet_diagnostic.VSTHRD110.severity = none -# Workaround for old Microsoft.VisualStudio.Progression.* packages: https://github.com/dotnet/roslyn/issues/71404 -dotnet_diagnostic.VSIXCompatibility1001.severity = none - dotnet_diagnostic.HAA0101.severity = none dotnet_diagnostic.HAA0102.severity = none dotnet_diagnostic.HAA0201.severity = none diff --git a/eng/config/main-to-main-vs-deps-branch-merge.jsonc b/eng/config/main-to-main-vs-deps-branch-merge.jsonc new file mode 100644 index 0000000000000..cd979074b54a4 --- /dev/null +++ b/eng/config/main-to-main-vs-deps-branch-merge.jsonc @@ -0,0 +1,10 @@ +// Used by .github/workflows/main-merge.yml +{ + "merge-flow-configurations": { + // Merge any main changes to main-vs-deps. + "main": { + "MergeToBranch": "main-vs-deps", + "ExtraSwitches": "-QuietComments" + } + } +} diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props index d2f750758827c..d6ed357dadc0c 100644 --- a/eng/targets/Settings.props +++ b/eng/targets/Settings.props @@ -11,9 +11,6 @@ RoslynDev - - $(NoWarn);VSIXCompatibility1001 - $(NoWarn);NU1507 @@ -120,6 +117,7 @@ 002400000480000094000000060200000024000052534131000400000100010009f3c8fcb7cb2592cc6e6d3646d4d9a2bda9e7a243d357bc17a5a06f50ed0dae74a343cd9538fe45bbc90a3f7cbecb23558205e3b246b69f7f7730e520460d560da1c68cda9e2256e3b801629e2dc1fd588b1681aaf4f2c98abcfc50086ecbcd55f76f7dbaf018e708527d8ae3a2714b3ec9b62bd9aaf56cf55b3ffc9eee31aa 00240000048000009400000006020000002400005253413100040000010001005984d9e99e5722bb74ddbb59972bff1a2fd9e0ccb2d50e09ef85e39ec4a4e4bf2de896997de1af164be0558cdd5a50a283b9353fc4e5ccc1c87363e6e7d87af7bec8ca40281596fc8f5b5aad9904230f6f3892f8dde382cee7ba9854004d86ce93834a86b42ebdd0faf86d9fa6d935e05aed68cb4d828cea77df028739aaa9dc 002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293 + $(MicrosoftSharedPublicKey) + """, mSkeleton.GetDocumentationCommentXml()); var mImplementation = e.GetMember("M"); AssertEx.Equal(""" - + """, mImplementation.GetDocumentationCommentXml()); var p = extension.GetMember("P"); AssertEx.Equal(""" - + """, p.GetDocumentationCommentXml()); var pGetImplementation = e.GetMember("get_P"); AssertEx.Equal(""" - + """, pGetImplementation.GetDocumentationCommentXml()); @@ -40150,8 +41370,6 @@ static class E } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider warning (WRN_MissingParamTag) about missing documentation for extension parameter - // since one of the instance members has a tag var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); comp.VerifyEmitDiagnostics( // (8,26): warning CS1572: XML comment has a param tag for 'o', but there is no parameter by that name @@ -40178,8 +41396,6 @@ static class E } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider warning (WRN_MissingParamTag) about missing documentation for member parameter - // since the extension parameter is documented var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); comp.VerifyEmitDiagnostics(); } @@ -40306,8 +41522,6 @@ static class E } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider warning (WRN_MissingTypeParamTag) about missing documentation for extension type parameter - // since one of the members has a tag var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); comp.VerifyEmitDiagnostics( // (8,30): warning CS1711: XML comment has a typeparam tag for 'T', but there is no type parameter by that name @@ -40334,8 +41548,6 @@ static class E } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider warning (WRN_MissingTypeParamTag) about missing documentation for member type parameter - // since the extension type parameter is documented var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); comp.VerifyEmitDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 712b431e107a8..3026f4bc56e36 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -4,9 +4,12 @@ #nullable disable using System; +using System.Globalization; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -219,6 +222,78 @@ static class E comp.VerifyEmitDiagnostics(); } + [Fact] + public void PositionalPattern_03() + { + // implicit span conversion + var src = """ +int[] i = new int[] { 1, 2 }; +if (i is var (x, y)) + System.Console.Write((x, y)); + +static class E +{ + extension(System.Span s) + { + public void Deconstruct(out int i, out int j) { i = 42; j = 43; } + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("(42, 43)"), verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + public void PositionalPattern_04() + { + // implicit tuple conversion + var src = """ +var t = (42, "ran"); +if (t is var (x, y)) + System.Console.Write((x, y)); + +static class E +{ + extension((object, object) t) + { + public void Deconstruct(out int i, out int j) { i = 42; j = 43; } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("(42, ran)")).VerifyDiagnostics(); + } + + [Fact] + public void PositionalPattern_05() + { + // We check conversion during initial binding + var src = """ +int[] i = []; +_ = i is var (x, y); + +static class E +{ + extension(System.ReadOnlySpan r) + { + public void Deconstruct(out int i, out int j) => throw null; + } +} + +namespace System +{ + public ref struct ReadOnlySpan + { + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,14): error CS0656: Missing compiler required member 'ReadOnlySpan.op_Implicit' + // _ = i is var (x, y); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "(x, y)").WithArguments("System.ReadOnlySpan", "op_Implicit").WithLocation(2, 14)); + } + [Fact] public void InvocationOnNull() { @@ -314,7 +389,7 @@ public static class E try { var comp = CreateCompilation([src, OverloadResolutionPriorityAttributeDefinition]); - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : assertion in NullableWalker + // Tracked by https://github.com/dotnet/roslyn/issues/78828 : assertion in NullableWalker CompileAndVerify(comp, expectedOutput: "42").VerifyDiagnostics(); } catch (InvalidOperationException) @@ -2176,7 +2251,7 @@ class C public object? P3 => throw null!; } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : incorrect nullability analysis for property pattern with extension property (unexpected warning) + // Tracked by https://github.com/dotnet/roslyn/issues/78828 : incorrect nullability analysis for property pattern with extension property (unexpected warning) var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); comp.VerifyEmitDiagnostics( // (4,5): warning CS8602: Dereference of a possibly null reference. @@ -2285,5 +2360,3190 @@ public static class E // [System.Diagnostics.CodeAnalysis.UnscopedRef] Diagnostic(ErrorCode.ERR_UnscopedRefAttributeUnsupportedMemberTarget, "System.Diagnostics.CodeAnalysis.UnscopedRef").WithLocation(8, 10)); } + + [Fact] + public void Ambiguity_01() + { + var src = """ +var x = object.M; // 1 +x(); + +System.Action y = object.M; // 2 + +static class E1 +{ + extension(object) + { + public static void M() { } + } +} + +static class E2 +{ + extension(object) + { + public static int M => 0; + } +} +"""; + + var comp = CreateCompilation(src); + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : the diagnostic should describe what went wrong + comp.VerifyEmitDiagnostics( + // (1,9): error CS9286: 'object' does not contain a definition for 'M' and no accessible extension member 'M' for receiver of type 'object' could be found (are you missing a using directive or an assembly reference?) + // var x = object.M; // 1 + Diagnostic(ErrorCode.ERR_ExtensionResolutionFailed, "object.M").WithArguments("object", "M").WithLocation(1, 9), + // (4,19): error CS9286: 'object' does not contain a definition for 'M' and no accessible extension member 'M' for receiver of type 'object' could be found (are you missing a using directive or an assembly reference?) + // System.Action y = object.M; // 2 + Diagnostic(ErrorCode.ERR_ExtensionResolutionFailed, "object.M").WithArguments("object", "M").WithLocation(4, 19)); + + src = """ +var x = I.M; // binds to I1.M (method) +x(); + +System.Action y = I.M; // binds to I1.M (method) +y(); + +interface I1 { static void M() { System.Console.Write("I1.M() "); } } +interface I2 { static int M => 0; } +interface I3 { static int M = 0; } +interface I : I1, I2, I3 { } +"""; + + comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("I1.M() I1.M()"), verify: Verification.Skipped).VerifyDiagnostics(); + + src = """ +I i = new C(); +var x = i.M; // binds to I1.M (method) +x(); + +System.Action y = i.M; // binds to I1.M (method) +y(); + +interface I1 { void M() { System.Console.Write("I1.M() "); } } +interface I2 { int M => 0; } +interface I : I1, I2 { } + +class C : I { } +"""; + + comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("I1.M() I1.M()"), verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + public void CS1943ERR_QueryTypeInferenceFailedSelectMany() + { + // ReportQueryInferenceFailedSelectMany + var comp = CreateCompilation(""" +using System; +using System.Collections.Generic; + +class Test +{ + class TestClass + { } + + static void Main() + { + int[] nums = { 0, 1, 2, 3, 4, 5 }; + TestClass tc = new TestClass(); + + var x = from n in nums + from s in tc // CS1943 + select n + s; + } +} + +static class E +{ + extension(IEnumerable source) + { + public IEnumerable SelectMany( + Func> collectionSelector, + Func resultSelector) + => throw null; + } +} +"""); + + comp.VerifyEmitDiagnostics( + // (13,27): error CS1943: An expression of type 'Test.TestClass' is not allowed in a subsequent from clause in a query expression with source type 'int[]'. Type inference failed in the call to 'SelectMany'. + // tc + Diagnostic(ErrorCode.ERR_QueryTypeInferenceFailedSelectMany, "tc").WithArguments("Test.TestClass", "int[]", "SelectMany")); + } + + [Fact] + public void Foreach_Extension_01() + { + var src = """ +class Program +{ + public static void M(Buffer4 x) + { + foreach(var s in x) + { + } + } +} + +namespace System +{ + public readonly ref struct Span + { + } +} + +static class Ext +{ + extension(System.Span f) + { + public Enumerator GetEnumerator() => default; + } + + public ref struct Enumerator + { + public ref T Current => throw null; + + public bool MoveNext() => false; + } +} + +[System.Runtime.CompilerServices.InlineArray(4)] +public struct Buffer4 +{ + private T _element0; +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll); + comp.VerifyEmitDiagnostics( + // (5,26): error CS9189: foreach statement on an inline array of type 'Buffer4' is not supported + // foreach(var s in x) + Diagnostic(ErrorCode.ERR_InlineArrayForEachNotSupported, "x").WithArguments("Buffer4").WithLocation(5, 26), + // (20,25): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // extension(System.Span f) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Span").WithArguments("", "System.Span", "System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Span").WithLocation(20, 25) + ); + } + + [Fact] + public void DelegateCreation_01() + { + var src = """ +string s; +_ = new System.Action(s.M); + +string s2; +_ = new System.Action(s2.M2); + +_ = new System.Action(string.M2); + +static class E +{ + extension(string s) + { + public void M() { } + public static void M2() { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,23): error CS0165: Use of unassigned local variable 's' + // _ = new System.Action(s.M); + Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(2, 23), + // (4,8): warning CS0168: The variable 's2' is declared but never used + // string s2; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "s2").WithArguments("s2").WithLocation(4, 8), + // (5,23): error CS0176: Member 'E.extension(string).M2()' cannot be accessed with an instance reference; qualify it with a type name instead + // _ = new System.Action(s2.M2); + Diagnostic(ErrorCode.ERR_ObjectProhibited, "s2.M2").WithArguments("E.extension(string).M2()").WithLocation(5, 23)); + } + + [Fact] + public void AsyncMethodBuilder_01() + { + var src = """ +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +Console.Write(await object.M()); + +static class C +{ + extension(object) + { + [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))] + public static async MyTask M() { await Task.Yield(); Console.Write("M "); return 3; } + } +} + +public class MyTask +{ + private Action _continuation; + private bool _isCompleted; + internal T _result; + + public Awaiter GetAwaiter() => new Awaiter(this); + public T Result => _result; + + internal void Complete(T result) + { + _result = result; + _isCompleted = true; + _continuation?.Invoke(); + } + + public readonly struct Awaiter : ICriticalNotifyCompletion + { + private readonly MyTask _task; + internal Awaiter(MyTask task) => _task = task; + + public bool IsCompleted => _task._isCompleted; + public T GetResult() => _task._result; + + public void OnCompleted(Action cont) => HandleCompletion(cont); + public void UnsafeOnCompleted(Action cont) => HandleCompletion(cont); + + private void HandleCompletion(Action cont) + { + if (_task._isCompleted) { cont(); return; } + + _task._continuation = cont; + } + } +} + +public struct MyTaskMethodBuilder +{ + private readonly MyTask _task; + private MyTaskMethodBuilder(MyTask task) => _task = task; + + public static MyTaskMethodBuilder Create() => new MyTaskMethodBuilder(new MyTask()); + public MyTask Task => _task; + public void Start(ref TSM sm) where TSM : IAsyncStateMachine => sm.MoveNext(); + + public void SetStateMachine(IAsyncStateMachine _) { } + public void SetResult(T result) => _task.Complete(result); + public void SetException(Exception e) => throw null; + + public void AwaitOnCompleted(ref TA a, ref TSM sm) where TA : INotifyCompletion where TSM: IAsyncStateMachine => a.OnCompleted(sm.MoveNext); + public void AwaitUnsafeOnCompleted(ref TA a, ref TSM sm) where TA : ICriticalNotifyCompletion where TSM: IAsyncStateMachine => a.UnsafeOnCompleted(sm.MoveNext); +} + +namespace System.Runtime.CompilerServices { class AsyncMethodBuilderAttribute : System.Attribute { public AsyncMethodBuilderAttribute(System.Type t) { } } } +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "M 3").VerifyDiagnostics(); + } + + [Fact] + public void PEMethodSymbol_GetUseSiteInfo() + { + // missing implementation method for M + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void Retargeting_01() + { + var libSrc = """ +public static class E +{ + extension(object) + { + public static void M() { } + } +} + +namespace System.Runtime.CompilerServices +{ + public class ExtensionAttribute : System.Attribute {} +} +"""; + var libComp = CreateCompilation(libSrc, targetFramework: TargetFramework.Mscorlib40); + + var src = """ +object.M(); +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Mscorlib46, references: [libComp.ToMetadataReference()]); + comp.VerifyEmitDiagnostics(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + Assert.IsType(extension); + AssertExtensionDeclaration(extension.GetPublicSymbol()); + } + + [Theory] + [InlineData("public")] + [InlineData("assembly")] + [InlineData("family")] + public void PENamedTypeSymbol_01(string accessibility) + { + // Accessibility of extension marker is not private + var ilSrc = $$""" +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method {{accessibility}} hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var invocation = GetSyntax(tree, "int.M()"); + Assert.Null(model.GetSymbolInfo(invocation).Symbol); + Assert.Equal([], model.GetSymbolInfo(invocation).CandidateSymbols.ToTestDisplayStrings()); + Assert.Equal([], model.GetMemberGroup(invocation).ToTestDisplayStrings()); + } + + [Fact] + public void PENamedTypeSymbol_02() + { + // Accessibility of extension marker is not private, instance extension method + var ilSrc = $$""" +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method public hidebysig specialname static void '$' ( int32 i ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig instance void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M ( int32 i ) cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +42.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,4): error CS1061: 'int' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) + // 42.M(); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M").WithArguments("int", "M").WithLocation(1, 4)); + } + + [Fact] + public void PENamedTypeSymbol_03() + { + // Extension marker method is generic + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_04() + { + // Extension marker method is not static + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_05() + { + // Extension marker doesn't return void + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static int32 '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ldc.i4.0 + IL_0001: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_06() + { + // Extension marker lacks its parameter + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_07() + { + // Extension marker has an extra parameter + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '', string s ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_08() + { + // No containing type + var ilSrc = """ +.class public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object +{ + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + var extension = comp.GlobalNamespace.GetTypeMember("<>E__0"); + Assert.True(extension.IsExtension); + } + + [Fact] + public void PENamedTypeSymbol_09() + { + // Two extension markers + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method private hidebysig specialname static void '$' ( string '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_10() + { + // Arity mismatch between skeleton and implementation + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_11() + { + // Accessibility mismatch between skeleton and implementation + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method assembly hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_12() + { + // parameter count mismatch between skeleton and implementation + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M (string s) cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_13() + { + // return type mismatch between skeleton and implementation + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static int32 M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_14() + { + // parameter type mismatch, instance method + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 i ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig instance void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M ( object i ) cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +42.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,4): error CS0570: 'E.extension(int).M()' is not supported by the language + // 42.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 4)); + } + + [Fact] + public void PENamedTypeSymbol_15() + { + // parameter type mismatch, instance method + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 i ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig instance void M ( string s ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M ( int32 i, object s ) cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +42.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,4): error CS0570: 'E.extension(int).M(string)' is not supported by the language + // 42.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M(string)").WithLocation(1, 4)); + } + + [Fact] + public void PENamedTypeSymbol_16() + { + // constraint mismatch between skeleton and implementation + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_17() + { + // implementation is not static + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig instance void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M()' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void PENamedTypeSymbol_18() + { + // skeleton type is not sealed + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + Assert.False(comp.GetTypeByMetadataName("E").GetTypeMembers().Single().IsExtension); + } + + [Theory] + [InlineData("assembly")] + [InlineData("family")] + public void PENamedTypeSymbol_19(string accessibility) + { + // skeleton type is not public + var ilSrc = $$""" +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested {{accessibility}} auto ansi specialname beforefieldinit sealed '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + Assert.False(comp.GetTypeByMetadataName("E").GetTypeMembers().Single().IsExtension); + } + + [Fact] + public void PENamedTypeSymbol_20() + { + // skeleton type not sealed + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + Assert.False(comp.GetTypeByMetadataName("E").GetTypeMembers().Single().IsExtension); + } + + [Fact] + public void PENamedTypeSymbol_21() + { + // skeleton type has a base that's not object + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi specialname beforefieldinit sealed '<>E__0' + extends [mscorlib]System.String + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + Assert.False(comp.GetTypeByMetadataName("E").GetTypeMembers().Single().IsExtension); + } + + [Fact] + public void PENamedTypeSymbol_22() + { + // skeleton type implements an interface + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi specialname beforefieldinit sealed '<>E__0' + extends [mscorlib]System.Object + implements I + { + .method private hidebysig specialname static void '$' ( int32 '' ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M () cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M () cil managed + { + IL_0000: nop + IL_0001: ret + } +} + +.class interface private auto ansi abstract beforefieldinit I +{ +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0117: 'int' does not contain a definition for 'M' + // int.M(); + Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("int", "M").WithLocation(1, 5)); + + Assert.False(comp.GetTypeByMetadataName("E").GetTypeMembers().Single().IsExtension); + } + + [Fact] + public void PENamedTypeSymbol_23() + { + // parameter type mismatch, static method + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0' + extends [mscorlib]System.Object + { + .method private hidebysig specialname static void '$' ( int32 i ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + IL_0000: ret + } + + .method public hidebysig static void M ( string s ) cil managed + { + IL_0000: ldnull + IL_0001: throw + } + } + + .method public hidebysig static void M ( object s ) cil managed + { + IL_0000: nop + IL_0001: ret + } +} +"""; + var src = """ +int.M(); +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,5): error CS0570: 'E.extension(int).M(string)' is not supported by the language + // int.M(); + Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M(string)").WithLocation(1, 5)); + } + + [Fact] + public void RefReadonlyExtensionParameterWithOneExplicitRefArgument() + { + var src = """ +int i = 0; +42.Copy(ref i); +System.Console.Write(i); + +static class E +{ + extension(ref readonly int i) + { + public void Copy(ref int j) { j = i; } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "42"); + } + + [Fact] + public void Cref_01() + { + var src = """ +/// +/// +/// +/// +static class E +{ + extension(int i) + { + /// + /// + /// + public void M(string s) => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (10,24): warning CS1574: XML comment has cref attribute 'M(string)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "M(string)").WithArguments("M(string)").WithLocation(10, 24)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + + + +""", e.GetDocumentationCommentXml()); + + AssertEx.Equal("T:E.<>E__0", e.GetTypeMembers().Single().GetDocumentationCommentId()); + + var mSkeleton = comp.GetMember("E").GetTypeMembers().Single().GetMember("M"); + AssertEx.Equal(""" + + + + + + +""", mSkeleton.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(int).M(string), void E.<>E__0.M(System.String s))", + "(E.M(int, string), void E.M(this System.Int32 i, System.String s))", + "(E.extension(int).M, void E.<>E__0.M(System.String s))", + "(E.M, void E.M(this System.Int32 i, System.String s))", + "(M(int, string), void E.M(this System.Int32 i, System.String s))", + "(M(string), null)", + "(M, void E.M(this System.Int32 i, System.String s))"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_02() + { + var src = """ +/// +static class E +{ + extension(T t) + { + public void M(U u) => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + AssertEx.Equal("T:E.<>E__0`1", e.GetTypeMembers().Single().GetDocumentationCommentId()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension{T}(T).M{U}(U), void E.<>E__0.M(U u))"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_03() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(ref int).M()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(ref int).M()").WithArguments("extension(ref int).M()").WithLocation(1, 16)); + } + + [Fact] + public void Cref_04() + { + var src = """ +/// +/// +static class E +{ + extension(ref int i) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (2,16): warning CS1574: XML comment has cref attribute 'extension(int).M()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).M()").WithArguments("extension(int).M()").WithLocation(2, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(ref int).M(), void E.<>E__0.M())", + "(E.extension(int).M(), null)"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_05() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + } + extension(string s) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M(), void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_06() + { + var src = """ +/// +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + } + extension(int) + { + public static void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M()'. Assuming 'E.extension(int).M()', but could have also matched other overloads including 'E.extension(int).M()'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M()").WithArguments("E.extension(int).M()", "E.extension(int).M()", "E.extension(int).M()").WithLocation(1, 16), + // (2,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M'. Assuming 'E.extension(int).M()', but could have also matched other overloads including 'E.extension(int).M()'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M").WithArguments("E.extension(int).M", "E.extension(int).M()", "E.extension(int).M()").WithLocation(2, 16), + // (11,28): error CS0111: Type 'E' already defines a member called 'M' with the same parameter types + // public static void M() => throw null!; + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "M").WithArguments("M", "E").WithLocation(11, 28)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(int).M(), null)", + "(E.extension(int).M, null)"], + PrintXmlCrefSymbols(tree, model)); + + var docComments = tree.GetCompilationUnitRoot().DescendantTrivia().Select(trivia => trivia.GetStructure()).OfType(); + var crefs = docComments.SelectMany(doc => doc.DescendantNodes().OfType()).ToArray(); + Assert.Equal(CandidateReason.OverloadResolutionFailure, model.GetSymbolInfo(crefs[0].Cref).CandidateReason); + Assert.Equal(CandidateReason.Ambiguous, model.GetSymbolInfo(crefs[1].Cref).CandidateReason); + } + + [Fact] + public void Cref_08() + { + var src = """ +/// +static class E +{ + extension(int i, int j) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (4,22): error CS9285: An extension container can have only one receiver parameter + // extension(int i, int j) + Diagnostic(ErrorCode.ERR_ReceiverParameterOnlyOne, "int j").WithLocation(4, 22)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M(), void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_09() + { + var src = """ +/// +static class E +{ + extension(int i, int j) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int, int).M()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int, int).M()").WithArguments("extension(int, int).M()").WithLocation(1, 16), + // (4,22): error CS9285: An extension container can have only one receiver parameter + // extension(int i, int j) + Diagnostic(ErrorCode.ERR_ReceiverParameterOnlyOne, "int j").WithLocation(4, 22)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int, int).M(), null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_10() + { + // Missing closing parens + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension(.M()' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension(.M()").WithArguments("E.extension(.M()").WithLocation(1, 16), + // (1,28): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, ".").WithArguments(") expected", "1026").WithLocation(1, 28)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(.M(), null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_11() + { + // Missing extension parameter + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension().M()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension().M()").WithArguments("extension().M()").WithLocation(1, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension().M(), null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_12() + { + // Two extension parameters + var src = """ +/// +static class E +{ + extension(int i, int j) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int, int).M()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int, int).M()").WithArguments("extension(int, int).M()").WithLocation(1, 16), + // (4,22): error CS9285: An extension container can have only one receiver parameter + // extension(int i, int j) + Diagnostic(ErrorCode.ERR_ReceiverParameterOnlyOne, "int j").WithLocation(4, 22)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int, int).M(), null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_13() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public int P => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).P, System.Int32 E.<>E__0.P { get; })"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_14() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public int P => throw null!; + } + extension(string s) + { + public string P => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).P, System.Int32 E.<>E__0.P { get; })"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_15() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public int P => throw null!; + } + extension(string s) + { + public string P => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(string).P, System.String E.<>E__1.P { get; })"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_16() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_17() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(string).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(string).M").WithArguments("extension(string).M").WithLocation(1, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(string).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_18() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_19() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_20() + { + var src = """ +/// +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(int).M{U}, void E.<>E__0.M())", + "(E.M{U}, void E.M(this System.Int32 i))"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_21() + { + // Arity for extension in cref differs from that in declaration + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).M").WithArguments("extension(int).M").WithLocation(1, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_22() + { + // Arity for extension in cref differs from that in declaration + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension{T}(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension{T}(int).M").WithArguments("extension{T}(int).M").WithLocation(1, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension{T}(int).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_23() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension{T}(int).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_24() + { + var src = """ +/// +static class E +{ + public static void M() => throw null; +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.M").WithArguments("M").WithLocation(1, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_25() + { + // Type argument name differs from type parameter name + var src = """ +/// +static class E +{ + extension(T t) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension{U}(U).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_26() + { + // __arglist + var src = """ +/// +static class E +{ + extension(__arglist) + { + public int P => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(string).P' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(string).P").WithArguments("extension(string).P").WithLocation(1, 16), + // (4,15): error CS1669: __arglist is not valid in this context + // extension(__arglist) + Diagnostic(ErrorCode.ERR_IllegalVarArgs, "__arglist").WithLocation(4, 15)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(string).P, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_27() + { + // member named "extension" + var src = """ +/// +static class E +{ + public static void extension(string s) => throw null!; +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(string), void E.extension(System.String s))"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_28() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public int P => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension(int).' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension(int).").WithArguments("E.extension(int).").WithLocation(1, 16), + // (1,33): warning CS1658: Identifier expected. See also error CS1001. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, @"""").WithArguments("Identifier expected", "1001").WithLocation(1, 33)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int)., null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_29() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public class Nested { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int).Nested' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).Nested").WithArguments("extension(int).Nested").WithLocation(1, 16), + // (6,22): error CS9282: This member is not allowed in an extension block + // public class Nested { } + Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Nested").WithLocation(6, 22)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).Nested, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_30() + { + var src = """ +/// +static class E +{ + extension(int i) + { + void I.M() { } + } +} + +interface I +{ + void M(); +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).M").WithArguments("extension(int).M").WithLocation(1, 16), + // (6,16): error CS0541: 'E.extension(int).M()': explicit interface declaration can only be declared in a class, record, struct or interface + // void I.M() { } + Diagnostic(ErrorCode.ERR_ExplicitInterfaceImplementationInNonClassOrStruct, "M").WithArguments("E.extension(int).M()").WithLocation(6, 16), + // (6,16): error CS9282: This member is not allowed in an extension block + // void I.M() { } + Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "M").WithLocation(6, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_31() + { + var src = """ +/// +static class E +{ + extension(object) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(missing).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(missing).M").WithArguments("extension(missing).M").WithLocation(1, 16), + // (1,28): warning CS1580: Invalid type for parameter missing in XML comment cref attribute: 'E.extension(missing).M' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefParamType, "missing").WithArguments("missing", "E.extension(missing).M").WithLocation(1, 28)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(missing).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_32() + { + // nested type named "extension" + var src = """ +/// +/// +/// +static class E +{ + class @extension + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension, E.extension)", + "(E.extension.M, void E.extension.M())", + "(E.extension.M(), void E.extension.M())"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_33() + { + // nested type named "extension", error cases + var src = """ +/// +/// +/// +static class E +{ + class @extension + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (2,16): warning CS1574: XML comment has cref attribute 'extension().M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension().M").WithArguments("extension().M").WithLocation(2, 16), + // (3,16): warning CS1574: XML comment has cref attribute 'extension(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).M").WithArguments("extension(int).M").WithLocation(3, 16)); + } + + [Fact] + public void Cref_34() + { + // generic nested type named "extension" + var src = """ +/// +/// +static class E +{ + class @extension + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension{T}, E.extension)", + "(E.extension{T}.M, void E.extension.M())"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_35() + { + // generic nested type named "extension", error cases + var src = """ +/// +static class E +{ + class @extension + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension{T}(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension{T}(int).M").WithArguments("extension{T}(int).M").WithLocation(1, 16)); + } + + [Fact] + public void Cref_36() + { + // can refer to method named "extension", but cannot refer to extension block + var src = """ +/// +/// +static class E +{ + public static void extension() { } + public static void extension(int i) { } +} + +/// +/// +static class E2 +{ + extension(int) + { + } + + /// + /// + static void M() { } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (9,16): warning CS1574: XML comment has cref attribute 'extension()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E2.extension()").WithArguments("extension()").WithLocation(9, 16), + // (10,16): warning CS1574: XML comment has cref attribute 'extension(int)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E2.extension(int)").WithArguments("extension(int)").WithLocation(10, 16), + // (17,20): warning CS1574: XML comment has cref attribute 'extension()' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension()").WithArguments("extension()").WithLocation(17, 20), + // (18,20): warning CS1574: XML comment has cref attribute 'extension(int)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int)").WithArguments("extension(int)").WithLocation(18, 20)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(), void E.extension())", + "(E.extension(int), void E.extension(System.Int32 i))", + "(E2.extension(), null)", + "(E2.extension(int), null)", + "(extension(), null)", + "(extension(int), null)"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_37() + { + // method named "extension" + var src = """ +/// +/// +static class E +{ + public static void extension() { } + public static void extension(int i) { } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(E.extension(), void E.extension())", + "(E.extension(int), void E.extension(System.Int32 i))"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_38() + { + // method named "extension", error case + var src = """ +/// +/// +static class E +{ + public static void extension() { } + public static void extension(int i) { } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension().M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension().M").WithArguments("extension().M").WithLocation(1, 16), + // (2,16): warning CS1574: XML comment has cref attribute 'extension(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(int).M").WithArguments("extension(int).M").WithLocation(2, 16)); + } + + [Fact] + public void Cref_39() + { + // nested type named "extension" + var src = """ +/// +static class E +{ + class @extension + { + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension, E.extension)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_40() + { + // inaccessible due to file accessibility on type + var src1 = """ +/// +class C { } +"""; + var src2 = """ +file static class E +{ + extension(object) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation([(src1, "file1"), (src2, "file2")], parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // file1(1,16): warning CS1574: XML comment has cref attribute 'extension(object).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(object).M").WithArguments("extension(object).M").WithLocation(1, 16)); + + var c = comp.GetMember("C"); + AssertEx.Equal(""" + + + + +""", c.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(object).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_41() + { + // inaccessible due to internal + var src = """ +/// +class C { } +"""; + var libSrc = """ +public static class E +{ + extension(object) + { + internal static void M() { } + } +} +"""; + var libComp = CreateCompilation(libSrc); + var comp = CreateCompilation(src, references: [libComp.EmitToImageReference()], + parseOptions: TestOptions.RegularPreviewWithDocumentationComments, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyEmitDiagnostics(); + + var c = comp.GetMember("C"); + AssertEx.Equal(""" + + + + +""", c.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(object).M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_42() + { + // inaccessible due to private + var src = """ +/// +class C { } +"""; + var libSrc = """ +public static class E +{ + extension(object) + { + private static void M() { } + } +} +"""; + var libComp = CreateCompilation(libSrc); + var comp = CreateCompilation(src, references: [libComp.EmitToImageReference()], parseOptions: TestOptions.RegularPreviewWithDocumentationComments, options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(object).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(object).M").WithArguments("extension(object).M").WithLocation(1, 16)); + + var c = comp.GetMember("C"); + AssertEx.Equal(""" + + + + +""", c.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(object).M, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_43() + { + var src = """ +/// +static class E +{ + extension(T t) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension{int}(int).M' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension{int}(int).M").WithArguments("E.extension{int}(int).M").WithLocation(1, 16), + // (1,28): warning CS1658: Type parameter declaration must be an identifier not a type. See also error CS0081. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "int").WithArguments("Type parameter declaration must be an identifier not a type", "0081").WithLocation(1, 28)); + } + + [Fact] + public void Cref_44() + { + var src = """ +/// +extension(int) +{ + public static void M() { } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).M").WithArguments("extension(int).M").WithLocation(1, 16), + // (2,1): error CS9283: Extensions must be declared in a top-level, non-generic, static class + // extension(int) + Diagnostic(ErrorCode.ERR_BadExtensionContainingType, "extension").WithLocation(2, 1), + // (4,24): warning CS1591: Missing XML comment for publicly visible type or member 'extension(int).M()' + // public static void M() { } + Diagnostic(ErrorCode.WRN_MissingXMLComment, "M").WithArguments("extension(int).M()").WithLocation(4, 24)); + } + + [Fact] + public void Cref_45() + { + var src = """ +/// +static class E +{ + extension(int) + { + extension(string) + { + public static void M() { } + } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension(int).extension(string).M' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension(int).extension(string).M").WithArguments("E.extension(int).extension(string).M").WithLocation(1, 16), + // (1,33): warning CS1658: An extension member syntax is disallowed in nested position within an extension member syntax. See also error CS9309. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "extension(string).M").WithArguments("An extension member syntax is disallowed in nested position within an extension member syntax", "9309").WithLocation(1, 33), + // (6,9): error CS9282: This member is not allowed in an extension block + // extension(string) + Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "extension").WithLocation(6, 9)); + } + + [Fact] + public void Cref_46() + { + var src = """ +/// +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + public void M(int j) => throw null!; + public void M2(int j) => throw null!; + public void M2() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M'. Assuming 'E.extension(int).M()', but could have also matched other overloads including 'E.extension(int).M(int)'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M").WithArguments("E.extension(int).M", "E.extension(int).M()", "E.extension(int).M(int)").WithLocation(1, 16), + // (2,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M2'. Assuming 'E.extension(int).M2(int)', but could have also matched other overloads including 'E.extension(int).M2()'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M2").WithArguments("E.extension(int).M2", "E.extension(int).M2(int)", "E.extension(int).M2()").WithLocation(2, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, null)", "(E.extension(int).M2, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_47() + { + // Xml doc APIs on PE symbols + var src = """ +static class E +{ + extension(int i) + { + public void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var comp2 = CreateCompilation("", references: [comp.EmitToImageReference(documentation: new TestDocumentationProvider())]); + + var mSkeleton = comp2.GetMember("E").GetTypeMembers().Single().GetMember("M"); + Assert.Equal("M:E.<>E__0.M", mSkeleton.GetDocumentationCommentId()); + Assert.Equal("M:E.<>E__0.M", mSkeleton.GetDocumentationCommentXml()); + } + + private class TestDocumentationProvider : DocumentationProvider + { + protected internal override string GetDocumentationForSymbol(string documentationMemberID, CultureInfo preferredCulture, CancellationToken cancellationToken = default) + { + return documentationMemberID; + } + + public override bool Equals(object obj) => (object)this == obj; + + public override int GetHashCode() => throw new NotImplementedException(); + } + + [Fact] + public void Cref_48() + { + var libSrc = """ +public static class E +{ + extension(int i) + { + public void M() => throw null!; + } +} +"""; + var libComp = CreateCompilation(libSrc); + + var src = """ +/// +class C +{ +} +"""; + var comp = CreateCompilation(src, references: [libComp.EmitToImageReference()], parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var c = comp.GetMember("C"); + AssertEx.Equal(""" + + + + +""", c.GetDocumentationCommentXml()); + } + + [Fact] + public void Cref_49() + { + var src = """ +/// +static class E +{ +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(missing).M' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.extension(missing).M").WithArguments("extension(missing).M").WithLocation(1, 16), + // (1,28): warning CS1580: Invalid type for parameter missing in XML comment cref attribute: 'E.extension(missing).M' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefParamType, "missing").WithArguments("missing", "E.extension(missing).M").WithLocation(1, 28)); + } + + [Fact] + public void Cref_50() + { + var src = """ +/// +/// +static class E +{ + extension(int i) + { + public void M() => throw null!; + public void M2(int j) => throw null!; + } + extension(int i) + { + public void M(int j) => throw null!; + public void M2() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M'. Assuming 'E.extension(int).M()', but could have also matched other overloads including 'E.extension(int).M(int)'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M").WithArguments("E.extension(int).M", "E.extension(int).M()", "E.extension(int).M(int)").WithLocation(1, 16), + // (2,16): warning CS0419: Ambiguous reference in cref attribute: 'E.extension(int).M2'. Assuming 'E.extension(int).M2(int)', but could have also matched other overloads including 'E.extension(int).M2()'. + // /// + Diagnostic(ErrorCode.WRN_AmbiguousXMLReference, "E.extension(int).M2").WithArguments("E.extension(int).M2", "E.extension(int).M2(int)", "E.extension(int).M2()").WithLocation(2, 16)); + + var e = comp.GetMember("E"); + AssertEx.Equal(""" + + + + + +""", e.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).M, null)", "(E.extension(int).M2, null)"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_51() + { + var src = """ +/// +static class E +{ + extension(int) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal(["(E.extension(int).@M, void E.<>E__0.M())"], PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_52() + { + // unqualified reference + var src = """ +/// +/// +static class E +{ + extension(int) + { + /// + /// + public static void M1() { } + + public static void Method() { } + public static int Property => 42; + } + + /// + /// + extension(object) + { + } + + /// + /// + public static void M2() { } +} +"""; + // Tracked by https://github.com/dotnet/roslyn/issues/76130 : cref, such unqualified references in CREF should work within context of enclosing static type + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'extension(int).Method' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Method").WithArguments("extension(int).Method").WithLocation(1, 16), + // (2,16): warning CS1574: XML comment has cref attribute 'extension(int).Property' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Property").WithArguments("extension(int).Property").WithLocation(2, 16), + // (7,24): warning CS1574: XML comment has cref attribute 'extension(int).Method' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Method").WithArguments("extension(int).Method").WithLocation(7, 24), + // (8,24): warning CS1574: XML comment has cref attribute 'extension(int).Property' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Property").WithArguments("extension(int).Property").WithLocation(8, 24), + // (15,20): warning CS1574: XML comment has cref attribute 'extension(int).Method' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Method").WithArguments("extension(int).Method").WithLocation(15, 20), + // (16,20): warning CS1574: XML comment has cref attribute 'extension(int).Property' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Property").WithArguments("extension(int).Property").WithLocation(16, 20), + // (21,20): warning CS1574: XML comment has cref attribute 'extension(int).M2' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).M2").WithArguments("extension(int).M2").WithLocation(21, 20), + // (22,20): warning CS1574: XML comment has cref attribute 'extension(int).Property' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).Property").WithArguments("extension(int).Property").WithLocation(22, 20)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(extension(int).Method, null)", + "(extension(int).Property, null)", + "(extension(int).Method, null)", + "(extension(int).Property, null)", + "(extension(int).Method, null)", + "(extension(int).Property, null)", + "(extension(int).M2, null)", + "(extension(int).Property, null)"], + PrintXmlCrefSymbols(tree, model)); + + src = """ +/// +static class E +{ + /// + static class Nested + { + /// + public static void M() { } + + public static void Method() { } + } +} +"""; + comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void Cref_53() + { + var src = """ +static class E +{ + extension(int i) + { + /// + /// + public void M(string s) => throw null!; + } + extension(int i) + { + public void M2(string s) => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (5,24): warning CS1574: XML comment has cref attribute 'M2(string)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "M2(string)").WithArguments("M2(string)").WithLocation(5, 24)); + + var mSkeleton = comp.GetMember("E").GetTypeMembers().First().GetMember("M"); + AssertEx.Equal(""" + + + + + +""", mSkeleton.GetDocumentationCommentXml()); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.Equal([ + "(M2(string), null)", + "(M2, void E.M2(this System.Int32 i, System.String s))"], + PrintXmlCrefSymbols(tree, model)); + } + + [Fact] + public void Cref_54() + { + var libSrc = """ +public static class E +{ + extension(int) + { + public static void M() { } + } +} +"""; + var libComp = CreateCompilation(libSrc); + var libRef = libComp.EmitToImageReference(); + + var src = """ +/// +class C +{ +} +"""; + var comp = CreateCompilation(src, references: [libRef], parseOptions: TestOptions.Regular13.WithDocumentationMode(DocumentationMode.Diagnose)); + comp.VerifyEmitDiagnostics( + // (1,18): error CS8652: The feature 'extensions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // /// + Diagnostic(ErrorCode.ERR_FeatureInPreview, "extension(int).M").WithArguments("extensions").WithLocation(1, 18)); + + comp = CreateCompilation(src, references: [libRef], parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)); + comp.VerifyEmitDiagnostics(); + + comp = CreateCompilation(src, references: [libRef], parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void Cref_55() + { + var src = """ +/// +static class E +{ + extension(int i) + { + public void M(string s) => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics( + // (1,16): warning CS1574: XML comment has cref attribute 'M(string)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "E.M(string)").WithArguments("M(string)").WithLocation(1, 16)); + } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index 69f12e7e3d7e2..0692049f45402 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Test.Utilities; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { @@ -35,12 +36,16 @@ IEnumerable I() var comp = CreateCompilation(text); var i = comp.GetMember("Test.I"); + IMethodSymbol publicI = i.GetPublicSymbol(); + Assert.True(i.IsIterator); + Assert.True(publicI.IsIterator); Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); comp.VerifyDiagnostics(); Assert.True(i.IsIterator); + Assert.True(publicI.IsIterator); Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); } @@ -61,6 +66,102 @@ IEnumerable I() comp.VerifyDiagnostics(); } + [Fact] + public void BasicIterators_Async() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + class Test + { + async IAsyncEnumerable I() + { + await Task.Yield(); + yield return 1; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var i = comp.GetMember("Test.I"); + Assert.True(i.IsIterator); + Assert.True(i.GetPublicSymbol().IsIterator); + Assert.Equal("System.Int32", i.IteratorElementTypeWithAnnotations.ToTestDisplayString()); + } + + [Fact] + public void BasicIterators_Metadata() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + public class Test + { + public IEnumerable I1() + { + yield return 1; + } + + public async IAsyncEnumerable I2() + { + await Task.Yield(); + yield return 1; + } + } + """; + + var sourceComp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + sourceComp.VerifyDiagnostics(); + + var userComp = CreateCompilation("", references: [sourceComp.EmitToImageReference()]); + userComp.VerifyEmitDiagnostics(); + var testType = Assert.IsAssignableFrom(userComp.GetTypeByMetadataName("Test")); + + var i1 = testType.GetMethod("I1"); + Assert.False(i1.IsIterator); + Assert.False(i1.GetPublicSymbol().IsIterator); + + var i2 = testType.GetMethod("I2"); + Assert.False(i2.IsIterator); + Assert.False(i2.GetPublicSymbol().IsIterator); + } + + [Fact] + public void MethodJustReturnsEnumerable_NotIterator() + { + var source = """ + using System.Collections.Generic; + + class Test + { + IEnumerable I1() + { + return []; + } + + IAsyncEnumerable I2() + { + return default; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var i1 = comp.GetMember("Test.I1"); + Assert.False(i1.IsIterator); + Assert.False(i1.GetPublicSymbol().IsIterator); + + var i2 = comp.GetMember("Test.I2"); + Assert.False(i2.IsIterator); + Assert.False(i2.GetPublicSymbol().IsIterator); + } + [Fact] public void WrongYieldType() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index a5b2d45978332..3f571adf122cc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -2374,11 +2374,13 @@ public unsafe IEnumerable M4(int* a) var local = model.GetDeclaredSymbol(declaration).GetSymbol(); Assert.True(local.IsIterator); + Assert.True(local.GetPublicSymbol().IsIterator); Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString()); model.GetOperation(declaration.Body); Assert.True(local.IsIterator); + Assert.True(local.GetPublicSymbol().IsIterator); Assert.Equal("System.Int32", local.IteratorElementTypeWithAnnotations.ToTestDisplayString()); comp.VerifyDiagnostics( @@ -10709,5 +10711,89 @@ public class C(string p) Diagnostic(ErrorCode.ERR_StaticLocalFunctionCannotCaptureVariable, "p").WithArguments("p").WithLocation(16, 42) ] : []); } + + [Fact] + public void SimpleIteratorLocalFunction() + { + var source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + + class C + { + void M() + { + #pragma warning disable 8321 // The local function 'X' is declared but never used + + IEnumerable I1() + { + yield return 1; + } + + async IAsyncEnumerable I2() + { + await Task.Yield(); + yield return 1; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.True(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.True(i2Symbol.IsIterator); + } + + [Fact] + public void LocalFunctionJustReturnsEnumerable_NotIterator() + { + var source = """ + using System.Collections.Generic; + + class C + { + void M() + { + #pragma warning disable 8321 // The local function 'X' is declared but never used + + IEnumerable I1() + { + return []; + } + + IAsyncEnumerable I2() + { + return default; + } + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net60); + comp.VerifyDiagnostics(); + + var syntaxTree = comp.SyntaxTrees.Single(); + var semanticModel = comp.GetSemanticModel(syntaxTree); + var localFunctionSyntaxes = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); + + var i1Syntax = localFunctionSyntaxes[0]; + IMethodSymbol i1Symbol = semanticModel.GetDeclaredSymbol(i1Syntax); + Assert.False(i1Symbol.IsIterator); + + var i2Syntax = localFunctionSyntaxes[1]; + IMethodSymbol i2Symbol = semanticModel.GetDeclaredSymbol(i2Syntax); + Assert.False(i2Symbol.IsIterator); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index bf231df859a11..977c987f503b7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -11300,5 +11300,162 @@ public static void M({{scoped2}} {{modifier}} R r) { } """; CreateCompilation(source).VerifyDiagnostics(); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + [InlineData("public void UseSpan(Span span)", 17)] + [InlineData("void I.UseSpan(Span span)", 12)] + public void RefStructInterface_ScopedDifference(string implSignature, int column) + { + // This is a case where interface methods need to be treated specially in scoped variance checks. + // Because a ref struct can implement the interface, we need to compare the signatures as if the interface has a receiver parameter which is ref-to-ref-struct. + var source = $$""" + using System; + + interface I + { + void UseSpan(scoped Span span); + } + + ref struct RS : I + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + + {{implSignature}} // 1 + { + this.Span = span; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (13,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan(Span span) + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(13, column)); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + [InlineData("public readonly void UseSpan(Span span)")] + [InlineData("readonly void I.UseSpan(Span span)")] + public void RefStructInterface_ScopedDifference_ReadonlyImplementation(string implSignature) + { + var source = $$""" + using System; + + interface I + { + void UseSpan(scoped Span span); + } + + ref struct RS : I + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + + {{implSignature}} + { + Span = span; // 1 + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (15,9): error CS1604: Cannot assign to 'Span' because it is read-only + // Span = span; // 1 + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "Span").WithArguments("Span").WithLocation(15, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + public void SimilarScenarioAs78711InvolvingNonReceiverParameter() + { + // Demonstrate a scenario similar to https://github.com/dotnet/roslyn/issues/78711 involving a non-receiver parameter has consistent behavior + // In this case, the interface and implementation parameters cannot differ by type. But it as close as we can get to the receiver parameter case. + var source = """ + using System; + + interface I + { + void UseSpan1(ref RS rs, scoped Span span); + void UseSpan2(ref readonly RS rs, scoped Span span); + } + + class C : I + { + public void UseSpan1(ref RS rs, Span span) // 1 + { + rs.Span = span; + } + + public void UseSpan2(ref readonly RS rs, Span span) + { + rs.Span = span; // 2 + } + } + + ref struct RS + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (11,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan1(ref RS rs, Span span) // 1 + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan1").WithArguments("span").WithLocation(11, 17), + // (18,9): error CS8332: Cannot assign to a member of variable 'rs' or use it as the right hand side of a ref assignment because it is a readonly variable + // rs.Span = span; // 2 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "rs.Span").WithArguments("variable", "rs").WithLocation(18, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + public void Repro_78711() + { + var source = """ + using System; + + public static class Demo + { + public static void Show() + { + var stru = new Stru(); + + Unsafe(ref stru); + + Console.Out.WriteLine(stru.Span); + } + + private static void Unsafe(ref T stru) where T : IStru, allows ref struct + { + Span span = stackalloc char[10]; + + "bug".CopyTo(span); + + stru.UseSpan(span); + } + } + + internal interface IStru + { + public void UseSpan(scoped Span span); + } + + internal ref struct Stru : IStru + { + public Span Span; + + public void UseSpan(Span span) => Span = span; // 1 + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (33,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan(Span span) => Span = span; + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(33, 17)); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs index 0edf1f6e5fe4a..4de4f0482aad2 100644 --- a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs +++ b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs @@ -628,6 +628,9 @@ private static Syntax.InternalSyntax.QualifiedCrefSyntax GenerateQualifiedCref() private static Syntax.InternalSyntax.NameMemberCrefSyntax GenerateNameMemberCref() => InternalSyntaxFactory.NameMemberCref(GenerateIdentifierName(), null); + private static Syntax.InternalSyntax.ExtensionMemberCrefSyntax GenerateExtensionMemberCref() + => InternalSyntaxFactory.ExtensionMemberCref(InternalSyntaxFactory.Token(SyntaxKind.ExtensionKeyword), null, GenerateCrefParameterList(), InternalSyntaxFactory.Token(SyntaxKind.DotToken), GenerateNameMemberCref()); + private static Syntax.InternalSyntax.IndexerMemberCrefSyntax GenerateIndexerMemberCref() => InternalSyntaxFactory.IndexerMemberCref(InternalSyntaxFactory.Token(SyntaxKind.ThisKeyword), null); @@ -3377,6 +3380,20 @@ public void TestNameMemberCrefFactoryAndProperties() AttachAndCheckDiagnostics(node); } + [Fact] + public void TestExtensionMemberCrefFactoryAndProperties() + { + var node = GenerateExtensionMemberCref(); + + Assert.Equal(SyntaxKind.ExtensionKeyword, node.ExtensionKeyword.Kind); + Assert.Null(node.TypeArgumentList); + Assert.NotNull(node.Parameters); + Assert.Equal(SyntaxKind.DotToken, node.DotToken.Kind); + Assert.NotNull(node.Member); + + AttachAndCheckDiagnostics(node); + } + [Fact] public void TestIndexerMemberCrefFactoryAndProperties() { @@ -9272,6 +9289,32 @@ public void TestNameMemberCrefIdentityRewriter() Assert.Same(oldNode, newNode); } + [Fact] + public void TestExtensionMemberCrefTokenDeleteRewriter() + { + var oldNode = GenerateExtensionMemberCref(); + var rewriter = new TokenDeleteRewriter(); + var newNode = rewriter.Visit(oldNode); + + if(!oldNode.IsMissing) + { + Assert.NotEqual(oldNode, newNode); + } + + Assert.NotNull(newNode); + Assert.True(newNode.IsMissing, "No tokens => missing"); + } + + [Fact] + public void TestExtensionMemberCrefIdentityRewriter() + { + var oldNode = GenerateExtensionMemberCref(); + var rewriter = new IdentityRewriter(); + var newNode = rewriter.Visit(oldNode); + + Assert.Same(oldNode, newNode); + } + [Fact] public void TestIndexerMemberCrefTokenDeleteRewriter() { @@ -10935,6 +10978,9 @@ private static QualifiedCrefSyntax GenerateQualifiedCref() private static NameMemberCrefSyntax GenerateNameMemberCref() => SyntaxFactory.NameMemberCref(GenerateIdentifierName(), default(CrefParameterListSyntax)); + private static ExtensionMemberCrefSyntax GenerateExtensionMemberCref() + => SyntaxFactory.ExtensionMemberCref(SyntaxFactory.Token(SyntaxKind.ExtensionKeyword), default(TypeArgumentListSyntax), GenerateCrefParameterList(), SyntaxFactory.Token(SyntaxKind.DotToken), GenerateNameMemberCref()); + private static IndexerMemberCrefSyntax GenerateIndexerMemberCref() => SyntaxFactory.IndexerMemberCref(SyntaxFactory.Token(SyntaxKind.ThisKeyword), default(CrefBracketedParameterListSyntax)); @@ -13684,6 +13730,20 @@ public void TestNameMemberCrefFactoryAndProperties() Assert.Equal(node, newNode); } + [Fact] + public void TestExtensionMemberCrefFactoryAndProperties() + { + var node = GenerateExtensionMemberCref(); + + Assert.Equal(SyntaxKind.ExtensionKeyword, node.ExtensionKeyword.Kind()); + Assert.Null(node.TypeArgumentList); + Assert.NotNull(node.Parameters); + Assert.Equal(SyntaxKind.DotToken, node.DotToken.Kind()); + Assert.NotNull(node.Member); + var newNode = node.WithExtensionKeyword(node.ExtensionKeyword).WithTypeArgumentList(node.TypeArgumentList).WithParameters(node.Parameters).WithDotToken(node.DotToken).WithMember(node.Member); + Assert.Equal(node, newNode); + } + [Fact] public void TestIndexerMemberCrefFactoryAndProperties() { @@ -19579,6 +19639,32 @@ public void TestNameMemberCrefIdentityRewriter() Assert.Same(oldNode, newNode); } + [Fact] + public void TestExtensionMemberCrefTokenDeleteRewriter() + { + var oldNode = GenerateExtensionMemberCref(); + var rewriter = new TokenDeleteRewriter(); + var newNode = rewriter.Visit(oldNode); + + if(!oldNode.IsMissing) + { + Assert.NotEqual(oldNode, newNode); + } + + Assert.NotNull(newNode); + Assert.True(newNode.IsMissing, "No tokens => missing"); + } + + [Fact] + public void TestExtensionMemberCrefIdentityRewriter() + { + var oldNode = GenerateExtensionMemberCref(); + var rewriter = new IdentityRewriter(); + var newNode = rewriter.Visit(oldNode); + + Assert.Same(oldNode, newNode); + } + [Fact] public void TestIndexerMemberCrefTokenDeleteRewriter() { diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs index b17f2475f3594..a2360d1bf4c0d 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -4572,5 +4573,90 @@ disabled text 2 Assert.True(trivia.ContainsDiagnostics); Assert.Equal((int)ErrorCode.ERR_Merge_conflict_marker_encountered, trivia.Errors().Single().Code); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78593")] + public void TestDotPrefixedNumberStartingAtStartOfSlidingTextWindow() + { + // This test depends on the line endings for the file being \r\n to ensure the right contents lines up at + // the right locations. + // + // It specifically validates what happens when we see `.0` at the start of the + // sliding text window, where the lexer tries to peek back one char to see if this + // is actually `..0` (a range expr) or `.0` (a floating point number). + var code = Resources.DotPrefixedNumberStartingAtStartOfSlidingTextWindow; + if (!code.Contains("\r\n")) + code = code.Replace("\n", "\r\n"); + + var sourceText = SourceText.From(code); + + { + // Run a full parse, and validate the tree returned). + + using var lexer = new Lexer(sourceText, CSharpParseOptions.Default); + + // Ensure we have a normal window size, not some larger array that another test created and cached in + // the window pool + lexer.TextWindow.GetTestAccessor().SetDefaultCharacterWindow(); + + using var parser = new LanguageParser(lexer, oldTree: null, changes: null); + + Microsoft.CodeAnalysis.SyntaxTreeExtensions.VerifySource( + sourceText, parser.ParseCompilationUnit().CreateRed()); + } + + { + // Now, replicate the same conditions that hte parser runs through by driving the a new lexer here + // directly. That ensures that we are actually validating exactly the conditions that led to the bug + // (a dot token starting a number, right at the start of the character window). + var lexer = new Lexer(sourceText, CSharpParseOptions.Default); + + // Ensure we have a normal window size, not some larger array that another test created and cached in + // the window pool + lexer.TextWindow.GetTestAccessor().SetDefaultCharacterWindow(); + + var mode = LexerMode.Syntax; + for (var i = 0; i < 1326; i++) + lexer.Lex(ref mode); + + // Lexer will read from index 0 in the arrray. + Assert.Equal(0, lexer.TextWindow.Offset); + + // We have 205 real chars in the window + Assert.Equal(205, lexer.TextWindow.CharacterWindowCount); + + // The lexer is at position 10199 in the file. + Assert.Equal(10199, lexer.TextWindow.Position); + + /// The 205 characters represent the final part of the doc + Assert.Equal(lexer.TextWindow.Text.Length, lexer.TextWindow.Position + lexer.TextWindow.CharacterWindowCount); + + // We're at the start of a token. + Assert.Equal(lexer.TextWindow.LexemeStartPosition, lexer.TextWindow.Position); + + // Ensure that the lexer's window is starting with the next FP number (".03") right at + // the start of the window. + Assert.True(lexer.TextWindow.CharacterWindow is ['.', '0', '3', ',', ..], $"Start of window was '{new string(lexer.TextWindow.CharacterWindow, 0, 4)}'"); + + var token = lexer.Lex(ref mode); + Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind); + Assert.Equal(3, token.FullWidth); + Assert.Equal(".03", token.ToString()); + + // But we moved 3 characters forward. + Assert.Equal(3, lexer.TextWindow.Offset); + + // We still have 205 real chars in the window + Assert.Equal(205, lexer.TextWindow.CharacterWindowCount); + + // The lexer position has moved 3 characters forward as well. + Assert.Equal(10202, lexer.TextWindow.Position); + + // We're at the start of a token. + Assert.Equal(lexer.TextWindow.LexemeStartPosition, lexer.TextWindow.Position); + + // Character window didn't changee. + Assert.True(lexer.TextWindow.CharacterWindow is ['.', '0', '3', ',', ..], $"Start of window was '{new string(lexer.TextWindow.CharacterWindow, 0, 4)}'"); + } + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs index b478bdf25b928..da1e331238d5e 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using System; using System.Linq; @@ -3573,5 +3574,475 @@ public void AliasQualifiedGenericTypeConstructor() } #endregion Non-simple-type constructors + + #region Extension members + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_01() + { + UsingNode("extension", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_02() + { + UsingNode("E.extension", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "extension"); + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_03() + { + UsingNode("E.extension()", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "extension"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_04() + { + UsingNode("E.extension(int)", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "extension"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_05() + { + UsingNode("E.extension{T}", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "extension"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_06() + { + UsingNode("E.extension{T}()", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "extension"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_07() + { + UsingNode("E.extension{T}(int)", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "extension"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_08() + { + UsingNode("E.extension{T}(int).", options: TestOptions.RegularPreviewWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension{T}(int).' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension{T}(int).").WithArguments("E.extension{T}(int).").WithLocation(1, 16), + // (1,36): warning CS1658: Identifier expected. See also error CS1001. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, @"""").WithArguments("Identifier expected", "1001").WithLocation(1, 36)); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + M(SyntaxKind.NameMemberCref); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_09() + { + UsingNode("E.extension{T}(int).M", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_10() + { + UsingNode("E.extension{T}().M", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_11() + { + UsingNode("E.extension().M", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_12() + { + UsingNode("E.extension().extension().M", options: TestOptions.RegularPreviewWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'E.extension().extension().M' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "E.extension().extension().M").WithArguments("E.extension().extension().M").WithLocation(1, 16), + // (1,30): warning CS1658: An extension member syntax is disallowed in nested position within an extension member syntax. See also error CS9309. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "extension().M").WithArguments("An extension member syntax is disallowed in nested position within an extension member syntax", "9309").WithLocation(1, 30)); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "E"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + } + } + } + EOF(); + } + + [Fact, CompilerTrait(CompilerFeature.Extensions)] + public void ExtensionCref_13() + { + UsingNode("extension().M", options: TestOptions.RegularPreviewWithDocumentationComments); + + N(SyntaxKind.ExtensionMemberCref); + { + N(SyntaxKind.ExtensionKeyword); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + } + EOF(); + } + + #endregion } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ExtensionsParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ExtensionsParsingTests.cs index 8e383919cef6a..8d7bf2990fe4a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ExtensionsParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ExtensionsParsingTests.cs @@ -1568,7 +1568,7 @@ static class C """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,9): error CS9282: Extension declarations can include only methods or properties + // (5,9): error CS9282: This member is not allowed in an extension block // extension(Type2) { } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "extension").WithLocation(5, 9)); @@ -2143,7 +2143,7 @@ static class C """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,19): error CS9282: Extension declarations can include only methods or properties + // (5,19): error CS9282: This member is not allowed in an extension block // const int i = 0; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "i").WithLocation(5, 19)); @@ -2226,7 +2226,7 @@ static class C // (5,19): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context // fixed int field[10]; Diagnostic(ErrorCode.ERR_UnsafeNeeded, "field[10]").WithLocation(5, 19), - // (5,19): error CS9282: Extension declarations can include only methods or properties + // (5,19): error CS9282: This member is not allowed in an extension block // fixed int field[10]; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "field").WithLocation(5, 19)); @@ -2308,13 +2308,13 @@ static class C """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,35): error CS9282: Extension declarations can include only methods or properties + // (5,35): error CS9282: This member is not allowed in an extension block // event System.EventHandler eventField; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "eventField").WithLocation(5, 35), - // (5,35): error CS9282: Extension declarations can include only methods or properties + // (5,35): error CS9282: This member is not allowed in an extension block // event System.EventHandler eventField; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "eventField").WithLocation(5, 35), - // (5,35): error CS9282: Extension declarations can include only methods or properties + // (5,35): error CS9282: This member is not allowed in an extension block // event System.EventHandler eventField; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "eventField").WithLocation(5, 35), // (5,35): warning CS0067: The event 'C.extension(object).eventField' is never used @@ -2395,13 +2395,13 @@ event System.EventHandler Event { add { } remove { } } """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,35): error CS9282: Extension declarations can include only methods or properties + // (5,35): error CS9282: This member is not allowed in an extension block // event System.EventHandler Event { add { } remove { } } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Event").WithLocation(5, 35), - // (5,43): error CS9282: Extension declarations can include only methods or properties + // (5,43): error CS9282: This member is not allowed in an extension block // event System.EventHandler Event { add { } remove { } } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "add").WithLocation(5, 43), - // (5,51): error CS9282: Extension declarations can include only methods or properties + // (5,51): error CS9282: This member is not allowed in an extension block // event System.EventHandler Event { add { } remove { } } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "remove").WithLocation(5, 51)); @@ -2583,7 +2583,7 @@ class Nested { } """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,15): error CS9282: Extension declarations can include only methods or properties + // (5,15): error CS9282: This member is not allowed in an extension block // class Nested { } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Nested").WithLocation(5, 15)); @@ -2646,7 +2646,7 @@ static class C // (5,9): error CS1520: Method must have a return type // Constructor() { } Diagnostic(ErrorCode.ERR_MemberNeedsType, "Constructor").WithLocation(5, 9), - // (5,9): error CS9282: Extension declarations can include only methods or properties + // (5,9): error CS9282: This member is not allowed in an extension block // Constructor() { } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Constructor").WithLocation(5, 9)); @@ -2717,7 +2717,7 @@ static Constructor() { } // (5,16): error CS1520: Method must have a return type // static Constructor() { } Diagnostic(ErrorCode.ERR_MemberNeedsType, "Constructor").WithLocation(5, 16), - // (5,16): error CS9282: Extension declarations can include only methods or properties + // (5,16): error CS9282: This member is not allowed in an extension block // static Constructor() { } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Constructor").WithLocation(5, 16)); @@ -2785,7 +2785,7 @@ static class C """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,10): error CS9282: Extension declarations can include only methods or properties + // (5,10): error CS9282: This member is not allowed in an extension block // ~Finalizer() { } Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "Finalizer").WithLocation(5, 10)); @@ -2854,7 +2854,7 @@ static class C """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,13): error CS9282: Extension declarations can include only methods or properties + // (5,13): error CS9282: This member is not allowed in an extension block // int field; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "field").WithLocation(5, 13), // (5,13): warning CS0169: The field 'C.extension(object).field' is never used @@ -3024,7 +3024,7 @@ static class C // (5,39): error CS0563: One of the parameters of a binary operator must be the containing type // public static object operator +(object a, object b) => a; Diagnostic(ErrorCode.ERR_BadBinaryOperatorSignature, "+").WithLocation(5, 39), - // (5,39): error CS9282: Extension declarations can include only methods or properties + // (5,39): error CS9282: This member is not allowed in an extension block // public static object operator +(object a, object b) => a; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "+").WithLocation(5, 39)); @@ -3121,7 +3121,7 @@ static class C // (5,41): error CS0556: User-defined conversion must convert to or from the enclosing type // public static implicit operator int(object t) => 0; Diagnostic(ErrorCode.ERR_ConversionNotInvolvingContainedType, "int").WithLocation(5, 41), - // (5,41): error CS9282: Extension declarations can include only methods or properties + // (5,41): error CS9282: This member is not allowed in an extension block // public static implicit operator int(object t) => 0; Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "int").WithLocation(5, 41)); diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/IgnoredDirectiveParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/IgnoredDirectiveParsingTests.cs index fbf167277ec88..c741759106b0a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/IgnoredDirectiveParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/IgnoredDirectiveParsingTests.cs @@ -287,7 +287,7 @@ public void AfterToken() EOF(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78157")] public void AfterIf() { var source = """ @@ -299,7 +299,7 @@ public void AfterIf() """; VerifyTrivia(); - UsingTree(source, TestOptions.Regular.WithFeature(FeatureName), + UsingTree(source, TestOptions.Regular.WithFeature(FeatureName).WithPreprocessorSymbols("X"), // (3,2): error CS9283: '#:' directives cannot be after '#if' directive // #:y Diagnostic(ErrorCode.ERR_PPIgnoredFollowsIf, ":").WithLocation(3, 2), @@ -366,6 +366,71 @@ public void AfterIf() } } EOF(); + + UsingTree(source, TestOptions.Regular.WithFeature(FeatureName), + // (5,2): error CS9299: '#:' directives cannot be after '#if' directive + // #:z + Diagnostic(ErrorCode.ERR_PPIgnoredFollowsIf, ":").WithLocation(5, 2)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.EndOfFileToken); + { + L(SyntaxKind.IgnoredDirectiveTrivia); + { + N(SyntaxKind.HashToken); + N(SyntaxKind.ColonToken); + N(SyntaxKind.StringLiteralToken, "x"); + N(SyntaxKind.EndOfDirectiveToken); + { + T(SyntaxKind.EndOfLineTrivia, "\n"); + } + } + L(SyntaxKind.IfDirectiveTrivia); + { + N(SyntaxKind.HashToken); + N(SyntaxKind.IfKeyword); + { + T(SyntaxKind.WhitespaceTrivia, " "); + } + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.EndOfDirectiveToken); + { + T(SyntaxKind.EndOfLineTrivia, "\n"); + } + } + L(SyntaxKind.IgnoredDirectiveTrivia); + { + N(SyntaxKind.HashToken); + N(SyntaxKind.ColonToken); + N(SyntaxKind.StringLiteralToken, "y"); + N(SyntaxKind.EndOfDirectiveToken); + { + T(SyntaxKind.EndOfLineTrivia, "\n"); + } + } + L(SyntaxKind.EndIfDirectiveTrivia); + { + N(SyntaxKind.HashToken); + N(SyntaxKind.EndIfKeyword); + N(SyntaxKind.EndOfDirectiveToken); + { + T(SyntaxKind.EndOfLineTrivia, "\n"); + } + } + L(SyntaxKind.IgnoredDirectiveTrivia); + { + N(SyntaxKind.HashToken); + N(SyntaxKind.ColonToken); + N(SyntaxKind.StringLiteralToken, "z"); + N(SyntaxKind.EndOfDirectiveToken); + } + } + } + EOF(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Syntax/Resources.resx b/src/Compilers/CSharp/Test/Syntax/Resources.resx index 2004f7e760271..37ec16cc49bde 100644 --- a/src/Compilers/CSharp/Test/Syntax/Resources.resx +++ b/src/Compilers/CSharp/Test/Syntax/Resources.resx @@ -119,6 +119,253 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + // ------------------------------ +// THIS FILE IS AUTO GENERATED +// PLEASE DO NOT MODIFY +// ------------------------------ +// Hash: D5-B1-B5-D5-B6-27-C1-44-E0-20-77-14-52-A7-7A-E3-26-1C-6C-74-82-E1-2D-00-C3-73-22-7D-F5-CF-1B-76-95-45-88-4A-9A-0D-E3-D1-8F-69-F3-C3-76-41-92-67-F1-9D-64-EF-86-77-39-48-8C-0E-20-44-51-CA-5D-65 + +using Goobar.Combat; +using Goobar.Commons.Rules; +using Goobar.Localization; +using Goobar.Shared.Entities.Units; +using static System.Collections.Immutable.ImmutableArray; +using System; +using System.Collections.Immutable; +using Goobar.Shared.Utils; +using System.Collections.Generic; +using Goobar.GeneratedCode; + +namespace Goobar; + + +[GenerateAllValues(typeof(Goobar.Commons.Rules.UnitEquipmentAttributeDefinition))] +public static partial class EquipmentAttributes +{ + + public static class Minor_Power_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Power_Flat", + Attribute: CombatAttribute.Power, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(30.0, 60.0, 110.0, 180.0, 280.0), + SecondaryStatByRarityTo: Create(40.0, 80.0, 140.0, 220.0, 320.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Power_Perc + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Power_Perc", + Attribute: CombatAttribute.Power, + ModifierType: AttributeModifierType.Percentage, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.02, .04, .06, .09, .13), + SecondaryStatByRarityTo: Create(.03, .05, .08, .12, .16), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_CritChance_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_CritChance_Flat", + Attribute: CombatAttribute.CritChance, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.02, .04, .06, .09, .13), + SecondaryStatByRarityTo: Create(.03, .05, .08, .12, .16), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_CritBoost_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_CritBoost_Flat", + Attribute: CombatAttribute.CritBoost, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.02, .04, .06, .09, .13), + SecondaryStatByRarityTo: Create(.03, .05, .08, .12, .16), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Defense_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Defense_Flat", + Attribute: CombatAttribute.Defense, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(40.0, 100.0, 170.0, 280.0, 380.0), + SecondaryStatByRarityTo: Create(60.0, 120.0, 210.0, 330.0, 480.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Defense_Perc + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Defense_Perc", + Attribute: CombatAttribute.Defense, + ModifierType: AttributeModifierType.Percentage, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.02, .04, .06, .09, .13), + SecondaryStatByRarityTo: Create(.03, .05, .08, .12, .16), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_HullPoints_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_HullPoints_Flat", + Attribute: CombatAttribute.HullPoints, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(170.0, 390.0, 700.0, 1000.0, 1400.0), + SecondaryStatByRarityTo: Create(230.0, 450.0, 780.0, 1200.0, 1800.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_HullPoints_Perc + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_HullPoints_Perc", + Attribute: CombatAttribute.HullPoints, + ModifierType: AttributeModifierType.Percentage, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.02, .04, .06, .09, .13), + SecondaryStatByRarityTo: Create(.03, .05, .08, .12, .16), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Manipulation_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Manipulation_Flat", + Attribute: CombatAttribute.Manipulation, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(2.0, 4.0, 6.0, 8.0, 10.0), + SecondaryStatByRarityTo: Create(3.0, 5.0, 7.0, 9.0, 12.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Security_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Security_Flat", + Attribute: CombatAttribute.Security, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(2.0, 4.0, 6.0, 8.0, 10.0), + SecondaryStatByRarityTo: Create(3.0, 5.0, 7.0, 9.0, 12.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Minor_Initiative_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Minor_Initiative_Flat", + Attribute: CombatAttribute.Initiative, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(2.0, 4.0, 6.0, 8.0, 10.0), + SecondaryStatByRarityTo: Create(3.0, 5.0, 7.0, 9.0, 12.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_Power_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_Power_Flat", + Attribute: CombatAttribute.Power, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(40.0, 70.0, 130.0, 220.0, 340.0), + SecondaryStatByRarityTo: Create(50.0, 100.0, 170.0, 270.0, 400.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_Power_Perc + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_Power_Perc", + Attribute: CombatAttribute.Power, + ModifierType: AttributeModifierType.Percentage, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.03, .05, .07, .1, .16), + SecondaryStatByRarityTo: Create(.04, .06, .09, .15, .2), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_CritChance_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_CritChance_Flat", + Attribute: CombatAttribute.CritChance, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.03, .05, .07, .1, .16), + SecondaryStatByRarityTo: Create(.04, .06, .09, .15, .2), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_CritBoost_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_CritBoost_Flat", + Attribute: CombatAttribute.CritBoost, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(.03, .05, .07, .1, .16), + SecondaryStatByRarityTo: Create(.04, .06, .09, .15, .2), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_Defense_Flat + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_Defense_Flat", + Attribute: CombatAttribute.Defense, + ModifierType: AttributeModifierType.Flat, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom: Create(50.0, 120.0, 200.0, 340.0, 460.0), + SecondaryStatByRarityTo: Create(70.0, 140.0, 250.0, 400.0, 580.0), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } + + public static class Major_Defense_Perc + { + public static readonly UnitEquipmentAttributeDefinition Instance = new( + Id: "Major_Defense_Perc", + Attribute: CombatAttribute.Defense, + ModifierType: AttributeModifierType.Percentage, + PrimaryStatsByLevel: null, + SecondaryStatByRarityFrom1: Create(.03, .05, .07, .1, .16), + SecondaryStatByRarityTo: Create(.04, .06, .09, .15, .2), + CalibrationStatsByLevel: null, + UnitEquipmentSubType: UnitEquipmentSubType.Implant); + } +} + extern alias libAlias=other_library.dll; class myClass @@ -18522,4 +18769,4 @@ static int Main() int x = 0; #if A == A\ - + \ No newline at end of file diff --git a/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/EnumerableExtensionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/EnumerableExtensionsTests.cs similarity index 100% rename from src/Compilers/Core/CodeAnalysisTest/InternalUtilities/EnumerableExtensionsTests.cs rename to src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/EnumerableExtensionsTests.cs diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ImmutableArrayExtensionsTests.cs similarity index 91% rename from src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs rename to src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ImmutableArrayExtensionsTests.cs index 537dd761567aa..12df4b9146bd1 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ImmutableArrayExtensionsTests.cs @@ -7,13 +7,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.ObjectModel; using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.UnitTests.Collections +namespace Microsoft.CodeAnalysis.UnitTests { public class ImmutableArrayExtensionsTests { @@ -544,5 +543,47 @@ public void IsSubsetOf_Equal(int[] array, int[] other) Assert.True(array.ToImmutableArray().IsSubsetOf(other.ToImmutableArray())); Assert.True(other.ToImmutableArray().IsSubsetOf(array.ToImmutableArray())); } + + public sealed class Comparer(Func equals, Func hashCode) : IEqualityComparer + { + private readonly Func _equals = equals; + private readonly Func _hashCode = hashCode; + + public bool Equals(T x, T y) => _equals(x, y); + public int GetHashCode(T obj) => _hashCode(obj); + } + + [Fact] + public void HasDuplicates() + { + var comparer = new Comparer((x, y) => x % 10 == y % 10, x => (x % 10).GetHashCode()); + + Assert.False(ImmutableArray.Empty.HasDuplicates()); + Assert.False(ImmutableArray.Empty.HasDuplicates(comparer)); + Assert.False(ImmutableArray.Empty.HasDuplicates(i => i + 1)); + + Assert.False(ImmutableArray.Create(1).HasDuplicates()); + Assert.False(ImmutableArray.Create(1).HasDuplicates(comparer)); + Assert.False(ImmutableArray.Create(1).HasDuplicates(i => i + 1)); + + Assert.False(ImmutableArray.Create(1, 2).HasDuplicates()); + Assert.False(ImmutableArray.Create(1, 2).HasDuplicates(comparer)); + Assert.False(ImmutableArray.Create(1, 2).HasDuplicates(i => i + 1)); + + Assert.True(ImmutableArray.Create(1, 1).HasDuplicates()); + Assert.True(ImmutableArray.Create(11, 1).HasDuplicates(comparer)); + Assert.True(ImmutableArray.Create(1, 3).HasDuplicates(i => i % 2)); + Assert.True(ImmutableArray.Create(11.0, 1.2).HasDuplicates(i => (int)i, comparer)); + + Assert.False(ImmutableArray.Create(2, 0, 1, 3).HasDuplicates()); + Assert.False(ImmutableArray.Create(2, 0, 1, 13).HasDuplicates(comparer)); + Assert.False(ImmutableArray.Create(2, 0, 1, 53).HasDuplicates(i => i % 10)); + Assert.False(ImmutableArray.Create(2.3, 0.1, 1.3, 53.4).HasDuplicates(i => (int)i, comparer)); + + Assert.True(ImmutableArray.Create(2, 0, 1, 2).HasDuplicates()); + Assert.True(ImmutableArray.Create(2, 0, 1, 12).HasDuplicates(comparer)); + Assert.True(ImmutableArray.Create(2, 0, 1, 52).HasDuplicates(i => i % 10)); + Assert.True(ImmutableArray.Create(2.3, 0.1, 1.3, 52.4).HasDuplicates(i => (int)i, comparer)); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ListExtensionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ListExtensionsTests.cs new file mode 100644 index 0000000000000..6e324819dfcdf --- /dev/null +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/Extensions/ListExtensionsTests.cs @@ -0,0 +1,58 @@ +// 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 System; +using System.Collections.Generic; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests; + +public class ListExtensionsTests +{ + public sealed class Comparer(Func equals, Func hashCode) : IEqualityComparer + { + private readonly Func _equals = equals; + private readonly Func _hashCode = hashCode; + + public bool Equals(T x, T y) => _equals(x, y); + public int GetHashCode(T obj) => _hashCode(obj); + } + + [Fact] + public void HasDuplicates() + { + var comparer = new Comparer((x, y) => x % 10 == y % 10, x => (x % 10).GetHashCode()); + + Assert.False(new int[0].HasDuplicates()); + Assert.False(new int[0].HasDuplicates(comparer)); + Assert.False(new int[0].HasDuplicates(i => i + 1)); + + Assert.False(new[] { 1 }.HasDuplicates()); + Assert.False(new[] { 1 }.HasDuplicates(comparer)); + Assert.False(new[] { 1 }.HasDuplicates(i => i + 1)); + + Assert.False(new[] { 1, 2 }.HasDuplicates()); + Assert.False(new[] { 1, 2 }.HasDuplicates(comparer)); + Assert.False(new[] { 1, 2 }.HasDuplicates(i => i + 1)); + + Assert.True(new[] { 1, 1 }.HasDuplicates()); + Assert.True(new[] { 11, 1 }.HasDuplicates(comparer)); + Assert.True(new[] { 1, 3 }.HasDuplicates(i => i % 2)); + Assert.True(new[] { 11.0, 1.2 }.HasDuplicates(i => (int)i, comparer)); + + Assert.False(new[] { 2, 0, 1, 3 }.HasDuplicates()); + Assert.False(new[] { 2, 0, 1, 13 }.HasDuplicates(comparer)); + Assert.False(new[] { 2, 0, 1, 53 }.HasDuplicates(i => i % 10)); + Assert.False(new[] { 2.3, 0.1, 1.3, 53.4 }.HasDuplicates(i => (int)i, comparer)); + + Assert.True(new[] { 2, 0, 1, 2 }.HasDuplicates()); + Assert.True(new[] { 2, 0, 1, 12 }.HasDuplicates(comparer)); + Assert.True(new[] { 2, 0, 1, 52 }.HasDuplicates(i => i % 10)); + Assert.True(new[] { 2.3, 0.1, 1.3, 52.4 }.HasDuplicates(i => (int)i, comparer)); + } +} + diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs index 63b1cc00bafbe..13d7735555a29 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs @@ -155,37 +155,6 @@ private void RepeatTest( RepeatTest((e, i, it) => testCode(e, i), iters); } - private void VerifyModifiedEnumerator( - IEnumerator enumerator, - object expectedCurrent, - bool expectCurrentThrow, - bool atEnd) - { - if (expectCurrentThrow) - { - Assert.Throws( - () => enumerator.Current); - } - else - { - object? current = enumerator.Current; - for (int i = 0; i < 3; i++) - { - Assert.Equal(expectedCurrent, current); - current = enumerator.Current; - } - } - - Assert.Throws( - () => enumerator.MoveNext()); - - if (!!ResetImplemented) - { - Assert.Throws( - () => enumerator.Reset()); - } - } - private void VerifyEnumerator( IEnumerator enumerator, T[] expectedItems) diff --git a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs index b8e9e1df6ab39..4e3dba61cd567 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs @@ -11,8 +11,9 @@ internal sealed partial class CodeAnalysisEventSource : EventSource { public static class Keywords { - public const EventKeywords Performance = (EventKeywords)1; - public const EventKeywords Correctness = (EventKeywords)2; + public const EventKeywords Performance = (EventKeywords)0b001; + public const EventKeywords Correctness = (EventKeywords)0b010; + public const EventKeywords AnalyzerLoading = (EventKeywords)0b100; } public static class Tasks @@ -100,18 +101,87 @@ internal unsafe void NodeTransform(int nodeHashCode, string name, string tableTy [Event(8, Message = "Server compilation {0} completed", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.Compilation)] internal void StopServerCompilation(string name) => WriteEvent(8, name); - [Event(9, Message = "ALC for directory '{0}' created", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Start, Task = Tasks.AnalyzerAssemblyLoader)] - internal void CreateAssemblyLoadContext(string directory) => WriteEvent(9, directory); + [Event(9, Message = "ALC for directory '{0}' created", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Opcode = EventOpcode.Start, Task = Tasks.AnalyzerAssemblyLoader)] + internal void CreateAssemblyLoadContext(string directory, string? alc) => WriteEvent(9, directory, alc); - [Event(10, Message = "ALC for directory '{0}' disposed", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] - internal void DisposeAssemblyLoadContext(string directory) => WriteEvent(10, directory); + [Event(10, Message = "ALC for directory '{0}' disposed", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] + internal void DisposeAssemblyLoadContext(string directory, string? alc) => WriteEvent(10, directory, alc); - [Event(11, Message = "ALC for directory '{0}' disposal failed with exception '{1}'", Keywords = Keywords.Performance, Level = EventLevel.Error, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] - internal void DisposeAssemblyLoadContextException(string directory, string errorMessage) => WriteEvent(11, directory, errorMessage); + [Event(11, Message = "ALC for directory '{0}' disposal failed with exception '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Error, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] + internal void DisposeAssemblyLoadContextException(string directory, string errorMessage, string? alc) => WriteEvent(11, directory, errorMessage, alc); - [Event(12, Message = "CreateNonLockingLoader", Keywords = Keywords.Performance, Level = EventLevel.Informational, Task = Tasks.AnalyzerAssemblyLoader)] + [Event(12, Message = "CreateNonLockingLoader", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Task = Tasks.AnalyzerAssemblyLoader)] internal void CreateNonLockingLoader(string directory) => WriteEvent(12, directory); + [Event(13, Message = "Request add Analyzer reference '{0}' to project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRequestAddToProject(string path, string projectName) => WriteEvent(13, path, projectName); + + [Event(14, Message = "Analyzer reference '{0}' was added to project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceAddedToProject(string path, string projectName) => WriteEvent(14, path, projectName); + + [Event(15, Message = "Request remove Analyzer reference '{0}' from project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRequestRemoveFromProject(string path, string projectName) => WriteEvent(15, path, projectName); + + [Event(16, Message = "Analyzer reference '{0}' was removed from project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRemovedFromProject(string path, string projectName) => WriteEvent(16, path, projectName); + + [Event(17, Message = "Analyzer reference was redirected by '{0}' from '{1}' to '{2}' for project '{3}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Verbose, Task = Tasks.BuildStateTable)] + internal unsafe void AnanlyzerReferenceRedirected(string redirectorType, string originalPath, string newPath, string project) + { + if (IsEnabled()) + { + fixed (char* redirectorTypeBytes = redirectorType) + fixed (char* originalPathBytes = originalPath) + fixed (char* newPathBytes = newPath) + fixed (char* projectBytes = project) + { + Span data = + [ + GetEventDataForString(redirectorType, redirectorTypeBytes), + GetEventDataForString(originalPath, originalPathBytes), + GetEventDataForString(newPath, newPathBytes), + GetEventDataForString(project, projectBytes), + ]; + + fixed (EventData* dataPtr = data) + { + WriteEventCore(eventId: 17, data.Length, dataPtr); + } + } + } + } + + [Event(18, Message = "ALC for directory '{0}': Assembly '{1}' was resolved by '{2}' ", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal unsafe void ResolvedAssembly(string directory, string assemblyName, string resolver, string filePath, string alc) + { + if (IsEnabled()) + { + fixed (char* directoryBytes = directory) + fixed (char* assemblyNameBytes = assemblyName) + fixed (char* resolverBytes = resolver) + fixed (char* filePathBytes = filePath) + fixed (char* alcBytes = alc) + { + Span data = + [ + GetEventDataForString(directory, directoryBytes), + GetEventDataForString(assemblyName, assemblyNameBytes), + GetEventDataForString(resolver, resolverBytes), + GetEventDataForString(filePath, filePathBytes), + GetEventDataForString(alc, alcBytes), + ]; + + fixed (EventData* dataPtr = data) + { + WriteEventCore(eventId: 18, data.Length, dataPtr); + } + } + } + } + + [Event(19, Message = "ALC for directory '{0}': Failed to resolve assembly '{1}' ", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal unsafe void ResolveAssemblyFailed(string directory, string assemblyName) => WriteEvent(19, directory, assemblyName); + private static unsafe EventData GetEventDataForString(string value, char* ptr) { fixed (char* ptr2 = value) diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs index 3d7af82151476..b79e41b9ac324 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs @@ -1277,11 +1277,11 @@ public int GetILOffsetFromMarker(int ilMarker) private string GetDebuggerDisplay() { #if DEBUG - var visType = Type.GetType("Roslyn.Test.Utilities.ILBuilderVisualizer, Roslyn.Test.Utilities", false); + var visType = Type.GetType("Roslyn.Test.Utilities.ILBuilderVisualizer, Microsoft.CodeAnalysis.Test.Utilities", false); if (visType != null) { var method = visType.GetTypeInfo().GetDeclaredMethod("ILBuilderToString"); - return (string)method!.Invoke(null, [this, null, null])!; + return (string)method!.Invoke(null, [this, null, null, null])!; } #endif diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs index cc79c598cd69f..75ae0055ff582 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs @@ -239,9 +239,9 @@ public abstract class CommandLineArguments public bool NoWin32Manifest { get; internal set; } /// - /// Resources specified as arguments to the compilation. + /// Manifest resource information parsed from /resource arguments. /// - public ImmutableArray ManifestResources { get; internal set; } + public ImmutableArray ManifestResourceArguments { get; internal set; } /// /// Encoding to be used for source files or 'null' for autodetect/default. @@ -308,13 +308,24 @@ public CompilationOptions CompilationOptions /// public CultureInfo? PreferredUILang { get; internal set; } + // Cache the values so that underlying file streams are not created multiple times for the same files. + private readonly Lazy> _lazyManifestResources; + internal StrongNameProvider GetStrongNameProvider(StrongNameFileSystem fileSystem) => new DesktopStrongNameProvider(KeyFileSearchPaths, fileSystem); internal CommandLineArguments() { + _lazyManifestResources = new Lazy>( + () => ManifestResourceArguments.SelectAsArray(static r => r.ToDescription())); } + /// + /// Resources specified as arguments to the compilation. + /// + public ImmutableArray ManifestResources + => _lazyManifestResources.Value; + /// /// Returns a full path of the file that the compiler will generate the assembly to if compilation succeeds. /// diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs index f591e49836df7..ac05da9bf7f0d 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs @@ -822,21 +822,24 @@ static bool isClientArgsOption(string arg, string optionName, out bool hasValue, private static readonly char[] s_resourceSeparators = { ',' }; - internal static void ParseResourceDescription( + internal static bool TryParseResourceDescription( ReadOnlyMemory resourceDescriptor, string? baseDirectory, - bool skipLeadingSeparators, //VB does this - out string? filePath, - out string? fullPath, - out string? fileName, - out string resourceName, - out string? accessibility) + bool skipLeadingSeparators, // VB does this + bool allowEmptyAccessibility, // VB does this + [NotNullWhen(true)] out string? filePath, + [NotNullWhen(true)] out string? fullPath, + [NotNullWhen(true)] out string? fileName, + [NotNullWhen(true)] out string? resourceName, + [NotNullWhen(true)] out bool? isPublic, + out string? rawAccessibility) { filePath = null; fullPath = null; fileName = null; - resourceName = ""; - accessibility = null; + resourceName = null; + isPublic = null; + rawAccessibility = null; // resource descriptor is: "[,[,public|private]]" var parts = ArrayBuilder>.GetInstance(); @@ -867,17 +870,41 @@ internal static void ParseResourceDescription( if (length >= 3) { - accessibility = RemoveQuotesAndSlashes(parts[offset + 2]); + rawAccessibility = RemoveQuotesAndSlashes(parts[offset + 2]); + } + + if (rawAccessibility == null || rawAccessibility == "" && allowEmptyAccessibility) + { + // If no accessibility is given, we default to "public". + // NOTE: Dev10 distinguishes between null and empty. + isPublic = true; + } + else if (string.Equals(rawAccessibility, "public", StringComparison.OrdinalIgnoreCase)) + { + isPublic = true; + } + else if (string.Equals(rawAccessibility, "private", StringComparison.OrdinalIgnoreCase)) + { + isPublic = false; + } + else + { + isPublic = null; } parts.Free(); - if (RoslynString.IsNullOrWhiteSpace(filePath)) + + if (isPublic == null || RoslynString.IsNullOrWhiteSpace(filePath)) { - return; + return false; } fileName = PathUtilities.GetFileName(filePath); fullPath = FileUtilities.ResolveRelativePath(filePath, baseDirectory); + if (!PathUtilities.IsValidFilePath(fullPath)) + { + return false; + } // The default resource name is the file name. // Also use the file name for the name when user specifies string like "filePath,,private" @@ -885,6 +912,8 @@ internal static void ParseResourceDescription( { resourceName = fileName; } + + return true; } /// diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineResource.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineResource.cs new file mode 100644 index 0000000000000..d3e6900bb3953 --- /dev/null +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineResource.cs @@ -0,0 +1,79 @@ +// 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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Describes a manifest resource specification stored in command line arguments. +/// +public readonly struct CommandLineResource +{ + /// + /// Name of the manifest resource as it appears in metadata. + /// + public string ResourceName { get; } + + /// + /// Full path to the resource content file. + /// + public string FullPath { get; } + + /// + /// Accessibility of the resource. + /// + public bool IsPublic { get; } + + /// + /// File name of a linked resource, or null if the resource is embedded. + /// + public string? LinkedResourceFileName { get; } + + internal CommandLineResource(string resourceName, string fullPath, string? linkedResourceFileName, bool isPublic) + { + Debug.Assert(!resourceName.IsEmpty()); + Debug.Assert(PathUtilities.IsAbsolute(fullPath)); + + ResourceName = resourceName; + FullPath = fullPath; + LinkedResourceFileName = linkedResourceFileName; + IsPublic = isPublic; + } + + /// + /// True if the resource is embedded. + /// + public bool IsEmbedded + => LinkedResourceFileName == null; + + /// + /// True if the resource is linked. + /// + [MemberNotNullWhen(true, nameof(LinkedResourceFileName))] + public bool IsLinked + => LinkedResourceFileName != null; + + /// + /// Creates for this resource. + /// + internal ResourceDescription ToDescription() + { + // fail fast if the method is called on default(CommandLineResource) + var fullPath = FullPath ?? throw new NullReferenceException(); + + Func dataProvider = () => + { + // Use FileShare.ReadWrite because the file could be opened by the current process. + // For example, it is an XML doc file produced by the build. + return new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + }; + + return new ResourceDescription(ResourceName, LinkedResourceFileName, dataProvider, IsPublic, isEmbedded: IsEmbedded, checkArgs: false); + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs index a4b6fb9a55513..1fe98873aa0da 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs @@ -87,8 +87,8 @@ private partial Assembly Load(AssemblyName assemblyName, string resolvedPath) { if (!_loadContextByDirectory.TryGetValue(fullDirectoryPath, out loadContext)) { - CodeAnalysisEventSource.Log.CreateAssemblyLoadContext(fullDirectoryPath); loadContext = new DirectoryLoadContext(fullDirectoryPath, this); + CodeAnalysisEventSource.Log.CreateAssemblyLoadContext(fullDirectoryPath, loadContext.ToString()); _loadContextByDirectory[fullDirectoryPath] = loadContext; } } @@ -179,11 +179,11 @@ private partial void DisposeWorker() try { context.Unload(); - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory, context.ToString()); } catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) { - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString()); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString(), context.ToString()); } } @@ -209,10 +209,12 @@ public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader) var assembly = resolver.Resolve(_loader, assemblyName, this, Directory); if (assembly is not null) { + CodeAnalysisEventSource.Log.ResolvedAssembly(Directory, assemblyName.ToString(), resolver.GetType().Name, assembly.Location, GetLoadContext(assembly)!.ToString()); return assembly; } } + CodeAnalysisEventSource.Log.ResolveAssemblyFailed(Directory, assemblyName.ToString()); return null; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs index 513b7fd7d20eb..de498db0836d2 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis { /// - /// This interface gives the host the ability to control the actaul path used to load an analyzer into the + /// This interface gives the host the ability to control the actual path used to load an analyzer into the /// compiler. /// /// Instances of these types are considered in the order they are added to the . diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 20d769cfd2bd7..3dd2e0369982c 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,8 +1,18 @@ +Microsoft.CodeAnalysis.CommandLineArguments.ManifestResourceArguments.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.CommandLineResource +Microsoft.CodeAnalysis.CommandLineResource.CommandLineResource() -> void +Microsoft.CodeAnalysis.CommandLineResource.FullPath.get -> string! +Microsoft.CodeAnalysis.CommandLineResource.IsEmbedded.get -> bool +Microsoft.CodeAnalysis.CommandLineResource.IsLinked.get -> bool +Microsoft.CodeAnalysis.CommandLineResource.IsPublic.get -> bool +Microsoft.CodeAnalysis.CommandLineResource.LinkedResourceFileName.get -> string? +Microsoft.CodeAnalysis.CommandLineResource.ResourceName.get -> string! Microsoft.CodeAnalysis.Compilation.EmitDifference(Microsoft.CodeAnalysis.Emit.EmitBaseline! baseline, System.Collections.Generic.IEnumerable! edits, System.Func! isAddedSymbol, System.IO.Stream! metadataStream, System.IO.Stream! ilStream, System.IO.Stream! pdbStream, Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitDifferenceResult! Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitDifferenceOptions() -> void Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.get -> bool Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.init -> void +Microsoft.CodeAnalysis.IMethodSymbol.IsIterator.get -> bool static readonly Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.Default -> Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions Microsoft.CodeAnalysis.IEventSymbol.IsPartialDefinition.get -> bool Microsoft.CodeAnalysis.IEventSymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IEventSymbol? diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 74b62ca605838..471315eee86e4 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -293,5 +293,10 @@ public interface IMethodSymbol : ISymbol /// Returns a flag indicating whether this symbol has at least one applied/inherited conditional attribute. /// bool IsConditional { get; } + + /// + /// Returns if this method is a source method implemented as an iterator (either sync or async) + /// + bool IsIterator { get; } } } diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxTreeExtensions.cs b/src/Compilers/Core/Portable/Syntax/SyntaxTreeExtensions.cs index 39dc59103b1ab..7422b9c1882ca 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxTreeExtensions.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxTreeExtensions.cs @@ -19,8 +19,13 @@ internal static class SyntaxTreeExtensions [Conditional("DEBUG")] internal static void VerifySource(this SyntaxTree tree, IEnumerable? changes = null) { - var root = tree.GetRoot(); - var text = tree.GetText(); + VerifySource(tree.GetText(), tree.GetRoot(), changes); + } + + /// + [Conditional("DEBUG")] + internal static void VerifySource(SourceText text, SyntaxNode root, IEnumerable? changes = null) + { var fullSpan = new TextSpan(0, text.Length); SyntaxNode? node = null; diff --git a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs index fa0dee9b65980..95769465a02a9 100644 --- a/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs +++ b/src/Compilers/Core/RebuildTest/RebuildCommandLineTests.cs @@ -38,11 +38,6 @@ private void AddSourceFile(string filePath, string content) FilePathToStreamMap.Add(Path.Combine(BuildPaths.WorkingDirectory, filePath), new TestableFile(content)); } - private void AddReference(string filePath, byte[] imageBytes) - { - FilePathToStreamMap.Add(Path.Combine(BuildPaths.SdkDirectory!, filePath), new TestableFile(imageBytes)); - } - private void AddOutputFile(ref string? filePath) { if (filePath is object) diff --git a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs index 32f881abf5f02..d5874d984e045 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs @@ -117,23 +117,25 @@ public static MetadataReference EmitToImageReference( EmitOptions options = null, bool embedInteropTypes = false, ImmutableArray aliases = default, - DiagnosticDescription[] expectedWarnings = null) => EmitToPortableExecutableReference(comp, options, embedInteropTypes, aliases, expectedWarnings); + DiagnosticDescription[] expectedWarnings = null, + DocumentationProvider documentation = null) => EmitToPortableExecutableReference(comp, options, embedInteropTypes, aliases, expectedWarnings, documentation); public static PortableExecutableReference EmitToPortableExecutableReference( this Compilation comp, EmitOptions options = null, bool embedInteropTypes = false, ImmutableArray aliases = default, - DiagnosticDescription[] expectedWarnings = null) + DiagnosticDescription[] expectedWarnings = null, + DocumentationProvider documentation = null) { var image = comp.EmitToArray(options, expectedWarnings: expectedWarnings); if (comp.Options.OutputKind == OutputKind.NetModule) { - return ModuleMetadata.CreateFromImage(image).GetReference(display: comp.MakeSourceModuleName()); + return ModuleMetadata.CreateFromImage(image).GetReference(documentation, display: comp.MakeSourceModuleName()); } else { - return AssemblyMetadata.CreateFromImage(image).GetReference(aliases: aliases, embedInteropTypes: embedInteropTypes, display: comp.MakeSourceAssemblySimpleName()); + return AssemblyMetadata.CreateFromImage(image).GetReference(documentation, aliases: aliases, embedInteropTypes: embedInteropTypes, display: comp.MakeSourceAssemblySimpleName()); } } diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 5a17d6ac9d980..8c97ecf3362ba 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -175,19 +175,6 @@ private static string GetSnippetFromSyntax(SyntaxNode syntax) return $"'{prefix} ... {suffix}'"; } - private static bool ShouldLogType(IOperation operation) - { - var operationKind = (int)operation.Kind; - - // Expressions - if (operationKind >= 0x100 && operationKind < 0x400) - { - return true; - } - - return false; - } - protected void LogString(string str) { if (_pendingIndent) @@ -409,11 +396,6 @@ private void VisitArray(ImmutableArray list, string header, bool logElemen VisitArrayCommon(list, header, logElementCount, logNullForDefault, o => Visit(o)); } - private void VisitArray(ImmutableArray list, string header, bool logElementCount, bool logNullForDefault = false) - { - VisitArrayCommon(list, header, logElementCount, logNullForDefault, VisitSymbolArrayElement); - } - private void VisitArray(ImmutableArray list, string header, bool logElementCount, bool logNullForDefault = false) { VisitArrayCommon(list, header, logElementCount, logNullForDefault, VisitStringArrayElement); diff --git a/src/Compilers/Test/Core/TestResource.resx b/src/Compilers/Test/Core/TestResource.resx index 22f413935ce41..385fb3b385d16 100644 --- a/src/Compilers/Test/Core/TestResource.resx +++ b/src/Compilers/Test/Core/TestResource.resx @@ -877,6 +877,15 @@ class RawStringLiterals """; } +static class Extensions +{ + extension(int) + { + public static void M() { } + public static int P => 0; + } +} + #line 6 #line 2 "test.cs" #line default diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index b5003cb91f2ed..8fa34d3fd1978 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -298,7 +298,6 @@ public static class Features public const string Packaging = nameof(Packaging); public const string PasteTracking = nameof(PasteTracking); public const string Peek = nameof(Peek); - public const string Progression = nameof(Progression); public const string ProjectSystemShims = nameof(ProjectSystemShims); public const string SarifErrorLogging = nameof(SarifErrorLogging); public const string QuickInfo = nameof(QuickInfo); @@ -315,6 +314,7 @@ public static class Features public const string SmartIndent = nameof(SmartIndent); public const string SmartTokenFormatting = nameof(SmartTokenFormatting); public const string Snippets = nameof(Snippets); + public const string SolutionExplorer = nameof(SolutionExplorer); public const string SourceGenerators = nameof(SourceGenerators); public const string SplitComment = nameof(SplitComment); public const string SplitStringLiteral = nameof(SplitStringLiteral); diff --git a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb index 70dfaf80e5cc6..707f5b985cd23 100644 --- a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb +++ b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb @@ -116,7 +116,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim win32ResourceFile As String = Nothing Dim win32IconFile As String = Nothing Dim noWin32Manifest As Boolean = False - Dim managedResources = New List(Of ResourceDescription)() + Dim managedResources = New List(Of CommandLineResource)() Dim sourceFiles = New List(Of CommandLineSourceFile)() Dim hasSourceFiles = False Dim additionalFiles = New List(Of CommandLineSourceFile)() @@ -704,15 +704,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Continue For Case "res", "resource" - Dim embeddedResource = ParseResourceDescription(name, value, baseDirectory, diagnostics, embedded:=True) - If embeddedResource IsNot Nothing Then + Dim embeddedResource As CommandLineResource + If TryParseResourceDescription(name, value, baseDirectory, diagnostics, isEmbedded:=True, embeddedResource) Then managedResources.Add(embeddedResource) End If Continue For Case "linkres", "linkresource" - Dim linkedResource = ParseResourceDescription(name, value, baseDirectory, diagnostics, embedded:=False) - If linkedResource IsNot Nothing Then + Dim linkedResource As CommandLineResource + If TryParseResourceDescription(name, value, baseDirectory, diagnostics, isEmbedded:=False, linkedResource) Then managedResources.Add(linkedResource) End If Continue For @@ -1509,7 +1509,7 @@ lVbRuntimePlus: .DisplayHelp = displayHelp, .DisplayVersion = displayVersion, .DisplayLangVersions = displayLangVersions, - .ManifestResources = managedResources.AsImmutable(), + .ManifestResourceArguments = managedResources.AsImmutable(), .CompilationOptions = options, .ParseOptions = parseOptions, .EmitOptions = emitOptions, @@ -1525,7 +1525,7 @@ lVbRuntimePlus: .SkipAnalyzers = skipAnalyzers, .EmbeddedFiles = embeddedFiles.AsImmutable(), .GeneratedFilesOutputDirectory = generatedFilesOutputDirectory, - .ReportInternalsVisibleToAttributes = reportIVTs + .ReportInternalsVisibleToAttributes = reportIvts } End Function @@ -1705,10 +1705,18 @@ lVbRuntimePlus: End Function ' See ParseCommandLine in vbc.cpp. - Friend Overloads Shared Function ParseResourceDescription(name As String, resourceDescriptor As String, baseDirectory As String, diagnostics As IList(Of Diagnostic), embedded As Boolean) As ResourceDescription + Friend Overloads Shared Function TryParseResourceDescription( + argName As String, + resourceDescriptor As String, + baseDirectory As String, + diagnostics As IList(Of Diagnostic), + isEmbedded As Boolean, + ByRef resource As CommandLineResource) As Boolean + If String.IsNullOrEmpty(resourceDescriptor) Then - AddDiagnostic(diagnostics, ERRID.ERR_ArgumentRequired, name, ":") - Return Nothing + AddDiagnostic(diagnostics, ERRID.ERR_ArgumentRequired, argName, ":") + resource = Nothing + Return False End If ' NOTE: these are actually passed to out parameters of .ParseResourceDescription. @@ -1716,52 +1724,41 @@ lVbRuntimePlus: Dim fullPath As String = Nothing Dim fileName As String = Nothing Dim resourceName As String = Nothing - Dim accessibility As String = Nothing + Dim isPublic As Boolean? = Nothing + Dim rawAccessibility As String = Nothing - ParseResourceDescription( + If Not TryParseResourceDescription( resourceDescriptor.AsMemory(), baseDirectory, - True, + skipLeadingSeparators:=True, + allowEmptyAccessibility:=True, filePath, fullPath, fileName, resourceName, - accessibility) + isPublic, + rawAccessibility) Then - If String.IsNullOrWhiteSpace(filePath) Then - AddInvalidSwitchValueDiagnostic(diagnostics, name, filePath) - Return Nothing - End If + If isPublic Is Nothing Then + AddInvalidSwitchValueDiagnostic(diagnostics, argName, rawAccessibility) + ElseIf RoslynString.IsNullOrWhiteSpace(filePath) Then + AddInvalidSwitchValueDiagnostic(diagnostics, argName, filePath) + Else + Debug.Assert(Not PathUtilities.IsValidFilePath(fullPath)) + AddDiagnostic(diagnostics, ERRID.FTL_InvalidInputFileName, filePath) + End If - If Not PathUtilities.IsValidFilePath(fullPath) Then - AddDiagnostic(diagnostics, ERRID.FTL_InvalidInputFileName, filePath) - Return Nothing + resource = Nothing + Return False End If - Dim isPublic As Boolean - If String.IsNullOrEmpty(accessibility) Then - ' If no accessibility is given, we default to "public". - ' NOTE: Dev10 treats empty the same as null (the difference being that empty indicates a comma after the resource name). - ' NOTE: Dev10 distinguishes between empty and whitespace-only. - isPublic = True - ElseIf String.Equals(accessibility, "public", StringComparison.OrdinalIgnoreCase) Then - isPublic = True - ElseIf String.Equals(accessibility, "private", StringComparison.OrdinalIgnoreCase) Then - isPublic = False - Else - AddInvalidSwitchValueDiagnostic(diagnostics, name, accessibility) - Return Nothing - End If + resource = New CommandLineResource( + resourceName:=resourceName, + fullPath:=fullPath, + linkedResourceFileName:=If(isEmbedded, Nothing, fileName), + isPublic.Value) - Dim dataProvider As Func(Of Stream) = Function() - ' Use FileShare.ReadWrite because the file could be opened by the current process. - ' For example, it Is an XML doc file produced by the build. - Return New FileStream(fullPath, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite) - End Function - Return New ResourceDescription(resourceName, fileName, dataProvider, isPublic, embedded, checkArgs:=False) + Return True End Function Private Shared Sub AddInvalidSwitchValueDiagnostic(diagnostics As IList(Of Diagnostic), ByVal name As String, ByVal nullStringText As String) diff --git a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb index c2905bea3bc0d..7438416a2b6a3 100644 --- a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb +++ b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb @@ -194,22 +194,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Interpolation End Enum - - - - - - - - - - - - - - - - Partial Friend MustInherit Class BoundExpression Inherits BoundNode diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index 215b7105d69f8..7acc4a1a228fc 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -133,7 +133,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' Source: Returns whether this method is an iterator; i.e., does it have the Iterator modifier? ''' Metadata: Returns False; methods from metadata cannot be an iterator. ''' - Public MustOverride ReadOnly Property IsIterator As Boolean + Public MustOverride ReadOnly Property IsIterator As Boolean Implements IMethodSymbol.IsIterator ''' ''' Indicates whether the accessor is marked with the 'init' modifier. diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb index b325f2d3eb235..78a0e9cea769d 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb @@ -519,12 +519,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Return _lazyNetModuleAttributesBag End Function - Private Function GetNetModuleAttributes() As ImmutableArray(Of VisualBasicAttributeData) - Dim attributesBag = Me.GetNetModuleAttributesBag() - Debug.Assert(attributesBag.IsSealed) - Return attributesBag.Attributes - End Function - Friend Function GetNetModuleDecodedWellKnownAttributeData() As CommonAssemblyWellKnownAttributeData Dim attributesBag = Me.GetNetModuleAttributesBag() Debug.Assert(attributesBag.IsSealed) diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 8841ef4e3fa67..b2cc251b377d1 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -1158,180 +1158,165 @@ End Module").Path End Sub - Public Sub ParseResourceDescription() + Public Sub TryParseResourceDescription() Dim diags = New List(Of Diagnostic)() - Dim desc As ResourceDescription + Dim resource As CommandLineResource - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.True(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someName", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someName", resource.ResourceName) + Assert.True(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName,public", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName,public", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someName", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someName", resource.ResourceName) + Assert.True(resource.IsPublic) ' use file name in place of missing resource name - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.False(resource.IsPublic) ' quoted accessibility is fine - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,,""private""", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,,""private""", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.False(resource.IsPublic) ' leading commas are ignored... - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", ",,\somepath\someFile.goo.bar,,private", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", ",,\somepath\someFile.goo.bar,,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.False(resource.IsPublic) ' ...as long as there's no whitespace between them - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", ", ,\somepath\someFile.goo.bar,,private", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", ", ,\somepath\someFile.goo.bar,,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) ' trailing commas are ignored... - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.False(resource.IsPublic) ' ...even if there's whitespace between them - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private, ,", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,,private, ,", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("someFile.goo.bar", desc.FileName) - Assert.Equal("someFile.goo.bar", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("someFile.goo.bar", resource.LinkedResourceFileName) + Assert.Equal("someFile.goo.bar", resource.ResourceName) + Assert.False(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName,publi", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "\somepath\someFile.goo.bar,someName,publi", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", "publi")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "D:rive\relative\path,someName,public", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "D:rive\relative\path,someName,public", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("D:rive\relative\path")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "inva\l*d?path,someName,public", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "inva\l*d?path,someName,public", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("inva\l*d?path")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", Nothing, _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", Nothing, _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_ArgumentRequired).WithArguments("resource", ":")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_ArgumentRequired).WithArguments("resource", ":")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " , ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " , ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path, ", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path, ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("path", desc.FileName) - Assert.Equal("path", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("path", resource.LinkedResourceFileName) + Assert.Equal("path", resource.ResourceName) + Assert.True(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " ,name", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " ,name", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " , , ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " , , ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path, , ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path, , ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " ,name, ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " ,name, ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " , ,private", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " , ,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path,name,", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path,name,", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("path", desc.FileName) - Assert.Equal("name", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("path", resource.LinkedResourceFileName) + Assert.Equal("name", resource.ResourceName) + Assert.True(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path,name,,", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path,name,,", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("path", desc.FileName) - Assert.Equal("name", desc.ResourceName) - Assert.True(desc.IsPublic) + Assert.Equal("path", resource.LinkedResourceFileName) + Assert.Equal("name", resource.ResourceName) + Assert.True(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path,name, ", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path,name, ", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", "path, ,private", _baseDirectory, diags, embedded:=False) + Assert.True(VisualBasicCommandLineParser.TryParseResourceDescription("resource", "path, ,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify() diags.Clear() - Assert.Equal("path", desc.FileName) - Assert.Equal("path", desc.ResourceName) - Assert.False(desc.IsPublic) + Assert.Equal("path", resource.LinkedResourceFileName) + Assert.Equal("path", resource.ResourceName) + Assert.False(resource.IsPublic) - desc = VisualBasicCommandLineParser.ParseResourceDescription("resource", " ,name,private", _baseDirectory, diags, embedded:=False) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("resource", " ,name,private", _baseDirectory, diags, isEmbedded:=False, resource)) diags.Verify(Diagnostic(ERRID.ERR_InvalidSwitchValue).WithArguments("resource", " ")) diags.Clear() - Assert.Null(desc) Dim longI = New String("i"c, 260) - desc = VisualBasicCommandLineParser.ParseResourceDescription("", String.Format("{0},e,private", longI), _baseDirectory, diags, embedded:=False) - ' // error BC2032: File name 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii' is empty, contains invalid characters, has a drive specification without an absolute path, or is too long - diags.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii").WithLocation(1, 1)) + Assert.False(VisualBasicCommandLineParser.TryParseResourceDescription("", String.Format("{0},e,private", longI), _baseDirectory, diags, isEmbedded:=False, resource)) + ' // error BC2032: File name '...' is empty, contains invalid characters, has a drive specification without an absolute path, or is too long + diags.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments(longI).WithLocation(1, 1)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb index 9eb87de985e30..6e5e0876ee2d4 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/IteratorTests.vb @@ -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. +Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities @@ -12,6 +14,75 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics Public Class IteratorTests Inherits FlowTestBase + + Public Sub BasicIterator() + Dim compilation = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim i = compilation.GetMember(Of MethodSymbol)("C.I") + Assert.True(i.IsIterator) + Assert.True(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + + + Public Sub BasicIterator_Metadata() + Dim sourceComp = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim userComp = CreateCompilation("", references:={sourceComp.EmitToImageReference()}).VerifyDiagnostics() + Dim cMetadataType = Assert.IsAssignableFrom(Of PENamedTypeSymbol)(userComp.GetTypeByMetadataName("C")) + + Dim i = cMetadataType.GetMethod("I") + Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + + + Public Sub Method_NotIterator() + Dim compilation = CreateCompilation( + + + + + ).VerifyDiagnostics() + + Dim i = compilation.GetMember(Of MethodSymbol)("C.I") + Assert.False(i.IsIterator) + Assert.False(DirectCast(i, IMethodSymbol).IsIterator) + End Sub + Public Sub IteratorNoYields() Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime( @@ -978,6 +1049,28 @@ End Class CompileAndVerify(compilation.WithOptions(TestOptions.ReleaseExe), expected) End Sub + + Public Sub SimpleIteratorProperty() + Dim compilation = CreateCompilation( + + +).VerifyDiagnostics() + + Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") + Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + @@ -1016,13 +1109,64 @@ End Class compilation.AssertTheseEmitDiagnostics() Dim [property] = compilation.GetMember(Of PropertySymbol)("A.P") Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) Assert.False([property].SetMethod.IsIterator) + Assert.False(DirectCast([property].SetMethod, IMethodSymbol).IsIterator) [property] = compilation.GetMember(Of PropertySymbol)("B.P") Assert.True([property].GetMethod.IsIterator) + Assert.True(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) Assert.False([property].SetMethod.IsIterator) + Assert.False(DirectCast([property].SetMethod, IMethodSymbol).IsIterator) CompileAndVerify(compilation, expectedOutput:="123") End Sub + + Public Sub IteratorProperty_Metadata() + Dim sourceComp = CreateCompilation( + + +).VerifyDiagnostics() + + Dim userComp = CreateCompilation("", references:={sourceComp.EmitToImageReference()}).VerifyDiagnostics() + Dim cMetadataType = Assert.IsAssignableFrom(Of PENamedTypeSymbol)(userComp.GetTypeByMetadataName("C")) + + Dim [property] = cMetadataType.GetProperty("P") + Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + + + Public Sub Property_NotIterator() + Dim compilation = CreateCompilation( + + +).VerifyDiagnostics() + + Dim [property] = compilation.GetMember(Of PropertySymbol)("C.P") + Assert.False([property].GetMethod.IsIterator) + Assert.False(DirectCast([property].GetMethod, IMethodSymbol).IsIterator) + End Sub + Public Sub CompilerLoweringPreserveAttribute_01() Dim source1 = " diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb index be410c2a6445f..e2cfdfb80a5ee 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/LambdaTests.vb @@ -5,6 +5,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics @@ -2491,5 +2492,54 @@ End Class CompileAndVerify(comp1, symbolValidator:=validate).VerifyDiagnostics() End Sub + + Public Sub IteratorLambda() + Dim compilation = CreateCompilation( + + +Imports System.Collections.Generic + +Class C + Sub M() + Dim lambda = Iterator Function() As IEnumerable(Of Integer) + Yield 1 + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim lambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Single() + Dim lambdaSymbolInfo = semanticModel.GetSymbolInfo(lambdaSyntax) + Dim lambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(lambdaSymbolInfo.Symbol) + Assert.True(lambdaMethod.IsIterator) + End Sub + + + Public Sub NotIteratorLambda() + Dim compilation = CreateCompilation( + + +Imports System.Collections.Generic + +Class C + Sub M() + Dim lambda = Function() As IEnumerable(Of Integer) + Return Nothing + End Function + End Sub +End Class + +).VerifyDiagnostics() + + Dim syntaxTree = compilation.SyntaxTrees.Single() + Dim semanticModel = compilation.GetSemanticModel(syntaxTree) + Dim lambdaSyntax = syntaxTree.GetRoot().DescendantNodes().OfType(Of LambdaExpressionSyntax)().Single() + Dim lambdaSymbolInfo = semanticModel.GetSymbolInfo(lambdaSyntax) + Dim lambdaMethod As IMethodSymbol = Assert.IsAssignableFrom(Of IMethodSymbol)(lambdaSymbolInfo.Symbol) + Assert.False(lambdaMethod.IsIterator) + End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb index 5c6423f123c86..2dd567fc52e14 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb @@ -6062,7 +6062,7 @@ static class E Dim comp As Compilation If useMetadata Then Dim libComp = CreateCSharpCompilation("c", text, parseOptions:=parseOptions) - comp = CreateCSharpCompilation("d", code:="", parseOptions:=parseOptions, referencedAssemblies:={libComp.EmitToImageReference()}) + comp = CreateCSharpCompilation("d", code:="", parseOptions:=parseOptions, referencedAssemblies:=libComp.References.Concat(libComp.EmitToImageReference())) Else comp = CreateCSharpCompilation("c", text, parseOptions:=parseOptions) End If @@ -6070,6 +6070,7 @@ static class E Dim e = DirectCast(comp.GlobalNamespace.GetMembers("E").Single(), ITypeSymbol) Dim extension = e.GetMembers().OfType(Of ITypeSymbol).Single() + Assert.True(extension.IsExtension) Assert.Equal("E.<>E__0", SymbolDisplay.ToDisplayString(extension, format)) Dim parts = SymbolDisplay.ToDisplayParts(extension, format) @@ -6116,7 +6117,7 @@ static class E Dim comp As Compilation If useMetadata Then Dim libComp = CreateCSharpCompilation("c", text, parseOptions:=parseOptions) - comp = CreateCSharpCompilation("d", code:="", parseOptions:=parseOptions, referencedAssemblies:={libComp.EmitToImageReference()}) + comp = CreateCSharpCompilation("d", code:="", parseOptions:=parseOptions, referencedAssemblies:=libComp.References.Concat(libComp.EmitToImageReference())) Else comp = CreateCSharpCompilation("c", text, parseOptions:=parseOptions) End If @@ -6125,6 +6126,7 @@ static class E Dim extension = e.GetMembers().OfType(Of ITypeSymbol).Single() ' Tracked by https://github.com/dotnet/roslyn/issues/76130 : the arity should not be included in the extension type name + Assert.True(extension.IsExtension) Assert.Equal("E.<>E__0`1(Of T)", SymbolDisplay.ToDisplayString(extension, format)) Dim parts = SymbolDisplay.ToDisplayParts(extension, format) diff --git a/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs b/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs index 0e86991e66dea..144d037776595 100644 --- a/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs +++ b/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs @@ -337,6 +337,72 @@ public static bool IsEmpty(this List source) return source.Count == 0; } + public static bool HasDuplicates(this IEnumerable source) + => source.HasDuplicates(EqualityComparer.Default); + + public static bool HasDuplicates(this IEnumerable source, IEqualityComparer comparer) + => source.HasDuplicates(static x => x, comparer); + + public static bool HasDuplicates(this IEnumerable source, Func selector) + => source.HasDuplicates(selector, EqualityComparer.Default); + + /// + /// Determines whether duplicates exist using given equality comparer. + /// + /// Array to search for duplicates + /// Whether duplicates were found + /// + /// API proposal: https://github.com/dotnet/runtime/issues/30582. + /// + /// + public static bool HasDuplicates(this IEnumerable source, Func selector, IEqualityComparer comparer) + { + if (source is IReadOnlyList list) + { + return list.HasDuplicates(selector, comparer); + } + + TItem firstItem = default!; + HashSet? set = null; + var isFirstItem = true; + var result = false; + + foreach (var item in source) + { + if (isFirstItem) + { + firstItem = item; + isFirstItem = false; + continue; + } + + var value = selector(item); + + if (set == null) + { + var firstValue = selector(firstItem); + + if (comparer.Equals(value, firstValue)) + { + result = true; + break; + } + + set = comparer == EqualityComparer.Default ? PooledHashSet.GetInstance() : new HashSet(comparer); + set.Add(firstValue); + set.Add(value); + } + else if (!set.Add(value)) + { + result = true; + break; + } + } + + (set as PooledHashSet)?.Free(); + return result; + } + private static readonly Func s_notNullTest = x => x != null; public static IEnumerable WhereNotNull(this IEnumerable source) diff --git a/src/Dependencies/Collections/Extensions/IListExtensions.cs b/src/Dependencies/Collections/Extensions/IListExtensions.cs new file mode 100644 index 0000000000000..bfff11ebe14f8 --- /dev/null +++ b/src/Dependencies/Collections/Extensions/IListExtensions.cs @@ -0,0 +1,61 @@ +// 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 Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis; + +internal static class IListExtensions +{ + public static bool HasDuplicates(this IReadOnlyList list) + => list.HasDuplicates(EqualityComparer.Default); + + public static bool HasDuplicates(this IReadOnlyList list, IEqualityComparer comparer) + => list.HasDuplicates(static x => x, comparer); + + public static bool HasDuplicates(this IReadOnlyList list, Func selector) + => list.HasDuplicates(selector, EqualityComparer.Default); + + /// + /// Determines whether duplicates exist using given equality comparer. + /// + /// Array to search for duplicates + /// Whether duplicates were found + /// + /// API proposal: https://github.com/dotnet/runtime/issues/30582. + /// + /// + /// + internal static bool HasDuplicates(this IReadOnlyList list, Func selector, IEqualityComparer comparer) + { + switch (list.Count) + { + case 0: + case 1: + return false; + + case 2: + return comparer.Equals(selector(list[0]), selector(list[1])); + + default: + var set = comparer == EqualityComparer.Default ? PooledHashSet.GetInstance() : new HashSet(comparer); + var result = false; + + // index to avoid allocating enumerator + for (int i = 0, n = list.Count; i < n; i++) + { + if (!set.Add(selector(list[i]))) + { + result = true; + break; + } + } + + (set as PooledHashSet)?.Free(); + return result; + } + } +} diff --git a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs index 06227b65bc8a5..21f358a580b0a 100644 --- a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs +++ b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs @@ -1041,48 +1041,25 @@ internal static ImmutableArray AddRange(this ImmutableArray self, in Te return ImmutableCollectionsMarshal.AsImmutableArray(builder); } - /// - /// Determines whether duplicates exist using default equality comparer. - /// - /// Array to search for duplicates - /// Whether duplicates were found internal static bool HasDuplicates(this ImmutableArray array) - { - switch (array.Length) - { - case 0: - case 1: - return false; - - case 2: - return EqualityComparer.Default.Equals(array[0], array[1]); - - default: - var set = PooledHashSet.GetInstance(); - var foundDuplicate = false; + => array.HasDuplicates(EqualityComparer.Default); - foreach (var element in array) - { - if (!set.Add(element)) - { - foundDuplicate = true; - break; - } - } + internal static bool HasDuplicates(this ImmutableArray array, IEqualityComparer comparer) + => array.HasDuplicates(static x => x, comparer); - set.Free(); - return foundDuplicate; - } - } + public static bool HasDuplicates(this ImmutableArray array, Func selector) + => array.HasDuplicates(selector, EqualityComparer.Default); /// - /// Determines whether duplicates exist using . Use other override - /// if you don't need a custom comparer. + /// Determines whether duplicates exist using given equality comparer. /// /// Array to search for duplicates - /// Comparer to use in search /// Whether duplicates were found - internal static bool HasDuplicates(this ImmutableArray array, IEqualityComparer comparer) + /// + /// API proposal: https://github.com/dotnet/runtime/issues/30582. + /// + /// + internal static bool HasDuplicates(this ImmutableArray array, Func selector, IEqualityComparer comparer) { switch (array.Length) { @@ -1091,20 +1068,23 @@ internal static bool HasDuplicates(this ImmutableArray array, IEqualityCom return false; case 2: - comparer ??= EqualityComparer.Default; - return comparer.Equals(array[0], array[1]); + return comparer.Equals(selector(array[0]), selector(array[1])); default: - var set = new HashSet(comparer); + var set = comparer == EqualityComparer.Default ? PooledHashSet.GetInstance() : new HashSet(comparer); + var result = false; + foreach (var element in array) { - if (!set.Add(element)) + if (!set.Add(selector(element))) { - return true; + result = true; + break; } } - return false; + (set as PooledHashSet)?.Free(); + return result; } } diff --git a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems index 0caa0d326ffad..77f4e14de4e80 100644 --- a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems +++ b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems @@ -70,6 +70,7 @@ + @@ -89,4 +90,4 @@ - \ No newline at end of file + diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/RemoveParametersTests.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/RemoveParametersTests.cs index 04a4375f91210..0d22ee322cb97 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/RemoveParametersTests.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/RemoveParametersTests.cs @@ -375,7 +375,7 @@ class C """), workspaceKind: WorkspaceKind.Interactive, - composition: EditorTestCompositions.EditorFeaturesWpf); + composition: EditorTestCompositions.EditorFeatures); // Force initialization. workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id).GetTextView()).ToList(); diff --git a/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs index aea301c35c97c..b7dc77d7c9d8c 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs @@ -34,7 +34,7 @@ protected override ImmutableArray MassageActions(ImmutableArray EditorTestCompositions.EditorFeaturesWpf; + => EditorTestCompositions.EditorFeatures; #region Generate Class diff --git a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs index 705834a29b7ad..7ee5e8e533ba7 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs @@ -220,7 +220,7 @@ public async Task TestNotOnValueType() class C { - public C([||]int i) + public C([||]DateTime d) { } } @@ -3582,4 +3582,352 @@ void M(object o, DayOfWeek dayOfWeek, string s) ReferenceAssemblies = ReferenceAssemblies.Net.Net80 }.RunAsync(); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + public async Task TestSimpleNumericChecks_ModernOverloads(string validNumericType) + { + var code = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + } + } + """; + + await new VerifyCS.Test() + { + TestCode = code, + FixedCode = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + ArgumentOutOfRangeException.ThrowIfNegative(num); + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + CodeActionIndex = 0, + }.RunAsync(); + + await new VerifyCS.Test() + { + TestCode = code, + FixedCode = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(num); + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + CodeActionIndex = 1, + }.RunAsync(); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + public async Task TestSimpleNumericChecks_OldStyleCheckStatement(string validNumericType) + { + var code = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + } + } + """; + + await new VerifyCS.Test() + { + TestCode = code, + FixedCode = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + if (num < 0) + { + throw new ArgumentOutOfRangeException(nameof(num), num, $"{{string.Format(FeaturesResources._0_cannot_be_negative, "{nameof(num)}").Replace("\"", "\\\"")}}"); + } + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, + CodeActionIndex = 0, + }.RunAsync(); + + await new VerifyCS.Test() + { + TestCode = code, + FixedCode = $$""" + using System; + + class C + { + void M({{validNumericType}} [|num|]) + { + if (num <= 0) + { + throw new ArgumentOutOfRangeException(nameof(num), num, $"{{string.Format(FeaturesResources._0_cannot_be_negative_or_zero, "{nameof(num)}").Replace("\"", "\\\"")}}"); + } + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, + CodeActionIndex = 1, + }.RunAsync(); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + [InlineData("byte")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + public async Task TestNoNumericChecksForUnsignedAndFloatingPointNumericTypes(string invalidNumericType) + { + await VerifyCS.VerifyRefactoringAsync($$""" + using System; + + class C + { + void M({{invalidNumericType}} [|num|]) + { + } + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + public async Task TestNoNumericChecksForOutParameter() + { + await VerifyCS.VerifyRefactoringAsync(""" + using System; + + class C + { + void M(out int [|num|]) + { + num = 0; + } + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + [InlineData("ThrowIfNegative(num)")] + [InlineData("ThrowIfNegativeOrZero(num)")] + [InlineData("ThrowIfEqual(num, 5)")] + [InlineData("ThrowIfGreaterThan(num, 6)")] + [InlineData("ThrowIfGreaterThanOrEqual(num, 1)")] + [InlineData("ThrowIfLessThan(num, 2)")] + [InlineData("ThrowIfLessThanOrEqual(num, 3)")] + [InlineData("ThrowIfEqual(num, 15)")] + [InlineData("ThrowIfNotEqual(num, 10)")] + [InlineData("ThrowIfZero(num)")] + public async Task TestNoNumericChecksIfAlreadyExist_ModernOverloads(string methodInvocation) + { + await new VerifyCS.Test() + { + TestCode = $$""" + using System; + + class C + { + void M(int [|num|]) + { + ArgumentOutOfRangeException.{{methodInvocation}}; + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }.RunAsync(); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + [InlineData("num < 0")] + [InlineData("num <= 0")] + [InlineData("num > 11")] + [InlineData("num >= 12")] + [InlineData("0 > num")] + [InlineData("0 >= num")] + [InlineData("14 < num")] + [InlineData("22 <= num")] + [InlineData("num is < 0")] + [InlineData("num is <= 0")] + [InlineData("num < 1")] + [InlineData("num <= 39")] + [InlineData("25 > num")] + [InlineData("29 >= num")] + [InlineData("num is < 8")] + [InlineData("num is <= 18")] + [InlineData("num is > 5")] + [InlineData("num is >= 3")] + public async Task TestNoNumericChecksIfAlreadyExist_OldStyleCheckStatements(string numericCheck) + { + await new VerifyCS.Test() + { + TestCode = $$""" + using System; + + class C + { + void M(int [|num|]) + { + if ({{numericCheck}}) + { + throw new ArgumentOutOfRangeException(nameof(num)); + } + } + } + """, + LanguageVersion = LanguageVersion.Latest, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80 + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + public async Task TestNumericChecksAfterAnotherNumericCheck() + { + await new VerifyCS.Test() + { + TestCode = """ + using System; + + class C + { + void M(sbyte a, short [|b|]) + { + ArgumentOutOfRangeException.ThrowIfNegative(a); + } + } + """, + FixedCode = """ + using System; + + class C + { + void M(sbyte a, short [|b|]) + { + ArgumentOutOfRangeException.ThrowIfNegative(a); + ArgumentOutOfRangeException.ThrowIfNegative(b); + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + public async Task TestNumericChecksBeforeAnotherNumericCheck() + { + await new VerifyCS.Test() + { + TestCode = """ + using System; + + class C + { + void M(long [|a|], int b) + { + if (b < 0) + { + throw new ArgumentOutOfRangeException(nameof(b)); + } + } + } + """, + FixedCode = """ + using System; + + class C + { + void M(long a, int b) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(a); + if (b < 0) + { + throw new ArgumentOutOfRangeException(nameof(b)); + } + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + CodeActionIndex = 1 + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37653")] + public async Task TestNumericChecksInBetweenDifferentChecks() + { + await new VerifyCS.Test() + { + TestCode = """ + using System; + + class C + { + void M(DayOfWeek day, int [|i|], string s) + { + if (!Enum.IsDefined(typeof(DayOfWeek), day)) + { + throw new System.ComponentModel.InvalidEnumArgumentException(nameof(day), (int)day, typeof(DayOfWeek)); + } + + if (string.IsNullOrEmpty(s)) + { + throw new ArgumentNullException(nameof(s)); + } + } + } + """, + FixedCode = $$""" + using System; + + class C + { + void M(DayOfWeek day, int i, string s) + { + if (!Enum.IsDefined(typeof(DayOfWeek), day)) + { + throw new System.ComponentModel.InvalidEnumArgumentException(nameof(day), (int)day, typeof(DayOfWeek)); + } + + if (i < 0) + { + throw new ArgumentOutOfRangeException(nameof(i), i, $"{{string.Format(FeaturesResources._0_cannot_be_negative, "{nameof(i)}").Replace("\"", "\\\"")}}"); + } + + if (string.IsNullOrEmpty(s)) + { + throw new ArgumentNullException(nameof(s)); + } + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20 + }.RunAsync(); + } } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs index 8ca51ced64ad1..8ca877748d2f3 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; public sealed partial class PreviewTests : AbstractCSharpCodeActionTest { - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures .AddParts( typeof(MockPreviewPaneService)); diff --git a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs index b935477b81d21..b821b64c58ad0 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/SyncNamespace/SyncNamespaceTests_ChangeNamespace.cs @@ -1448,6 +1448,72 @@ class Class1 await TestChangeNamespaceAsync(code, expectedSourceOriginal); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/34707")] + public async Task ChangeFromGlobalNamespace_DoNotSimplifyUnrelatedCode() + { + var defaultNamespace = "A"; + var (folder, filePath) = CreateDocumentFilePath(["B", "C"], "File1.cs"); + var documentPath2 = CreateDocumentFilePath([], "File2.cs"); + var code = + $$""" + + + + class [||]Class1 + { + private A.Class2 c2; + private A.B.Class3 c3; + private A.B.C.Class4 c4; + + void M1() + { + int i = 0; + // This cast should not be touched. + int j = (int)i; + } + } + + + namespace A + { + class Class2{} + + namespace B + { + class Class3 {} + + namespace C + { + class Class4 {} + } + } + } + + + """; + + var expectedSourceOriginal = + """ + namespace A.B.C + { + class Class1 + { + private Class2 c2; + private Class3 c3; + private Class4 c4; + + void M1() + { + int i = 0; + // This cast should not be touched. + int j = (int)i; + } + } + } + """; + await TestChangeNamespaceAsync(code, expectedSourceOriginal); + } + [Fact] public async Task ChangeFromGlobalNamespace_ChangeUsingsInMultipleContainers() { @@ -2169,7 +2235,7 @@ namespace {{defaultNamespace}} { public static class Extensions { - public static bool Foo(this string s) => true; + public static bool Foo(this String s) => true; } } """; diff --git a/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ArgumentProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ArgumentProviderOrderTests.cs index ed9138e9ab93c..1c88cbb5d29e2 100644 --- a/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ArgumentProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ArgumentProviderOrderTests.cs @@ -24,7 +24,7 @@ public sealed class ArgumentProviderOrderTests [Fact] public void TestArgumentProviderOrder() { - var exportProvider = EditorTestCompositions.EditorFeaturesWpf.ExportProviderFactory.CreateExportProvider(); + var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); var argumentProviderExports = exportProvider.GetExports(); var orderedCSharpArgumentProviders = ExtensionOrderer.Order(argumentProviderExports.Where(export => export.Metadata.Language == LanguageNames.CSharp)); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs index ba043e61da81b..2c6ac59465094 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs @@ -25,7 +25,7 @@ public sealed class CompletionProviderOrderTests [Fact] public void TestCompletionProviderOrder() { - var exportProvider = EditorTestCompositions.EditorFeaturesWpf.ExportProviderFactory.CreateExportProvider(); + var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); var completionProviderExports = exportProvider.GetExports(); var orderedCSharpCompletionProviders = ExtensionOrderer.Order(completionProviderExports.Where(export => export.Metadata.Language == LanguageNames.CSharp)); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs index 7ccbef1c21a8a..3ab99fbf2377d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs @@ -90,34 +90,35 @@ private static string GetMarkup(string current, string referenced, ReferenceType public static IEnumerable BuiltInTypesWithReferenceTypeData => CombineWithReferenceTypeData(BuiltInTypes); - [MemberData(nameof(BuiltInTypesWithReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(BuiltInTypesWithReferenceTypeData))] public async Task TestPredefinedType(string type1, string type2, ReferenceType refType) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod(this {type1} x) - => true; - }} -}}"; - var file2 = $@" -using System; + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod(this {{type1}} x) + => true; + } + } + """; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M({type2} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{type2}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, refType); @@ -128,8 +129,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration(ReferenceType refType) { var file1 = """ @@ -168,8 +168,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration_PrimitiveType(ReferenceType refType) { var file1 = """ @@ -208,8 +207,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration_RegularType(ReferenceType refType) { var file1 = """ @@ -248,8 +246,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration_GenericType(ReferenceType refType) { var file1 = """ @@ -288,8 +285,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration_RegularTypeWithSameSimpleName(ReferenceType refType) { var file1 = """ @@ -327,8 +323,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInDeclaration_Namespace(ReferenceType refType) { var file1 = """ @@ -368,8 +363,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UsingAliasInUsage(ReferenceType refType) { var file1 = """ @@ -408,23 +402,23 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(AllTypeKindsWithReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(AllTypeKindsWithReferenceTypeData))] public async Task RegularType(string typeKind, ReferenceType refType) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -public {typeKind} MyType {{ }} + public {{typeKind}} MyType { } -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod(this MyType t) - => true; - }} -}}"; + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod(this MyType t) + => true; + } + } + """; var file2 = """ using System; @@ -447,23 +441,23 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(AllTypeKindsWithReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(AllTypeKindsWithReferenceTypeData))] public async Task ObjectType(string typeKind, ReferenceType refType) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -public {typeKind} MyType {{ }} + public {{typeKind}} MyType { } -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod(this object t) - => true; - }} -}}"; + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod(this object t) + => true; + } + } + """; var file2 = """ using System; @@ -494,34 +488,35 @@ await VerifyImportItemExistsAsync( "(string a, string b)" }).Select(tuple => new List() { tuple })); - [MemberData(nameof(TupleWithRefTypeData))] - [Theory] + [Theory, MemberData(nameof(TupleWithRefTypeData))] public async Task ValueTupleType(string tupleType, ReferenceType refType) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod(this {tupleType} t) - => true; - }} -}}"; - var file2 = $@" -using System; + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod(this {{tupleType}} t) + => true; + } + } + """; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M({tupleType} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{tupleType}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, refType); await VerifyImportItemExistsAsync( markup, @@ -533,25 +528,25 @@ await VerifyImportItemExistsAsync( public static IEnumerable DerivableTypeKindsWithReferenceTypeData => CombineWithReferenceTypeData((new[] { "class", "interface", "abstract class" }).Select(kind => new List() { kind })); - [MemberData(nameof(DerivableTypeKindsWithReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(DerivableTypeKindsWithReferenceTypeData))] public async Task RegularTypeAsBase(string baseType, ReferenceType refType) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -public {baseType} MyBase {{ }} + public {{baseType}} MyBase { } -public class MyType : MyBase {{ }} + public class MyType : MyBase { } -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod(this MyBase t) - => true; - }} -}}"; + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod(this MyBase t) + => true; + } + } + """; var file2 = """ using System; @@ -582,8 +577,7 @@ await VerifyImportItemExistsAsync( "string[]" }).Select(tuple => new List() { tuple })); - [MemberData(nameof(BounedGenericTypeWithRefTypeData))] - [Theory] + [Theory, MemberData(nameof(BounedGenericTypeWithRefTypeData))] public async Task BoundedGenericType(string type, ReferenceType refType) { var file1 = """ @@ -599,20 +593,21 @@ public static bool ExtentionMethod(this IEnumerable t) } } """; - var file2 = $@" -using System; -using System.Collections.Generic; + var file2 = $$""" + using System; + using System.Collections.Generic; -namespace Baz -{{ - public class Bat - {{ - public void M({type} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{type}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, refType); await VerifyImportItemExistsAsync( markup, @@ -629,8 +624,7 @@ await VerifyImportItemExistsAsync( "Bat" }).Select(tuple => new List() { tuple })); - [MemberData(nameof(TypeParameterWithRefTypeData))] - [Theory] + [Theory, MemberData(nameof(TypeParameterWithRefTypeData))] public async Task MatchingTypeParameter(string type, ReferenceType refType) { var file1 = """ @@ -645,22 +639,23 @@ public static bool ExtentionMethod(this T t) } } """; - var file2 = $@" -using System; -using System.Collections.Generic; + var file2 = $$""" + using System; + using System.Collections.Generic; -namespace Baz -{{ - public interface Bar {{}} - - public class Bat - {{ - public void M({type} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public interface Bar {} + + public class Bat + { + public void M({{type}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, refType); await VerifyImportItemExistsAsync( markup, @@ -670,9 +665,9 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } + [Theory] [InlineData(ReferenceType.Project)] [InlineData(ReferenceType.Metadata)] - [Theory] public async Task TestInternalExtensionMethods_NoIVT_InReference(ReferenceType refType) { var file1 = """ @@ -748,9 +743,9 @@ await VerifyImportItemExistsAsync( } // SymbolTreeInfo explicitly ignores non-public types from metadata(likely for perf reasons). So we don't need to test internals in PE reference + [Theory] [InlineData(ReferenceType.None)] [InlineData(ReferenceType.Project)] - [Theory] public async Task TestInternalExtensionMethods_WithIVT(ReferenceType refType) { var file1 = """ @@ -786,8 +781,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } - [MemberData(nameof(ReferenceTypeData))] - [Theory] + [Theory, MemberData(nameof(ReferenceTypeData))] public async Task UserDefinedGenericType(ReferenceType refType) { var file1 = """ @@ -846,19 +840,20 @@ public static bool ExtentionMethod(this int x) } } """; - var file2 = $@" -using System; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M() - {{ - {expression}.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M() + { + {{expression}}.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, ReferenceType.None); await VerifyImportItemExistsAsync( @@ -897,35 +892,36 @@ public static IEnumerable VBBuiltInTypes } } - [MemberData(nameof(VBBuiltInTypes))] - [Theory] + [Theory, MemberData(nameof(VBBuiltInTypes))] public async Task ExtensionMethodDelcaredInVBSource(string vbType, string csType) { - var file1 = $@" -Imports System -Imports System.Runtime.CompilerServices - -Namespace NS - Public Module Foo - - public Function ExtentionMethod(x As {vbType}) As Boolean - Return True - End Function - End Module -End Namespace"; - var file2 = $@" -using System; + var file1 = $""" + Imports System + Imports System.Runtime.CompilerServices + + Namespace NS + Public Module Foo + + public Function ExtentionMethod(x As {vbType}) As Boolean + Return True + End Function + End Module + End Namespace + """; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M({csType} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{csType}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, ReferenceType.Project, currentLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.VisualBasic); await VerifyImportItemExistsAsync( @@ -1047,9 +1043,9 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } + [Theory] [InlineData("int", "Int32Method", "Foo")] [InlineData("string", "StringMethod", "Bar")] - [Theory] public async Task TestIdenticalAliases(string type, string expectedMethodname, string expectedNamespace) { var file1 = """ @@ -1075,19 +1071,20 @@ internal static bool StringMethod(this X x) } } """; - var file2 = $@" -using System; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M({type} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{type}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, ReferenceType.None); await VerifyImportItemExistsAsync( @@ -1097,9 +1094,9 @@ await VerifyImportItemExistsAsync( inlineDescription: expectedNamespace); } + [Theory] [InlineData("int")] [InlineData("Exception")] - [Theory] public async Task TestIdenticalMethodName(string type) { var file1 = """ @@ -1117,19 +1114,20 @@ public static bool ExtMethod(this Exception x) } } """; - var file2 = $@" -using System; + var file2 = $$""" + using System; -namespace Baz -{{ - public class Bat - {{ - public void M({type} x) - {{ - x.$$ - }} - }} -}}"; + namespace Baz + { + public class Bat + { + public void M({{type}} x) + { + x.$$ + } + } + } + """; var markup = GetMarkup(file2, file1, ReferenceType.None); await VerifyImportItemExistsAsync( @@ -1235,17 +1233,18 @@ await VerifyImportItemExistsAsync( [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/42325")] public async Task TestExtensionMethodsInConflictingTypes(ReferenceType refType, string accessibility) { - var refDoc = $@" -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")] - -namespace Foo -{{ - {accessibility} static class ExtensionClass - {{ - public static bool ExtentionMethod1(this int x) - => true; - }} -}}"; + var refDoc = $$""" + [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")] + + namespace Foo + { + {{accessibility}} static class ExtensionClass + { + public static bool ExtentionMethod1(this int x) + => true; + } + } + """; var srcDoc = """ using System; @@ -1347,38 +1346,40 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } + [Theory] [InlineData("", "", false)] [InlineData("", "public", true)] [InlineData("public", "", false)] - [Theory] public async Task TestCSharpDefaultAccessibility(string containerAccessibility, string methodAccessibility, bool isAvailable) { - var file1 = $@" -using System; + var file1 = $$""" + using System; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")] - -namespace Foo -{{ - {containerAccessibility} static class ExtensionClass - {{ - {methodAccessibility} static bool ExtentionMethod(this int x) - => true; - }} -}}"; - var file2 = $@" -using System; + [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")] -namespace Baz -{{ - public class Bat - {{ - public void M(int x) - {{ - x.$$ - }} - }} -}}"; + namespace Foo + { + {{containerAccessibility}} static class ExtensionClass + { + {{methodAccessibility}} static bool ExtentionMethod(this int x) + => true; + } + } + """; + var file2 = $$""" + using System; + + namespace Baz + { + public class Bat + { + public void M(int x) + { + x.$$ + } + } + } + """; var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference: true); @@ -1399,6 +1400,7 @@ await VerifyImportItemIsAbsentAsync( } } + [Theory] [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")] [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")] [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")] @@ -1407,43 +1409,44 @@ await VerifyImportItemIsAbsentAsync( [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")] [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")] [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")] - [Theory] public async Task TestExtensionMethodsForSimpleArrayType(ReferenceType refType, string rank, string expectedName) { - var refDoc = $@" -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")] - -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod1(this int x) - => true; - - public static bool ExtentionMethod2(this int[] x) - => true; - - public static bool ExtentionMethod3(this int[][] x) - => true; - - public static bool ExtentionMethod4(this int[,] x) - => true; - - public static bool ExtentionMethod5(this int[][,] x) - => true; - }} -}}"; - var srcDoc = $@" -namespace Baz -{{ - public class Bat - {{ - public void M(int{rank} x) - {{ - x.$$ - }} - }} -}}"; + var refDoc = $$""" + [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")] + + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod1(this int x) + => true; + + public static bool ExtentionMethod2(this int[] x) + => true; + + public static bool ExtentionMethod3(this int[][] x) + => true; + + public static bool ExtentionMethod4(this int[,] x) + => true; + + public static bool ExtentionMethod5(this int[][,] x) + => true; + } + } + """; + var srcDoc = $$""" + namespace Baz + { + public class Bat + { + public void M(int{{rank}} x) + { + x.$$ + } + } + } + """; var markup = refType switch { @@ -1459,6 +1462,7 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } + [Theory] [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")] [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")] [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")] @@ -1467,43 +1471,44 @@ await VerifyImportItemExistsAsync( [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")] [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")] [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")] - [Theory] public async Task TestExtensionMethodsForGenericArrayType(ReferenceType refType, string rank, string expectedName) { - var refDoc = $@" -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")] - -namespace Foo -{{ - public static class ExtensionClass - {{ - public static bool ExtentionMethod1(this T x) - => true; - - public static bool ExtentionMethod2(this T[] x) - => true; - - public static bool ExtentionMethod3(this T[][] x) - => true; - - public static bool ExtentionMethod4(this T[,] x) - => true; - - public static bool ExtentionMethod5(this T[][,] x) - => true; - }} -}}"; - var srcDoc = $@" -namespace Baz -{{ - public class Bat - {{ - public void M(int{rank} x) - {{ - x.$$ - }} - }} -}}"; + var refDoc = $$""" + [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")] + + namespace Foo + { + public static class ExtensionClass + { + public static bool ExtentionMethod1(this T x) + => true; + + public static bool ExtentionMethod2(this T[] x) + => true; + + public static bool ExtentionMethod3(this T[][] x) + => true; + + public static bool ExtentionMethod4(this T[,] x) + => true; + + public static bool ExtentionMethod5(this T[][,] x) + => true; + } + } + """; + var srcDoc = $$""" + namespace Baz + { + public class Bat + { + public void M(int{{rank}} x) + { + x.$$ + } + } + } + """; var markup = refType switch { @@ -1520,9 +1525,9 @@ await VerifyImportItemExistsAsync( inlineDescription: "Foo"); } + [Theory] [InlineData(ReferenceType.Project)] [InlineData(ReferenceType.Metadata)] - [Theory] public async Task TestGenericReceiverTypeWithConstraint(ReferenceType refType) { var refDoc = """ @@ -1568,34 +1573,36 @@ await VerifyImportItemExistsAsync( inlineDescription: "NS2"); } + [Theory] [InlineData(ReferenceType.Project, "(int,int)")] [InlineData(ReferenceType.Project, "(int,int,int,int,int,int,int,int,int,int)")] // more than 8 tuple elements [InlineData(ReferenceType.Metadata, "(int,int)")] [InlineData(ReferenceType.Metadata, "(int,int,int,int,int,int,int,int,int,int)")] // more than 8 tuple elements - [Theory] public async Task TestTupleArray(ReferenceType refType, string tupleType) { - var refDoc = $@" -using System; + var refDoc = $$""" + using System; -namespace NS2 -{{ - public static class Extensions - {{ - public static bool ExtentionMethod(this {tupleType}[] x) => false; - }} -}}"; - var srcDoc = $@" -namespace NS1 -{{ - public class C - {{ - public void M({tupleType}[] x) - {{ - x.$$ - }} - }} -}}"; + namespace NS2 + { + public static class Extensions + { + public static bool ExtentionMethod(this {{tupleType}}[] x) => false; + } + } + """; + var srcDoc = $$""" + namespace NS1 + { + public class C + { + public void M({{tupleType}}[] x) + { + x.$$ + } + } + } + """; var markup = refType switch { @@ -1611,34 +1618,36 @@ await VerifyImportItemExistsAsync( inlineDescription: "NS2"); } + [Theory] [InlineData(ReferenceType.Project, "(int[],int[])")] [InlineData(ReferenceType.Project, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements [InlineData(ReferenceType.Metadata, "(int[],int[])")] [InlineData(ReferenceType.Metadata, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements - [Theory] public async Task TestArrayTuple(ReferenceType refType, string tupleType) { - var refDoc = $@" -using System; + var refDoc = $$""" + using System; -namespace NS2 -{{ - public static class Extensions - {{ - public static bool ExtentionMethod(this {tupleType} x) => false; - }} -}}"; - var srcDoc = $@" -namespace NS1 -{{ - public class C - {{ - public void M({tupleType} x) - {{ - x.$$ - }} - }} -}}"; + namespace NS2 + { + public static class Extensions + { + public static bool ExtentionMethod(this {{tupleType}} x) => false; + } + } + """; + var srcDoc = $$""" + namespace NS1 + { + public class C + { + public void M({{tupleType}} x) + { + x.$$ + } + } + } + """; var markup = refType switch { @@ -1654,9 +1663,9 @@ await VerifyImportItemExistsAsync( inlineDescription: "NS2"); } + [Theory] [InlineData(ReferenceType.Project)] [InlineData(ReferenceType.Metadata)] - [Theory] public async Task TestDescriptionOfGenericReceiverType(ReferenceType refType) { var refDoc = """ @@ -1699,9 +1708,9 @@ await VerifyImportItemExistsAsync( expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod()"); } + [Theory] [InlineData(ReferenceType.Project)] [InlineData(ReferenceType.Metadata)] - [Theory] public async Task TestDescriptionOfOverloads(ReferenceType refType) { var refDoc = """ @@ -1980,10 +1989,10 @@ public void M() await VerifyProviderCommitAsync(markup, "ToInt", expected, commitChar: commitChar, sourceCodeKind: SourceCodeKind.Regular); } + [Theory] [InlineData("int", true, "int a")] [InlineData("int[]", true, "int a, int b")] [InlineData("bool", false, null)] - [Theory] public async Task TestTargetTypedCompletion(string targetType, bool matchTargetType, string expectedParameterList) { var refDoc = """ @@ -1999,17 +2008,18 @@ public static class Extensions } } """; - var srcDoc = $@" -namespace NS1 -{{ - public class C - {{ - public void M(int x) - {{ - {targetType} y = x.$$ - }} - }} -}}"; + var srcDoc = $$""" + namespace NS1 + { + public class C + { + public void M(int x) + { + {{targetType}} y = x.$$ + } + } + } + """; ShowTargetTypedCompletionFilter = true; var markup = CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp); @@ -2034,6 +2044,262 @@ await VerifyImportItemExistsAsync( expectedDescriptionOrNull: expectedDescription); } + [Fact] + public async Task TestComplexConstraint_NotShownOnObject() + { + await VerifyItemIsAbsentAsync( + """ + interface I + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : I + { + } + } + } + + void M(object i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnExactInterfaceConstraintMatch() + { + await VerifyItemExistsAsync( + """ + interface I + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : I + { + } + } + } + + void M(I i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnInterfaceMatchThroughChainedTypeParameter() + { + await VerifyItemExistsAsync( + """ + interface I + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : U where U : I + { + } + } + } + + void M(I i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnInterfaceMatchThroughChainedTypeParameter_BaseInterface() + { + await VerifyItemExistsAsync( + """ + interface I1 + { + } + + interface I2 : I1 + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : U where U : I1 + { + } + } + } + + void M(I2 i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnExactBaseTypeConstraintMatch() + { + await VerifyItemExistsAsync( + """ + class C + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : C + { + } + } + } + + void M(C i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnBaseTypeMatchThroughChainedTypeParameter() + { + await VerifyItemExistsAsync( + """ + class C + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : U where U : C + { + } + } + } + + void M(C i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnBaseTypeMatchThroughChainedTypeParameter_InheritedType() + { + await VerifyItemExistsAsync( + """ + class C1 + { + } + + class C2 : C1 + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : U where U : C1 + { + } + } + } + + void M(C2 i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + + [Fact] + public async Task TestComplexConstraint_ShownOnBaseTypeMatchThroughChainedTypeParameter_InheritedBaseTypeAndInterface() + { + await VerifyItemExistsAsync( + """ + interface I1 + { + } + + interface I2 : I1 + { + } + + class C1 : I2 + { + } + + namespace N + { + static class Extensions + { + public static void M(this T t) where T : U where U : I1 + { + } + } + } + + void M(C1 i) + { + i.$$ + } + """, + "M", + displayTextSuffix: "<>", + inlineDescription: "N", + sourceCodeKind: SourceCodeKind.Regular); + } + private Task VerifyImportItemExistsAsync(string markup, string expectedItem, string inlineDescription, Glyph? glyph = null, string displayTextSuffix = null, string expectedDescriptionOrNull = null, List expectedFilters = null) => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, matchingFilters: expectedFilters); diff --git a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs index 59794cea19d87..b0ab041c23b26 100644 --- a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs @@ -18,7 +18,7 @@ public sealed class ConvertNamespaceCommandHandlerTests { internal sealed class ConvertNamespaceTestState : AbstractCommandHandlerTestState { - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf.AddParts( + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures.AddParts( typeof(ConvertNamespaceCommandHandler)); private readonly ConvertNamespaceCommandHandler _commandHandler; diff --git a/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.cs index 460072243cc5d..c479e98c05e96 100644 --- a/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.cs @@ -202,7 +202,7 @@ class C """), workspaceKind: WorkspaceKind.Interactive, - composition: EditorTestCompositions.EditorFeaturesWpf); + composition: EditorTestCompositions.EditorFeatures); // Force initialization. workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id).GetTextView()).ToList(); diff --git a/src/EditorFeatures/CSharpTest/EventHookup/EventHookupTestState.cs b/src/EditorFeatures/CSharpTest/EventHookup/EventHookupTestState.cs index 34ee3662015da..b51f6979de1f1 100644 --- a/src/EditorFeatures/CSharpTest/EventHookup/EventHookupTestState.cs +++ b/src/EditorFeatures/CSharpTest/EventHookup/EventHookupTestState.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EventHookup; internal sealed class EventHookupTestState : AbstractCommandHandlerTestState { - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf.AddParts( + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures.AddParts( typeof(EventHookupCommandHandler), typeof(EventHookupSessionManager)); diff --git a/src/EditorFeatures/CSharpTest/ExtractInterface/ExtractInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ExtractInterface/ExtractInterfaceTests.cs index 64ccb42a46c8b..903381749b89a 100644 --- a/src/EditorFeatures/CSharpTest/ExtractInterface/ExtractInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractInterface/ExtractInterfaceTests.cs @@ -1449,7 +1449,7 @@ public void M() { } """), workspaceKind: WorkspaceKind.Interactive, - composition: EditorTestCompositions.EditorFeaturesWpf); + composition: EditorTestCompositions.EditorFeatures); // Force initialization. workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id)!.GetTextView()).ToList(); diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodMiscellaneousTests.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodMiscellaneousTests.cs index dda79afdf720f..ba33a86b17b1f 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodMiscellaneousTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodMiscellaneousTests.cs @@ -131,7 +131,7 @@ class A private static async Task TestCommandHandler(string markupCode, string? result, bool expectNotification) { - using var workspace = EditorTestWorkspace.CreateCSharp(markupCode, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(markupCode, composition: EditorTestCompositions.EditorFeatures); var testDocument = workspace.Documents.Single(); var view = testDocument.GetTextView(); diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs index 3f0f511800302..a3d115d446fac 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs @@ -11504,7 +11504,7 @@ public void ExtractMethodCommandDisabledInSubmission() """), workspaceKind: WorkspaceKind.Interactive, - composition: EditorTestCompositions.EditorFeaturesWpf); + composition: EditorTestCompositions.EditorFeatures); // Force initialization. workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id)!.GetTextView()).ToList(); diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index cf230bd99c096..205244c113f71 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -782,7 +782,7 @@ private static async Task TestThirdPartyCodeFixer(string co where TCodefix : CodeFixProvider, new() { - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf.AddParts(typeof(TCodefix))); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures.AddParts(typeof(TCodefix))); var project = workspace.CurrentSolution.Projects.Single(); var analyzer = (DiagnosticAnalyzer)new TAnalyzer(); @@ -833,12 +833,12 @@ static EditorTestWorkspace GetTestWorkspaceForLanguage(string language) { if (language == LanguageNames.CSharp) { - return EditorTestWorkspace.CreateCSharp(string.Empty, composition: EditorTestCompositions.EditorFeaturesWpf); + return EditorTestWorkspace.CreateCSharp(string.Empty, composition: EditorTestCompositions.EditorFeatures); } if (language == LanguageNames.VisualBasic) { - return EditorTestWorkspace.CreateVisualBasic(string.Empty, composition: EditorTestCompositions.EditorFeaturesWpf); + return EditorTestWorkspace.CreateVisualBasic(string.Empty, composition: EditorTestCompositions.EditorFeatures); } return null; @@ -871,7 +871,7 @@ private static Task AssertCodeCleanupResult(string expected, string code, bool s /// The to test code cleanup. private static async Task AssertCodeCleanupResult(string expected, string code, CodeStyleOption2 preferredImportPlacement, bool systemUsingsFirst = true, bool separateUsingGroups = false, Func enabledFixIdsFilter = null, (string, DiagnosticSeverity)[] diagnosticIdsWithSeverity = null) { - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); // must set global options since incremental analyzer infra reads from global options workspace.SetAnalyzerFallbackAndGlobalOptions(new OptionsCollection(LanguageNames.CSharp) diff --git a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs index b2461c98ba239..9dee661af6a6b 100644 --- a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs +++ b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs @@ -1175,7 +1175,7 @@ class C """), workspaceKind: WorkspaceKind.Interactive, - composition: EditorTestCompositions.EditorFeaturesWpf); + composition: EditorTestCompositions.EditorFeatures); // Force initialization. workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id).GetTextView()).ToList(); diff --git a/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs index 03ddbbb1a3364..4f82584548cb5 100644 --- a/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs @@ -19,7 +19,7 @@ public sealed class RawStringLiteralCommandHandlerTests { internal sealed class RawStringLiteralTestState : AbstractCommandHandlerTestState { - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf.AddParts( + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures.AddParts( typeof(RawStringLiteralCommandHandler)); private readonly RawStringLiteralCommandHandler _commandHandler; diff --git a/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs b/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs index 33f249ab6ba5a..786fc6e2ade5e 100644 --- a/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs +++ b/src/EditorFeatures/CSharpTest/RefactoringHelpers/RefactoringHelpersTests.cs @@ -1722,6 +1722,32 @@ void Goo() """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78749")] + public async Task TestMalformedIfBlock1() + { + await TestAsync( + """ + { + {|result:[||]if (devsBad) + [crash] + else return;|} + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78749")] + public async Task TestMalformedIfBlock2() + { + await TestAsync( + """ + { + if (devsBad) + {|result:[|[crash] + else return;|]|} + } + """); + } + #endregion #region Test Deep in expression diff --git a/src/EditorFeatures/CSharpTest/StringCopyPaste/StringCopyPasteCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/StringCopyPaste/StringCopyPasteCommandHandlerTests.cs index faec3d3367b79..6e2d7270c8d7d 100644 --- a/src/EditorFeatures/CSharpTest/StringCopyPaste/StringCopyPasteCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/StringCopyPaste/StringCopyPasteCommandHandlerTests.cs @@ -29,11 +29,11 @@ public abstract class StringCopyPasteCommandHandlerTests internal sealed class StringCopyPasteTestState : AbstractCommandHandlerTestState { private static readonly TestComposition s_composition = - EditorTestCompositions.EditorFeaturesWpf + EditorTestCompositions.EditorFeatures .AddParts(typeof(StringCopyPasteCommandHandler)); private static readonly TestComposition s_compositionWithMockCopyPasteService = - EditorTestCompositions.EditorFeaturesWpf + EditorTestCompositions.EditorFeatures .RemoveExcludedPartTypes(typeof(WpfStringCopyPasteService)) .AddParts(typeof(TestStringCopyPasteService)) .AddParts(typeof(StringCopyPasteCommandHandler)); diff --git a/src/EditorFeatures/Core/Classification/Semantic/EmbeddedLanguageClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Classification/Semantic/EmbeddedLanguageClassificationViewTaggerProvider.cs index 7cfb9bbe398ef..fa3744a407cbf 100644 --- a/src/EditorFeatures/Core/Classification/Semantic/EmbeddedLanguageClassificationViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Classification/Semantic/EmbeddedLanguageClassificationViewTaggerProvider.cs @@ -14,6 +14,4 @@ namespace Microsoft.CodeAnalysis.Classification; /// internal sealed class EmbeddedLanguageClassificationViewTaggerProvider( TaggerHost taggerHost, ClassificationTypeMap typeMap) - : AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(taggerHost, typeMap, ClassificationType.EmbeddedLanguage) -{ -} + : AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(taggerHost, typeMap, ClassificationType.EmbeddedLanguage); diff --git a/src/EditorFeatures/Core/Classification/Semantic/SemanticClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Classification/Semantic/SemanticClassificationViewTaggerProvider.cs index 4a0628e8c4809..af945be50612c 100644 --- a/src/EditorFeatures/Core/Classification/Semantic/SemanticClassificationViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Classification/Semantic/SemanticClassificationViewTaggerProvider.cs @@ -13,6 +13,4 @@ namespace Microsoft.CodeAnalysis.Classification; /// exported. It is consumed by the instead. /// internal sealed class SemanticClassificationViewTaggerProvider(TaggerHost taggerHost, ClassificationTypeMap typeMap) - : AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(taggerHost, typeMap, ClassificationType.Semantic) -{ -} + : AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(taggerHost, typeMap, ClassificationType.Semantic); diff --git a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs index c3200dd73944b..f3d09ca34bf1f 100644 --- a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -9,10 +9,8 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Tagging; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.CodeAnalysis.Threading; @@ -44,8 +42,8 @@ private sealed record CachedServices( private static readonly object s_uniqueKey = new(); private readonly SyntacticClassificationTaggerProvider _taggerProvider; - private readonly ITextBuffer2 _subjectBuffer; - private readonly WorkspaceRegistration _workspaceRegistration; + private readonly ITextBuffer _subjectBuffer; + private readonly ITaggerEventSource _taggerEventSource; private readonly CancellationTokenSource _disposalCancellationSource = new(); @@ -61,10 +59,6 @@ private sealed record CachedServices( /// private readonly TimeSpan _diffTimeout; - private Workspace? _workspace; - private WorkspaceEventRegistration? _workspaceChangedDisposer; - private WorkspaceEventRegistration? _workspaceDocumentActiveContextChangedDisposer; - /// /// Cached values for the last services we computed for a particular and . These rarely change, and are expensive enough to show up in very hot scenarios (like @@ -89,12 +83,11 @@ private sealed record CachedServices( /// again and again to find exactly same answer /// private readonly ClassifiedLineCache _lineCache; - private int _taggerReferenceCount; public TagComputer( SyntacticClassificationTaggerProvider taggerProvider, - ITextBuffer2 subjectBuffer, + ITextBuffer subjectBuffer, TimeSpan diffTimeout) { _taggerProvider = taggerProvider; @@ -109,13 +102,19 @@ public TagComputer( _lineCache = new ClassifiedLineCache(taggerProvider.ThreadingContext); - _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); - _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged; + _taggerEventSource = TaggerEventSources.Compose( + TaggerEventSources.OnWorkspaceChanged(subjectBuffer, taggerProvider._listener), + TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer), + TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), + TaggerEventSources.OnTextChanged(subjectBuffer), + TaggerEventSources.OnParseOptionChanged(subjectBuffer)); + + _taggerEventSource.Connect(); - if (_workspaceRegistration.Workspace != null) - ConnectToWorkspace(_workspaceRegistration.Workspace); + _taggerEventSource.Changed += OnEventSourceChanged; - _subjectBuffer.ChangedOnBackground += this.OnSubjectBufferChanged; + // Kick off work to classify the current snapshot of the subject buffer. + _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); } public static TagComputer GetOrCreate( @@ -130,14 +129,13 @@ public static TagComputer GetOrCreate( return tagComputer; } - private void DisconnectTagComputer() - => _subjectBuffer.Properties.RemoveProperty(s_uniqueKey); - public event EventHandler? TagsChanged; private (SolutionServices solutionServices, IClassificationService classificationService)? TryGetClassificationService(ITextSnapshot snapshot) { - var workspace = _workspace; + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + var workspace = document?.Project.Solution.Workspace; + var contentType = snapshot.ContentType; var lastCachedServices = _lastCachedServices; @@ -158,45 +156,6 @@ private void DisconnectTagComputer() return (lastCachedServices.SolutionServices, lastCachedServices.ClassificationService); } - #region Workspace Hookup - - private void OnWorkspaceRegistrationChanged(object? sender, EventArgs e) - { - var token = _taggerProvider._listener.BeginAsyncOperation(nameof(OnWorkspaceRegistrationChanged)); - var task = SwitchToMainThreadAndHookupWorkspaceAsync(); - task.CompletesAsyncOperation(token); - } - - private async Task SwitchToMainThreadAndHookupWorkspaceAsync() - { - try - { - await _taggerProvider.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_disposalCancellationSource.Token); - - // We both try to connect synchronously, and register for workspace registration events. - // It's possible (particularly in tests), to connect in the startup path, but then get a - // previously scheduled, but not yet delivered event. Don't bother connecting to the - // same workspace again in that case. - var newWorkspace = _workspaceRegistration.Workspace; - if (newWorkspace == _workspace) - return; - - DisconnectFromWorkspace(); - - if (newWorkspace != null) - ConnectToWorkspace(newWorkspace); - } - catch (OperationCanceledException) - { - // can happen if we were disposed of. - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - // We were in a fire and forget task. So just report the NFW and do not let - // this bleed out to an unhandled exception. - } - } - internal void IncrementReferenceCount() { _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); @@ -213,103 +172,16 @@ internal void DecrementReferenceCount() // stop any bg work we're doing. _disposalCancellationSource.Cancel(); - _subjectBuffer.ChangedOnBackground -= this.OnSubjectBufferChanged; + _taggerEventSource.Changed -= OnEventSourceChanged; + _taggerEventSource.Disconnect(); - DisconnectFromWorkspace(); - _workspaceRegistration.WorkspaceChanged -= OnWorkspaceRegistrationChanged; - DisconnectTagComputer(); + _subjectBuffer.Properties.RemoveProperty(s_uniqueKey); + _lastCachedServices = null; } } - private void ConnectToWorkspace(Workspace workspace) - { - _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); - - _workspace = workspace; - _workspaceChangedDisposer = _workspace.RegisterWorkspaceChangedHandler(this.OnWorkspaceChanged); - _workspaceDocumentActiveContextChangedDisposer = _workspace.RegisterDocumentActiveContextChangedHandler(this.OnDocumentActiveContextChanged); - - // Now that we've connected to the workspace, kick off work to reclassify this buffer. - _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); - } - - public void DisconnectFromWorkspace() - { - _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); - _lastCachedServices = null; - - lock (_gate) - { - _lastProcessedData = null; - } - - if (_workspace != null) - { - _workspaceChangedDisposer?.Dispose(); - _workspaceChangedDisposer = null; - - _workspaceDocumentActiveContextChangedDisposer?.Dispose(); - _workspaceDocumentActiveContextChangedDisposer = null; - - _workspace = null; - - // Now that we've disconnected to the workspace, kick off work to reclassify this buffer. - _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); - } - } - - #endregion - - #region Event Handling - - private void OnSubjectBufferChanged(object? sender, TextContentChangedEventArgs args) - { - // we know a change to our buffer is always affecting this document. So we can - // just enqueue the work to reclassify things unilaterally. - _workQueue.AddWork(args.After); - } - - private void OnDocumentActiveContextChanged(DocumentActiveContextChangedEventArgs args) - { - if (_workspace == null) - return; - - var documentId = args.NewActiveContextDocumentId; - var bufferDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (bufferDocumentId != documentId) - return; - - _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); - } - - private void OnWorkspaceChanged(WorkspaceChangeEventArgs args) - { - // We may be getting an event for a workspace we already disconnected from. If so, - // ignore them. We won't be able to find the Document corresponding to our text buffer, - // so we can't reasonably classify this anyways. - var workspace = _workspace; - if (args.NewSolution.Workspace != workspace) - return; - - if (args.Kind != WorkspaceChangeKind.ProjectChanged) - return; - - var documentId = workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); - if (args.ProjectId != documentId?.ProjectId) - return; - - var oldProject = args.OldSolution.GetProject(args.ProjectId); - var newProject = args.NewSolution.GetProject(args.ProjectId); - - // In case of parse options change reclassify the doc as it may have affected things - // like preprocessor directives. - if (Equals(oldProject?.ParseOptions, newProject?.ParseOptions)) - return; - - _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); - } - - #endregion + private void OnEventSourceChanged(object? sender, EventArgs e) + => _workQueue.AddWork(_subjectBuffer.CurrentSnapshot); /// /// Parses the document in the background and determines what has changed to report to @@ -416,7 +288,7 @@ public void AddTags(NormalizedSnapshotSpanCollection spans, SegmentedList> tags) { _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); - if (spans.Count == 0 || _workspace == null) + if (spans.Count == 0) return; var snapshot = spans[0].Snapshot; diff --git a/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs b/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs index 76aa982ff92df..5118e11a7d526 100644 --- a/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs +++ b/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs @@ -41,7 +41,7 @@ public static ManagedHotReloadUpdate FromContract(this InternalContracts.Managed exceptionRegions: update.ExceptionRegions.SelectAsArray(FromContract)); public static ManagedHotReloadUpdates FromContract(this InternalContracts.ManagedHotReloadUpdates updates) - => new(updates.Updates.FromContract(), updates.Diagnostics.FromContract()); + => new(updates.Updates.FromContract(), updates.Diagnostics.FromContract(), updates.ProjectsToRebuild, updates.ProjectsToRestart); public static ImmutableArray FromContract(this ImmutableArray diagnostics) => diagnostics.SelectAsArray(FromContract); diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs index 4e8dc859383f2..8ba83cfca7f08 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -398,26 +398,26 @@ public async ValueTask GetUpdatesAsync(ImmutableArray? deletedDocumentRudeEdits = null; - foreach (var rudeEdit in result.RudeEdits) + ArrayBuilder? applyChangesDiagnostics = null; + foreach (var diagnostic in result.Diagnostics) { - if (await solution.GetDocumentAsync(rudeEdit.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false) == null) + // Report warnings and errors that are not reported when analyzing documents or are reported for deleted documents. + + if (diagnostic.Severity is not (DiagnosticSeverity.Error or DiagnosticSeverity.Warning)) { - deletedDocumentRudeEdits ??= ArrayBuilder.GetInstance(); - deletedDocumentRudeEdits.Add(rudeEdit); + continue; } - } - if (deletedDocumentRudeEdits != null) - { - deletedDocumentRudeEdits.AddRange(result.Diagnostics); - UpdateApplyChangesDiagnostics(deletedDocumentRudeEdits.ToImmutableAndFree()); - } - else - { - UpdateApplyChangesDiagnostics(result.Diagnostics); + if ((!EditAndContinueDiagnosticDescriptors.IsRudeEdit(diagnostic.Id)) || + await solution.GetDocumentAsync(diagnostic.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false) == null) + { + applyChangesDiagnostics ??= ArrayBuilder.GetInstance(); + applyChangesDiagnostics.Add(diagnostic); + } } + UpdateApplyChangesDiagnostics(applyChangesDiagnostics.ToImmutableOrEmptyAndFree()); + return new ManagedHotReloadUpdates( result.ModuleUpdates.Updates.FromContract(), result.GetAllDiagnostics().FromContract(), diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptAsynchronousTaggerProvider.cs index c1ae35460bc68..29de0595ff23e 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptAsynchronousTaggerProvider.cs @@ -3,12 +3,18 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis.Editor.Tagging; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +// TODO: remove internal abstract class VSTypeScriptAsynchronousTaggerProvider : AsynchronousViewTaggerProvider where TTag : ITag { @@ -23,3 +29,30 @@ protected VSTypeScriptAsynchronousTaggerProvider(VSTypeScriptTaggerHost taggerHo { } } + +internal abstract class VSTypeScriptAsynchronousTaggerProvider2 : AsynchronousViewTaggerProvider + where TTag : ITag +{ + protected VSTypeScriptAsynchronousTaggerProvider2(VSTypeScriptTaggerHost taggerHost) + : base(taggerHost.UnderlyingObject, FeatureAttribute.Classification) + { + } + + protected sealed override bool TryAddSpansToTag(ITextView? textView, ITextBuffer subjectBuffer, ref TemporaryArray result) + { + using var _ = ArrayBuilder.GetInstance(out var builder); + if (TryAddSpansToTagImpl(textView, subjectBuffer, builder)) + { + foreach (var item in builder) + { + result.Add(item); + } + + return true; + } + + return false; + } + + protected abstract bool TryAddSpansToTagImpl(ITextView? textView, ITextBuffer subjectBuffer, ICollection result); +} diff --git a/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs b/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs deleted file mode 100644 index 04ad6f0cb961c..0000000000000 --- a/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs +++ /dev/null @@ -1,159 +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 System; -using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Utilities; - -namespace Microsoft.CodeAnalysis.FindReferences; - -[Export(typeof(ICommandHandler))] -[ContentType(ContentTypeNames.RoslynContentType)] -[Name(PredefinedCommandHandlerNames.FindReferences)] -internal sealed class FindReferencesCommandHandler : ICommandHandler -{ - private readonly IStreamingFindUsagesPresenter _streamingPresenter; - private readonly IGlobalOptionService _globalOptions; - private readonly IAsynchronousOperationListener _asyncListener; - - public string DisplayName => EditorFeaturesResources.Find_References; - - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public FindReferencesCommandHandler( - IStreamingFindUsagesPresenter streamingPresenter, - IGlobalOptionService globalOptions, - IAsynchronousOperationListenerProvider listenerProvider) - { - Contract.ThrowIfNull(listenerProvider); - - _streamingPresenter = streamingPresenter; - _globalOptions = globalOptions; - _asyncListener = listenerProvider.GetListener(FeatureAttribute.FindReferences); - } - - public CommandState GetCommandState(FindReferencesCommandArgs args) - { - var (_, service) = GetDocumentAndService(args.SubjectBuffer.CurrentSnapshot); - return service != null - ? CommandState.Available - : CommandState.Unspecified; - } - - public bool ExecuteCommand(FindReferencesCommandArgs args, CommandExecutionContext context) - { - var subjectBuffer = args.SubjectBuffer; - - // Get the selection that user has in our buffer (this also works if there - // is no selection and the caret is just at a single position). If we - // can't get the selection, or there are multiple spans for it (i.e. a - // box selection), then don't do anything. - var snapshotSpans = args.TextView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer); - if (snapshotSpans.Count == 1) - { - var selectedSpan = snapshotSpans[0]; - var (document, service) = GetDocumentAndService(subjectBuffer.CurrentSnapshot); - if (document != null) - { - // Do a find-refs at the *start* of the selection. That way if the - // user has selected a symbol that has another symbol touching it - // on the right (i.e. Goo++ ), then we'll do the find-refs on the - // symbol selected, not the symbol following. - if (TryExecuteCommand(selectedSpan.Start, document, service)) - { - return true; - } - } - } - - return false; - } - - private static (Document, IFindUsagesService) GetDocumentAndService(ITextSnapshot snapshot) - { - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - return (document, document?.GetLanguageService()); - } - - private bool TryExecuteCommand(int caretPosition, Document document, IFindUsagesService findUsagesService) - { - // See if we're running on a host that can provide streaming results. - // We'll both need a FAR service that can stream results to us, and - // a presenter that can accept streamed results. - if (findUsagesService != null && _streamingPresenter != null) - { - // kick this work off in a fire and forget fashion. Importantly, this means we do - // not pass in any ambient cancellation information as the execution of this command - // will complete and will have no bearing on the computation of the references we compute. - _ = StreamingFindReferencesAsync(document, caretPosition, findUsagesService, _streamingPresenter); - return true; - } - - return false; - } - - private async Task StreamingFindReferencesAsync( - Document document, - int caretPosition, - IFindUsagesService findUsagesService, - IStreamingFindUsagesPresenter presenter) - { - try - { - using var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindReferencesAsync)); - var classificationOptions = _globalOptions.GetClassificationOptionsProvider(); - - // Let the presented know we're starting a search. It will give us back the context object that the FAR - // service will push results into. This operation is not externally cancellable. Instead, the find refs - // window will cancel it if another request is made to use it. - var (context, cancellationToken) = presenter.StartSearch( - EditorFeaturesResources.Find_References, - new StreamingFindUsagesPresenterOptions() - { - SupportsReferences = true, - IncludeContainingTypeAndMemberColumns = document.Project.SupportsCompilation, - IncludeKindColumn = document.Project.Language != LanguageNames.FSharp - }); - - using (Logger.LogBlock( - FunctionId.CommandHandler_FindAllReference, - KeyValueLogMessage.Create(LogType.UserAction, static m => m["type"] = "streaming"), - cancellationToken)) - { - try - { - await findUsagesService.FindReferencesAsync(context, document, caretPosition, classificationOptions, cancellationToken).ConfigureAwait(false); - } - finally - { - await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) - { - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - } - } -} diff --git a/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs index 5855f02feab90..98586744752be 100644 --- a/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs +++ b/src/EditorFeatures/Core/FindUsages/BufferedFindUsagesContext.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -28,6 +29,7 @@ private sealed class State public string? InformationalMessage; public string? SearchTitle; public ImmutableArray.Builder Definitions = ImmutableArray.CreateBuilder(); + public ImmutableArray.Builder References = ImmutableArray.CreateBuilder(); } /// @@ -108,6 +110,8 @@ public async Task AttachToStreamingPresenterAsync(IFindUsagesContext presenterCo foreach (var definition in _state.Definitions) await presenterContext.OnDefinitionFoundAsync(definition, cancellationToken).ConfigureAwait(false); + await presenterContext.OnReferencesFoundAsync(_state.References.AsAsyncEnumerable(), cancellationToken).ConfigureAwait(false); + // Now swap over to the presenter being the sink for all future callbacks, and clear any buffered data. _streamingPresenterContext = presenterContext; _state = null; @@ -199,11 +203,18 @@ async ValueTask IFindUsagesContext.OnDefinitionFoundAsync(DefinitionItem definit } } - ValueTask IFindUsagesContext.OnReferencesFoundAsync(IAsyncEnumerable references, CancellationToken cancellationToken) + async ValueTask IFindUsagesContext.OnReferencesFoundAsync(IAsyncEnumerable references, CancellationToken cancellationToken) { - // Entirely ignored. These features do not show references. - Contract.Fail("GoToImpl/Base should never report a reference."); - return ValueTaskFactory.CompletedTask; + using var _ = await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false); + if (IsSwapped) + { + await _streamingPresenterContext.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false); + } + else + { + await foreach (var reference in references) + _state.References.Add(reference); + } } #endregion diff --git a/src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindCommandHandler.cs b/src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindCommandHandler.cs new file mode 100644 index 0000000000000..eeb7b6d11bce1 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindCommandHandler.cs @@ -0,0 +1,44 @@ +// 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 Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Text.Editor.Commanding; + +namespace Microsoft.CodeAnalysis.GoOrFind; + +internal abstract class AbstractGoOrFindCommandHandler( + IGoOrFindNavigationService navigationService) : ICommandHandler + where TCommandArgs : EditorCommandArgs +{ + private readonly IGoOrFindNavigationService _navigationService = navigationService; + + public string DisplayName => _navigationService.DisplayName; + + public CommandState GetCommandState(TCommandArgs args) + { + var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + return _navigationService.IsAvailable(document) + ? CommandState.Available + : CommandState.Unspecified; + } + + public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) + { + var subjectBuffer = args.SubjectBuffer; + var caret = args.TextView.GetCaretPoint(subjectBuffer); + if (!caret.HasValue) + return false; + + var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (!_navigationService.IsAvailable(document)) + return false; + + // We were called on the current snapshot of a buffer, and we have a position within that buffer. + // We should never have an invalid position here, so pass "false" for allowInvalidPosition so we + // blow up if some invariant was broken. + return _navigationService.ExecuteCommand(document, caret.Value.Position, allowInvalidPosition: false); + } +} diff --git a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs b/src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindNavigationService.cs similarity index 73% rename from src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs rename to src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindNavigationService.cs index b8ce01e14c998..660e2aa630ce3 100644 --- a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs +++ b/src/EditorFeatures/Core/GoOrFind/AbstractGoOrFindNavigationService.cs @@ -3,14 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; @@ -19,27 +18,25 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor.Commanding; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Threading; -using Microsoft.VisualStudio.Utilities; -namespace Microsoft.CodeAnalysis.GoToDefinition; +namespace Microsoft.CodeAnalysis.GoOrFind; -internal abstract class AbstractGoToCommandHandler( +/// +/// Core service responsible for handling an operation (like 'go to base, go to impl, find references') +/// and trying to navigate quickly to them if possible, or show their results in the find-usages window. +/// +internal abstract class AbstractGoOrFindNavigationService( IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingPresenter, - IUIThreadOperationExecutor uiThreadOperationExecutor, IAsynchronousOperationListener listener, - IGlobalOptionService globalOptions) : ICommandHandler + IGlobalOptionService globalOptions) + : IGoOrFindNavigationService where TLanguageService : class, ILanguageService - where TCommandArgs : EditorCommandArgs { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IStreamingFindUsagesPresenter _streamingPresenter = streamingPresenter; - private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor = uiThreadOperationExecutor; private readonly IAsynchronousOperationListener _listener = listener; public readonly OptionsProvider ClassificationOptionsProvider = globalOptions.GetClassificationOptionsProvider(); @@ -71,53 +68,48 @@ internal abstract class AbstractGoToCommandHandler? _delayHook; public abstract string DisplayName { get; } - protected abstract string ScopeDescription { get; } + protected abstract FunctionId FunctionId { get; } - protected abstract Task FindActionAsync(IFindUsagesContext context, Document document, int caretPosition, CancellationToken cancellationToken); + /// + /// If we should try to navigate to the sole item found, if that item was found within 1.5seconds. + /// + protected abstract bool NavigateToSingleResultIfQuick { get; } - private static (Document?, TLanguageService?) GetDocumentAndService(ITextSnapshot snapshot) - { - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - return (document, document?.GetLanguageService()); - } + protected virtual StreamingFindUsagesPresenterOptions GetStreamingPresenterOptions(Document document) + => StreamingFindUsagesPresenterOptions.Default; - public CommandState GetCommandState(TCommandArgs args) - { - var (_, service) = GetDocumentAndService(args.SubjectBuffer.CurrentSnapshot); - return service != null - ? CommandState.Available - : CommandState.Unspecified; - } + protected abstract Task FindActionAsync(IFindUsagesContext context, Document document, TLanguageService service, int caretPosition, CancellationToken cancellationToken); - public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) + public bool IsAvailable([NotNullWhen(true)] Document? document) + => document?.GetLanguageService() != null; + + public bool ExecuteCommand(Document document, int position, bool allowInvalidPosition) { _threadingContext.ThrowIfNotOnUIThread(); - - var subjectBuffer = args.SubjectBuffer; - var caret = args.TextView.GetCaretPoint(subjectBuffer); - if (!caret.HasValue) + if (document is null) return false; - var (document, service) = GetDocumentAndService(subjectBuffer.CurrentSnapshot); + var service = document.GetLanguageService(); if (service == null) return false; - Contract.ThrowIfNull(document); - // cancel any prior find-refs that might be in progress. _cancellationTokenSource.Cancel(); _cancellationTokenSource = new(); // we're going to return immediately from ExecuteCommand and kick off our own async work to invoke the // operation. Once this returns, the editor will close the threaded wait dialog it created. - _inProgressCommand = ExecuteCommandAsync(document, caret.Value.Position, _cancellationTokenSource); + _inProgressCommand = ExecuteCommandAsync( + document, service, position, allowInvalidPosition, _cancellationTokenSource); return true; } private async Task ExecuteCommandAsync( Document document, + TLanguageService service, int position, + bool allowInvalidPosition, CancellationTokenSource cancellationTokenSource) { // This is a fire-and-forget method (nothing guarantees observing it). As such, we have to handle cancellation @@ -137,7 +129,8 @@ private async Task ExecuteCommandAsync( // any failures from it. Technically this should not be possible as it should be inside this same // try/catch. however this code wants to be very resilient to any prior mistakes infecting later operations. await _inProgressCommand.NoThrowAwaitable(captureContext: false); - await ExecuteCommandWorkerAsync(document, position, cancellationTokenSource).ConfigureAwait(false); + await ExecuteCommandWorkerAsync( + document, service, position, allowInvalidPosition, cancellationTokenSource).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -149,7 +142,9 @@ private async Task ExecuteCommandAsync( private async Task ExecuteCommandWorkerAsync( Document document, + TLanguageService service, int position, + bool allowInvalidPosition, CancellationTokenSource cancellationTokenSource) { // Switch to the BG immediately so we can keep as much work off the UI thread. @@ -169,16 +164,17 @@ private async Task ExecuteCommandWorkerAsync( var findContext = new BufferedFindUsagesContext(); var cancellationToken = cancellationTokenSource.Token; - var delayTask = DelayAsync(cancellationToken); - var findTask = FindResultsAsync(findContext, document, position, cancellationToken); + var delayBeforeShowingResultsWindowTask = DelayAsync(cancellationToken); + var findTask = FindResultsAsync( + findContext, document, service, position, allowInvalidPosition, cancellationToken); - var firstFinishedTask = await Task.WhenAny(delayTask, findTask).ConfigureAwait(false); + var firstFinishedTask = await Task.WhenAny(delayBeforeShowingResultsWindowTask, findTask).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) // we bailed out because another command was issued. Immediately stop everything we're doing and return // back so the next operation can run. return; - if (firstFinishedTask == findTask) + if (this.NavigateToSingleResultIfQuick && firstFinishedTask == findTask) { // We completed the search within 1.5 seconds. If we had at least one result then Navigate to it directly // (if there is just one) or present them all if there are many. @@ -199,7 +195,7 @@ await _streamingPresenter.TryPresentLocationOrNavigateIfOneAsync( // We either got no results, or 1.5 has passed and we didn't figure out the symbols to navigate to or // present. So pop up the presenter to show the user that we're involved in a longer search, without // blocking them. - await PresentResultsInStreamingPresenterAsync(findContext, findTask, cancellationTokenSource).ConfigureAwait(false); + await PresentResultsInStreamingPresenterAsync(document, findContext, findTask, cancellationTokenSource).ConfigureAwait(false); } private Task DelayAsync(CancellationToken cancellationToken) @@ -209,17 +205,25 @@ private Task DelayAsync(CancellationToken cancellationToken) return delayHook(cancellationToken); } - return Task.Delay(TaggerDelay.OnIdle.ComputeTimeDelay(), cancellationToken); + // If we want to navigate to a single result if it is found quickly, then delay showing the find-refs window + // for 1.5 seconds to see if a result comes in by then. If we're not navigating and are always showing the + // far window, then don't have any delay showing the window. + var delay = this.NavigateToSingleResultIfQuick + ? DelayTimeSpan.Idle + : TimeSpan.Zero; + + return Task.Delay(delay, cancellationToken); } private async Task PresentResultsInStreamingPresenterAsync( + Document document, BufferedFindUsagesContext findContext, Task findTask, CancellationTokenSource cancellationTokenSource) { var cancellationToken = cancellationTokenSource.Token; await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var (presenterContext, presenterCancellationToken) = _streamingPresenter.StartSearch(DisplayName, StreamingFindUsagesPresenterOptions.Default); + var (presenterContext, presenterCancellationToken) = _streamingPresenter.StartSearch(DisplayName, GetStreamingPresenterOptions(document)); try { @@ -248,7 +252,12 @@ private async Task PresentResultsInStreamingPresenterAsync( } private async Task FindResultsAsync( - IFindUsagesContext findContext, Document document, int position, CancellationToken cancellationToken) + IFindUsagesContext findContext, + Document document, + TLanguageService service, + int position, + bool allowInvalidPosition, + CancellationToken cancellationToken) { // Ensure that we relinquish the thread so that the caller can proceed with their work. await TaskScheduler.Default.SwitchTo(alwaysYield: true); @@ -259,17 +268,25 @@ private async Task FindResultsAsync( // Let the user know in the FAR window if results may be inaccurate because this is running prior to the // solution being fully loaded. - var service = document.Project.Solution.Services.GetRequiredService(); - var isFullyLoaded = await service.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); + var statusService = document.Project.Solution.Services.GetRequiredService(); + var isFullyLoaded = await statusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); if (!isFullyLoaded) { await findContext.ReportMessageAsync( EditorFeaturesResources.The_results_may_be_incomplete_due_to_the_solution_still_loading_projects, NotificationSeverity.Information, cancellationToken).ConfigureAwait(false); } + // If we're allowing invalid positions (say from features that are passed stale positions), + // then ensure the position is within the bounds of the document before proceeding. + if (allowInvalidPosition) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + position = Math.Min(text.Length, position); + } + // We were able to find the doc prior to loading the workspace (or else we would not have the service). // So we better be able to find it afterwards. - await FindActionAsync(findContext, document, position, cancellationToken).ConfigureAwait(false); + await FindActionAsync(findContext, document, service, position, cancellationToken).ConfigureAwait(false); } } @@ -280,9 +297,9 @@ internal TestAccessor GetTestAccessor() internal readonly struct TestAccessor { - private readonly AbstractGoToCommandHandler _instance; + private readonly AbstractGoOrFindNavigationService _instance; - internal TestAccessor(AbstractGoToCommandHandler instance) + internal TestAccessor(AbstractGoOrFindNavigationService instance) => _instance = instance; internal ref Func? DelayHook diff --git a/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesCommandHandler.cs b/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesCommandHandler.cs new file mode 100644 index 0000000000000..0cdb681762bc1 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesCommandHandler.cs @@ -0,0 +1,21 @@ +// 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.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.FindReferences; + +[Export(typeof(ICommandHandler))] +[ContentType(ContentTypeNames.RoslynContentType)] +[Name(PredefinedCommandHandlerNames.FindReferences)] +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class FindReferencesCommandHandler(FindReferencesNavigationService navigationService) + : AbstractGoOrFindCommandHandler(navigationService); diff --git a/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesNavigationService.cs b/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesNavigationService.cs new file mode 100644 index 0000000000000..c1de755a242a7 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/FindReferences/FindReferencesNavigationService.cs @@ -0,0 +1,53 @@ +// 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.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Microsoft.CodeAnalysis.FindReferences; + +[Export(typeof(FindReferencesNavigationService))] +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class FindReferencesNavigationService( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingPresenter, + IAsynchronousOperationListenerProvider listenerProvider, + IGlobalOptionService globalOptions) : AbstractGoOrFindNavigationService( + threadingContext, + streamingPresenter, + listenerProvider.GetListener(FeatureAttribute.FindReferences), + globalOptions) +{ + public override string DisplayName => EditorFeaturesResources.Find_References; + + protected override FunctionId FunctionId => FunctionId.CommandHandler_FindAllReference; + + /// + /// For find-refs, we *always* use the window. Even if there is only a single result. This is not a 'go' command + /// which imperatively tries to navigate to the location if possible. The intent here is to keep the results in view + /// so that the user can always refer to them, even as they do other work. + /// + protected override bool NavigateToSingleResultIfQuick => false; + + protected override StreamingFindUsagesPresenterOptions GetStreamingPresenterOptions(Document document) + => new() + { + SupportsReferences = true, + IncludeContainingTypeAndMemberColumns = document.Project.SupportsCompilation, + IncludeKindColumn = document.Project.Language != LanguageNames.FSharp + }; + + protected override Task FindActionAsync(IFindUsagesContext context, Document document, IFindUsagesService service, int caretPosition, CancellationToken cancellationToken) + => service.FindReferencesAsync(context, document, caretPosition, this.ClassificationOptionsProvider, cancellationToken); +} diff --git a/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseCommandHandler.cs b/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseCommandHandler.cs new file mode 100644 index 0000000000000..f0ac697638c47 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseCommandHandler.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; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; +using Microsoft.VisualStudio.Utilities; +using VSCommanding = Microsoft.VisualStudio.Commanding; + +namespace Microsoft.CodeAnalysis.GoToBase; + +[Export(typeof(VSCommanding.ICommandHandler))] +[ContentType(ContentTypeNames.RoslynContentType)] +[Name(PredefinedCommandHandlerNames.GoToBase)] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GoToBaseCommandHandler(GoToBaseNavigationService navigationService) + : AbstractGoOrFindCommandHandler(navigationService); diff --git a/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseNavigationService.cs b/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseNavigationService.cs new file mode 100644 index 0000000000000..42b5f1140a661 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/GoToBase/GoToBaseNavigationService.cs @@ -0,0 +1,46 @@ +// 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.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Microsoft.CodeAnalysis.GoToBase; + +[Export(typeof(GoToBaseNavigationService))] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GoToBaseNavigationService( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingPresenter, + IAsynchronousOperationListenerProvider listenerProvider, + IGlobalOptionService globalOptions) + : AbstractGoOrFindNavigationService( + threadingContext, + streamingPresenter, + listenerProvider.GetListener(FeatureAttribute.GoToBase), + globalOptions) +{ + public override string DisplayName => EditorFeaturesResources.Go_To_Base; + + protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToBase; + + /// + /// If we find a single results quickly enough, we do want to take the user directly to it, + /// instead of popping up the FAR window to show it. + /// + protected override bool NavigateToSingleResultIfQuick => true; + + protected override Task FindActionAsync(IFindUsagesContext context, Document document, IGoToBaseService service, int caretPosition, CancellationToken cancellationToken) + => service.FindBasesAsync(context, document, caretPosition, ClassificationOptionsProvider, cancellationToken); +} diff --git a/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationCommandHandler.cs b/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationCommandHandler.cs new file mode 100644 index 0000000000000..baaaa8fa487dd --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationCommandHandler.cs @@ -0,0 +1,23 @@ +// 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.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Commanding.Commands; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.GoToDefinition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.GoToImplementation; + +[Export(typeof(ICommandHandler))] +[ContentType(ContentTypeNames.RoslynContentType)] +[Name(PredefinedCommandHandlerNames.GoToImplementation)] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GoToImplementationCommandHandler(GoToImplementationNavigationService navigationService) + : AbstractGoOrFindCommandHandler(navigationService); diff --git a/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationNavigationService.cs b/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationNavigationService.cs new file mode 100644 index 0000000000000..80406263463d4 --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/GoToImplementation/GoToImplementationNavigationService.cs @@ -0,0 +1,45 @@ +// 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.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Microsoft.CodeAnalysis.GoToImplementation; + +[Export(typeof(GoToImplementationNavigationService))] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GoToImplementationNavigationService( + IThreadingContext threadingContext, + IStreamingFindUsagesPresenter streamingPresenter, + IAsynchronousOperationListenerProvider listenerProvider, + IGlobalOptionService globalOptions) : AbstractGoOrFindNavigationService( + threadingContext, + streamingPresenter, + listenerProvider.GetListener(FeatureAttribute.GoToImplementation), + globalOptions) +{ + public override string DisplayName => EditorFeaturesResources.Go_To_Implementation; + + protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToImplementation; + + /// + /// If we find a single results quickly enough, we do want to take the user directly to it, + /// instead of popping up the FAR window to show it. + /// + protected override bool NavigateToSingleResultIfQuick => true; + + protected override Task FindActionAsync(IFindUsagesContext context, Document document, IFindUsagesService service, int caretPosition, CancellationToken cancellationToken) + => service.FindImplementationsAsync(context, document, caretPosition, ClassificationOptionsProvider, cancellationToken); +} diff --git a/src/EditorFeatures/Core/GoOrFind/IGoOrFindNavigationService.cs b/src/EditorFeatures/Core/GoOrFind/IGoOrFindNavigationService.cs new file mode 100644 index 0000000000000..2783d815806ba --- /dev/null +++ b/src/EditorFeatures/Core/GoOrFind/IGoOrFindNavigationService.cs @@ -0,0 +1,15 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.GoOrFind; + +internal interface IGoOrFindNavigationService +{ + string DisplayName { get; } + + bool IsAvailable([NotNullWhen(true)] Document? document); + bool ExecuteCommand(Document document, int position, bool allowInvalidPosition); +} diff --git a/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs b/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs deleted file mode 100644 index d8d091263f367..0000000000000 --- a/src/EditorFeatures/Core/GoToBase/GoToBaseCommandHandler.cs +++ /dev/null @@ -1,49 +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.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.GoToDefinition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Utilities; -using VSCommanding = Microsoft.VisualStudio.Commanding; - -namespace Microsoft.CodeAnalysis.GoToBase; - -[Export(typeof(VSCommanding.ICommandHandler))] -[ContentType(ContentTypeNames.RoslynContentType)] -[Name(PredefinedCommandHandlerNames.GoToBase)] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class GoToBaseCommandHandler( - IThreadingContext threadingContext, - IStreamingFindUsagesPresenter streamingPresenter, - IUIThreadOperationExecutor uiThreadOperationExecutor, - IAsynchronousOperationListenerProvider listenerProvider, - IGlobalOptionService globalOptions) : AbstractGoToCommandHandler(threadingContext, - streamingPresenter, - uiThreadOperationExecutor, - listenerProvider.GetListener(FeatureAttribute.GoToBase), - globalOptions) -{ - public override string DisplayName => EditorFeaturesResources.Go_To_Base; - - protected override string ScopeDescription => EditorFeaturesResources.Locating_bases; - protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToBase; - - protected override Task FindActionAsync(IFindUsagesContext context, Document document, int caretPosition, CancellationToken cancellationToken) - => document.GetRequiredLanguageService() - .FindBasesAsync(context, document, caretPosition, ClassificationOptionsProvider, cancellationToken); -} diff --git a/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs b/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs deleted file mode 100644 index 442fd4d8aca9d..0000000000000 --- a/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs +++ /dev/null @@ -1,49 +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.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Commanding.Commands; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.GoToDefinition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Utilities; - -namespace Microsoft.CodeAnalysis.GoToImplementation; - -[Export(typeof(ICommandHandler))] -[ContentType(ContentTypeNames.RoslynContentType)] -[Name(PredefinedCommandHandlerNames.GoToImplementation)] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class GoToImplementationCommandHandler( - IThreadingContext threadingContext, - IStreamingFindUsagesPresenter streamingPresenter, - IUIThreadOperationExecutor uiThreadOperationExecutor, - IAsynchronousOperationListenerProvider listenerProvider, - IGlobalOptionService globalOptions) : AbstractGoToCommandHandler(threadingContext, - streamingPresenter, - uiThreadOperationExecutor, - listenerProvider.GetListener(FeatureAttribute.GoToImplementation), - globalOptions) -{ - public override string DisplayName => EditorFeaturesResources.Go_To_Implementation; - - protected override string ScopeDescription => EditorFeaturesResources.Locating_implementations; - protected override FunctionId FunctionId => FunctionId.CommandHandler_GoToImplementation; - - protected override Task FindActionAsync(IFindUsagesContext context, Document document, int caretPosition, CancellationToken cancellationToken) - => document.GetRequiredLanguageService() - .FindImplementationsAsync(context, document, caretPosition, ClassificationOptionsProvider, cancellationToken); -} diff --git a/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs b/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs index adcdf58f6cec9..9813af452df05 100644 --- a/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs +++ b/src/EditorFeatures/Core/IntelliSense/ModelComputation.cs @@ -146,8 +146,10 @@ public void ChainTaskAndNotifyControllerWhenFinished( async Task TransformModelAsync(Task lastTask) { - // Ensure we're on the BG before doing any model transformation work. - await TaskScheduler.Default; + // Ensure we're on the BG before doing any model transformation work. Also, ensure we yield so that we don't + // end up with an enormously long chain of tasks unwinding. That can otherwise cause a stack overflow if + // this chain gets too long. + await Task.Yield().ConfigureAwait(false); var model = await lastTask.ConfigureAwait(false); return await transformModelAsync(model, _stopCancellationToken).ConfigureAwait(false); } diff --git a/src/EditorFeatures/Core/LineSeparators/LineSeparatorTaggerProvider.cs b/src/EditorFeatures/Core/LineSeparators/LineSeparatorTaggerProvider.cs index 179e9a13b2fab..18f6ec738be40 100644 --- a/src/EditorFeatures/Core/LineSeparators/LineSeparatorTaggerProvider.cs +++ b/src/EditorFeatures/Core/LineSeparators/LineSeparatorTaggerProvider.cs @@ -65,13 +65,10 @@ private void OnFormatMappingChanged(object sender, FormatItemsEventArgs e) } } - protected override ITaggerEventSource CreateEventSource( - ITextView? textView, ITextBuffer subjectBuffer) - { - return TaggerEventSources.Compose( + protected override ITaggerEventSource CreateEventSource(ITextView? textView, ITextBuffer subjectBuffer) + => TaggerEventSources.Compose( new EditorFormatMapChangedEventSource(_editorFormatMap), TaggerEventSources.OnTextChanged(subjectBuffer)); - } protected override async Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs index 42adfdedb87d7..335439f1be1ef 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.TextChangedEventSource.cs @@ -19,10 +19,20 @@ public TextChangedEventSource(ITextBuffer subjectBuffer) } public override void Connect() - => _subjectBuffer.Changed += OnTextBufferChanged; + { + if (_subjectBuffer is ITextBuffer2 buffer2) + buffer2.ChangedOnBackground += OnTextBufferChanged; + else + _subjectBuffer.Changed += OnTextBufferChanged; + } public override void Disconnect() - => _subjectBuffer.Changed -= OnTextBufferChanged; + { + if (_subjectBuffer is ITextBuffer2 buffer2) + buffer2.ChangedOnBackground -= OnTextBufferChanged; + else + _subjectBuffer.Changed -= OnTextBufferChanged; + } private void OnTextBufferChanged(object? sender, TextContentChangedEventArgs e) { diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.State.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.State.cs index 091db4281128a..baff5376463e9 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.State.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.State.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -20,9 +19,6 @@ private sealed class State : IDisposable public readonly SuggestedActionsSourceProvider Owner; public readonly ITextView TextView; public readonly ITextBuffer SubjectBuffer; - public readonly WorkspaceRegistration Registration; - - public Workspace? Workspace => Registration.Workspace; public State(SuggestedActionsSource source, SuggestedActionsSourceProvider owner, ITextView textView, ITextBuffer textBuffer) { @@ -31,7 +27,6 @@ public State(SuggestedActionsSource source, SuggestedActionsSourceProvider owner Owner = owner; TextView = textView; SubjectBuffer = textBuffer; - Registration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer()); } void IDisposable.Dispose() diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.cs index 257df15bf8b0f..b65af94657a58 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource.cs @@ -63,29 +63,10 @@ public bool TryGetTelemetryId(out Guid telemetryId) using var state = _state.TryAddReference(); if (state is null) - { - return false; - } - - var workspace = state.Target.Workspace; - if (workspace == null) - { - return false; - } - - var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); - if (documentId == null) - { - return false; - } - - var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); - if (project == null) - { return false; - } - switch (project.Language) + var document = state.Target.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + switch (document?.Project.Language) { case LanguageNames.CSharp: telemetryId = s_CSharpSourceGuid; @@ -171,10 +152,6 @@ private void OnTextViewClosed(object sender, EventArgs e) range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - var workspace = state.Target.Workspace; - if (workspace == null) - return null; - using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); if (document == null) diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs index df4fcbbee298c..1b60d0cac2d70 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs @@ -73,18 +73,16 @@ private async Task GetSuggestedActionsWorkerAsync( if (state is null) return; - var workspace = state.Target.Workspace; - if (workspace is null) + var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); + if (document is null) return; var selection = TryGetCodeRefactoringSelection(state, range); - await workspace.Services.GetRequiredService().WaitUntilFullyLoadedAsync(cancellationToken).ConfigureAwait(false); + await document.Project.Solution.Services.GetRequiredService() + .WaitUntilFullyLoadedAsync(cancellationToken).ConfigureAwait(false); using (Logger.LogBlock(FunctionId.SuggestedActions_GetSuggestedActionsAsync, cancellationToken)) { - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document is null) - return; // Create a single keep-alive session as we process each lightbulb priority group. We want to // ensure that all calls to OOP will reuse the same solution-snapshot on the oop side (including diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index 3a7ed489a5f72..6c929302293d1 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -55,11 +55,6 @@ private sealed partial class TagSource private readonly AbstractAsynchronousTaggerProvider _dataSource; - /// - /// Information about what workspace the buffer we're tagging is associated with. - /// - private readonly WorkspaceRegistration _workspaceRegistration; - /// /// Work queue that collects high priority requests to call TagsChanged with. /// @@ -155,8 +150,6 @@ public TagSource( _nonFrozenComputationCancellationSeries = new(_disposalTokenSource.Token); _tagSpanSetPool = new ObjectPool>>(() => new HashSet>(this), trimOnFree: false); - _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); - // PERF: Use AsyncBatchingWorkQueue<_, VoidResult> instead of AsyncBatchingWorkQueue<_> because the latter // has an async state machine that rethrows a very common cancellation exception. _eventChangeQueue = new AsyncBatchingWorkQueue( diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs index 4233fdfac332f..bd4eee8c6de74 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; public abstract partial class AbstractUserDiagnosticTest { // TODO: IInlineRenameService requires WPF (https://github.com/dotnet/roslyn/issues/46153) - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures .AddParts( typeof(TestGenerateTypeOptionsService), typeof(TestProjectManagementService)); diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs index 2a338dbee381e..13bdb053ed31f 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs @@ -30,7 +30,7 @@ public abstract class AbstractMoveTypeTest : AbstractCodeActionTest // TODO: Requires WPF due to IInlineRenameService dependency (https://github.com/dotnet/roslyn/issues/46153) protected override TestComposition GetComposition() - => EditorTestCompositions.EditorFeaturesWpf; + => EditorTestCompositions.EditorFeatures; protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorTestWorkspace workspace, TestParameters parameters) => new MoveTypeCodeRefactoringProvider(); diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs index cd9743e109eca..5f0c1571e4343 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs @@ -118,7 +118,7 @@ public async Task Test(bool commitChanges) await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution .AddTestProject("proj", out var projectId) - .AddTestDocument("test.cs", "class C { }", out var documentId).Project.Solution); + .AddTestDocument("class C { }", "test.cs", out var documentId).Project.Solution); var solution = localWorkspace.CurrentSolution; var project = solution.GetRequiredProject(projectId); @@ -156,15 +156,30 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution // EmitSolutionUpdate - var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); + var errorReadingFileDescriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); + var moduleErrorDescriptor = EditAndContinueDiagnosticDescriptors.GetModuleDiagnosticDescriptor(Contracts.EditAndContinue.ManagedHotReloadAvailabilityStatus.Optimized); + var syntaxErrorDescriptor = new DiagnosticDescriptor("CS0001", "Syntax error", "Syntax error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true); + var compilerHiddenDescriptor = new DiagnosticDescriptor("CS0002", "Hidden", "Emit Hidden", "Compiler", DiagnosticSeverity.Hidden, isEnabledByDefault: true); + var compilerInfoDescriptor = new DiagnosticDescriptor("CS0003", "Info", "Emit Info", "Compiler", DiagnosticSeverity.Info, isEnabledByDefault: true); + var compilerWarningDescriptor = new DiagnosticDescriptor("CS0004", "Emit Warning", "Emit Warning", "Compiler", DiagnosticSeverity.Warning, isEnabledByDefault: true); + var compilerErrorDescriptor = new DiagnosticDescriptor("CS0005", "Emit Error", "Emit Error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true); mockEncService.EmitSolutionUpdateImpl = (solution, _, _) => { var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!; - var documentDiagnostic = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]); - var projectDiagnostic = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.None, ["proj", "error 2"]); - var syntaxError = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "syntax error 3"]); + var documentDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]); + var projectDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.None, ["proj", "error 2"]); + var moduleError = CodeAnalysis.Diagnostic.Create(moduleErrorDescriptor, Location.None, ["proj", "module error"]); + var syntaxError = CodeAnalysis.Diagnostic.Create(syntaxErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); + var compilerDocHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); + var compilerDocInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); + var compilerDocWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); + var compilerDocError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); + var compilerProjectHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.None); + var compilerProjectInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.None); + var compilerProjectWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.None); + var compilerProjectError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.None); var rudeEditDiagnostic = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["x"]).ToDiagnostic(syntaxTree); var deletedDocumentRudeEdit = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: [""]).ToDiagnostic(tree: null); @@ -172,8 +187,26 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution { Solution = solution, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []), - Diagnostics = [new ProjectDiagnostics(project.Id, [documentDiagnostic, projectDiagnostic])], - RudeEdits = [new ProjectDiagnostics(project.Id, [rudeEditDiagnostic, deletedDocumentRudeEdit])], + Diagnostics = + [ + new ProjectDiagnostics( + project.Id, + [ + documentDiagnostic, + projectDiagnostic, + moduleError, + rudeEditDiagnostic, + deletedDocumentRudeEdit, + compilerDocError, + compilerDocWarning, + compilerDocHidden, + compilerDocInfo, + compilerProjectError, + compilerProjectWarning, + compilerProjectHidden, + compilerProjectInfo, + ]) + ], SyntaxError = syntaxError, ProjectsToRebuild = [project.Id], ProjectsToRestart = ImmutableDictionary>.Empty.Add(project.Id, []) @@ -186,18 +219,28 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution AssertEx.Equal( [ - $"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "")}", $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", - $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}" + $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", + $"Error ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}", + $"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "")}", + $"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error", + $"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning", + $"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error", + $"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning", ], sessionState.ApplyChangesDiagnostics.Select(Inspect)); AssertEx.Equal( [ - $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", - $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", - $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}", + $"RestartRequired ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", + $"RestartRequired ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", + $"RestartRequired ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}", $"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}", $"RestartRequired ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "")}", + $"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error", + $"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning", + $"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error", + $"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning", + $"Error CS0001: {document.FilePath}(0, 1, 0, 2): Syntax error", ], updates.Diagnostics.Select(Inspect)); Assert.True(sessionState.IsSessionActive); diff --git a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs index 63f4a77d62c4f..3a6a31da9eb4c 100644 --- a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs +++ b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.FindReferences; using Microsoft.CodeAnalysis.FindUsages; @@ -65,9 +66,11 @@ public async Task TestFindReferencesAsynchronousCall() var listenerProvider = workspace.ExportProvider.GetExportedValue(); var handler = new FindReferencesCommandHandler( - presenter, - workspace.GlobalOptions, - listenerProvider); + new FindReferencesNavigationService( + workspace.ExportProvider.GetExportedValue(), + presenter, + listenerProvider, + workspace.GlobalOptions)); var textView = workspace.Documents[0].GetTextView(); textView.Caret.MoveTo(new SnapshotPoint(textView.TextSnapshot, 7)); diff --git a/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs b/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs index 47974f2cfb369..fcfceb4989777 100644 --- a/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs +++ b/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs @@ -52,7 +52,7 @@ protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorT public async Task TestCodeActionPreviewAndApply() { // TODO: WPF required due to https://github.com/dotnet/roslyn/issues/46153 - using var workspace = EditorTestWorkspace.Create(WorkspaceXml, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.Create(WorkspaceXml, composition: EditorTestCompositions.EditorFeatures); var codeIssueOrRefactoring = await GetCodeRefactoringAsync(workspace, new TestParameters()); await TestActionOnLinkedFiles( diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs index 4c28368add087..a8e4384a24247 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs @@ -128,7 +128,7 @@ private static EditorTestWorkspace CreateTestWorkspace(string code, string langu private static EditorTestWorkspace CreateTestWorkspace(string xml) { - return EditorTestWorkspace.Create(xml, composition: EditorTestCompositions.EditorFeaturesWpf); + return EditorTestWorkspace.Create(xml, composition: EditorTestCompositions.EditorFeatures); } public void SendEscape() diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index a25ef23df2286..48bc08e15157e 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -52,7 +52,7 @@ static void Main(string[] args) #endregion }"; - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); var globalOptions = workspace.GlobalOptions; globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); @@ -119,7 +119,7 @@ public class Bar } "; - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); var globalOptions = workspace.GlobalOptions; globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); @@ -164,7 +164,7 @@ public class Bar } "; - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); var globalOptions = workspace.GlobalOptions; globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); @@ -209,7 +209,7 @@ public class Bar } "; - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); var globalOptions = workspace.GlobalOptions; globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); @@ -261,7 +261,7 @@ End Module #End Region End Namespace"; - using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeatures); var globalOptions = workspace.GlobalOptions; globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic, collapseRegionsWhenCollapsingToDefinitions); @@ -317,7 +317,7 @@ Sub Main(args As String()) End Sub End Module"; - using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeatures); var tags = await GetTagsFromWorkspaceAsync(workspace); var hints = tags.Select(x => x.GetCollapsedHintForm()).Cast().ToArray(); @@ -339,7 +339,7 @@ End If End Sub End Module"; - using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeatures); var tags = await GetTagsFromWorkspaceAsync(workspace); Assert.Collection(tags, programTag => { @@ -371,7 +371,7 @@ public void MyMethod() => System.Linq.Enumerable.Range(10, 100).Any(x => } """; - using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeatures); var tags = await GetTagsFromWorkspaceAsync(workspace); Assert.Collection(tags, programTag => diff --git a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs index 86ee29b50ffcd..1c6b693d9d5f4 100644 --- a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs +++ b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs @@ -99,7 +99,7 @@ public void TestNotSynchronousOutlining() class Program { } - """, composition: EditorTestCompositions.EditorFeaturesWpf); + """, composition: EditorTestCompositions.EditorFeatures); WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestNotSynchronousOutlining)} creates asynchronous taggers"); var tagProvider = workspace.ExportProvider.GetExportedValue(); @@ -125,7 +125,7 @@ class Program } #endregion - """, composition: EditorTestCompositions.EditorFeaturesWpf); + """, composition: EditorTestCompositions.EditorFeatures); WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestSynchronousOutlining)} creates asynchronous taggers"); var tagProvider = workspace.ExportProvider.GetExportedValue(); diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb index 79a6d53ab09ae..d48285d64141e 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesCommandHandlerTests.vb @@ -4,6 +4,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.Host +Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.FindReferences Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.Shared.TestHooks @@ -40,9 +41,11 @@ class C Dim context = New FindUsagesTestContext() Dim commandHandler = New FindReferencesCommandHandler( - New MockStreamingFindReferencesPresenter(context), - workspace.GlobalOptions, - listenerProvider) + New FindReferencesNavigationService( + workspace.ExportProvider.GetExportedValue(Of IThreadingContext)(), + New MockStreamingFindReferencesPresenter(context), + listenerProvider, + workspace.GlobalOptions)) Dim document = workspace.CurrentSolution.GetDocument(testDocument.Id) commandHandler.ExecuteCommand( diff --git a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb index 3b3ae7b85a943..dcee8c44cad61 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb @@ -217,7 +217,9 @@ class Program Await TestAsync(workspace) End Function - + + + Public Async Function TestCSharpGotoDefinitionPartialMethod() As Task Dim workspace = @@ -225,7 +227,7 @@ class Program partial class Test { - partial void M(); + partial void [|M|](); } @@ -237,7 +239,7 @@ class Program t.M$$(); } - partial void [|M|]() + partial void M() { throw new NotImplementedException(); } @@ -249,7 +251,7 @@ class Program Await TestAsync(workspace) End Function - + Public Async Function TestCSharpGotoDefinitionExtendedPartialMethod() As Task Dim workspace = @@ -257,7 +259,7 @@ class Program partial class Test { - public partial void M(); + public partial void [|M|](); } @@ -269,7 +271,7 @@ class Program t.M$$(); } - public partial void [|M|]() + public partial void M() { throw new NotImplementedException(); } @@ -281,7 +283,7 @@ class Program Await TestAsync(workspace) End Function - + Public Async Function TestCSharpGotoDefinitionPartialProperty() As Task Dim workspace = @@ -289,7 +291,7 @@ class Program partial class Test { - public partial int Prop { get; set; } + public partial int [|Prop|] { get; set; } } @@ -301,7 +303,7 @@ class Program int i = t.Prop$$; } - public partial void [|Prop|] + public partial void Prop { get => throw new NotImplementedException(); set => throw new NotImplementedException(); @@ -314,7 +316,7 @@ class Program Await TestAsync(workspace) End Function - + Public Async Function TestCSharpGotoDefinitionPartialEvent() As Task Dim workspace = @@ -322,7 +324,7 @@ class Program partial class Test { - public partial event System.Action E; + public partial event System.Action [|E|]; } @@ -334,7 +336,7 @@ class Program int i = t.E$$; } - public partial event System.Action [|E|] + public partial event System.Action E { add { } remove { } @@ -347,7 +349,7 @@ class Program Await TestAsync(workspace) End Function - + Public Async Function TestCSharpGotoDefinitionPartialConstructor() As Task Dim workspace = @@ -355,7 +357,7 @@ class Program partial class Test { - public partial Test(); + public partial [|Test|](); } @@ -366,7 +368,7 @@ class Program var t = new Te$$st(); } - public partial [|Test|]() + public partial Test() { } } diff --git a/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb index 3e0549be8d262..49d8d2e926479 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb @@ -216,7 +216,7 @@ End Class Partial Class Customer - Private Sub [|OnNameChanged|]() + Private Sub OnNameChanged() End Sub End Class @@ -227,7 +227,7 @@ End Class Dim x As New Customer() x.OnNameChanged$$() End Sub - Partial Private Sub OnNameChanged() + Partial Private Sub [|OnNameChanged|]() End Sub End Class diff --git a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb index 1b1894a41da13..3bf22eadb66cb 100644 --- a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb +++ b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb @@ -999,5 +999,170 @@ class D : C Await TestAsync(workspace, host) End Function + + + + Public Async Function TestPartialMethod(host As TestHost) As Task + Dim workspace = + + + + partial class Test + { + partial void M(); + } + + + partial class Test + { + void Goo() + { + var t = new Test(); + t.M$$(); + } + + partial void [|M|]() + { + throw new NotImplementedException(); + } + } + + + + + Await TestAsync(workspace, host) + End Function + + + + Public Async Function TestExtendedPartialMethod(host As TestHost) As Task + Dim workspace = + + + + partial class Test + { + public partial void M(); + } + + + partial class Test + { + void Goo() + { + var t = new Test(); + t.M$$(); + } + + public partial void [|M|]() + { + throw new NotImplementedException(); + } + } + + + + + Await TestAsync(workspace, host) + End Function + + + + Public Async Function TestPartialProperty(host As TestHost) As Task + Dim workspace = + + + + partial class Test + { + public partial int Prop { get; set; } + } + + + partial class Test + { + void Goo() + { + var t = new Test(); + int i = t.Prop$$; + } + + public partial void [|Prop|] + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + } + + + + + Await TestAsync(workspace, host) + End Function + + + + Public Async Function TestPartialEvent(host As TestHost) As Task + Dim workspace = + + + + partial class Test + { + public partial event System.Action E; + } + + + partial class Test + { + void Goo() + { + var t = new Test(); + int i = t.E$$; + } + + public partial event System.Action [|E|] + { + add { } + remove { } + } + } + + + + + Await TestAsync(workspace, host) + End Function + + + + Public Async Function TestPartialConstructor(host As TestHost) As Task + Dim workspace = + + + + partial class Test + { + public partial Test(); + } + + + partial class Test + { + void Goo() + { + var t = new Te$$st(); + } + + public partial [|Test|]() + { + } + } + + + + + Await TestAsync(workspace, host) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb index 8a2c2582e62e1..8e1b13c98d038 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb @@ -934,5 +934,34 @@ class C Await VerifyTypeHints(input, output) End Function + + + Public Async Function TestNoDoubleClickWithCollectionExpression() As Task + Dim input = + + + +class A +{ + private static readonly ImmutableHashSet<string?> Hashes = {| ImmutableHashSet<string?>:|}[]; +} + + + + + Dim output = + + + +class A +{ + private static readonly ImmutableHashSet<string?> Hashes = []; +} + + + + + Await VerifyTypeHints(input, output) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index 021397c61035a..ffbf828f599af 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -1507,6 +1507,76 @@ public class C } } } +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_AsyncEnumerableMethodDeclaration1() As Task + Using state = TestStateFactory.CreateCSharpTestState( + Main() + { + $$ + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True) + + state.SendTab() + Assert.Equal(" +using System.Collections.Generic; +using System.Threading.Tasks; + +public class C +{ + public static async IAsyncEnumerable Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_AsyncEnumerableMethodDeclaration2() As Task + Using state = TestStateFactory.CreateCSharpTestState( + Main() + { + $$ + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True) + + state.SendTab() + Assert.Equal(" +using System.Collections.Generic; +using System.Threading.Tasks; + +public class C +{ + public static async IAsyncEnumerable Main() + { + await + } +} ", state.GetDocumentText()) End Using End Function diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb index 993017d94dd8a..a63e361df44df 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb @@ -2608,7 +2608,7 @@ Class C End Sub End Class }]]>, - extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) + extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider)}.ToList()) state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) @@ -2629,7 +2629,7 @@ Class C End Sub End Class }]]>, - extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) + extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider)}.ToList()) state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) @@ -2651,7 +2651,7 @@ Class C End Sub End Class }]]>, - extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) + extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider)}.ToList()) state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) diff --git a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb index e4058e424dc33..47a05d06159c6 100644 --- a/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/InteractivePaste/InteractivePasteCommandHandlerTests.vb @@ -34,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste , - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) Dim textView = workspace.Documents.Single().GetTextView() @@ -68,7 +68,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste , - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) @@ -104,7 +104,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste , - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) @@ -144,7 +144,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste , - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) @@ -188,7 +188,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InteractivePaste , - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) Dim textView = workspace.Documents.Single().GetTextView() Dim editorOperations = workspace.GetService(Of IEditorOperationsFactoryService)().GetEditorOperations(textView) diff --git a/src/EditorFeatures/Test2/Peek/PeekTests.vb b/src/EditorFeatures/Test2/Peek/PeekTests.vb index 4661fd9bb1566..233b2cc93e29a 100644 --- a/src/EditorFeatures/Test2/Peek/PeekTests.vb +++ b/src/EditorFeatures/Test2/Peek/PeekTests.vb @@ -234,13 +234,13 @@ public partial class D $$PartialMethod(); } - partial void PartialMethod(); + partial void {|Identifier:PartialMethod|}(); } ]]> @@ -289,7 +289,7 @@ public partial class D End Sub Private Shared Function CreateTestWorkspace(element As XElement) As EditorTestWorkspace - Return EditorTestWorkspace.Create(element, composition:=EditorTestCompositions.EditorFeaturesWpf) + Return EditorTestWorkspace.Create(element, composition:=EditorTestCompositions.EditorFeatures) End Function Private Shared Function GetPeekResultCollection(element As XElement) As PeekResultCollection diff --git a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb index 472194cbdf9f0..5c028b35ac1c1 100644 --- a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb @@ -58,7 +58,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb index bf29cf35ecad4..87e16d03f9a4e 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -20,7 +20,7 @@ Imports Microsoft.VisualStudio.Text.Tagging Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Friend Module RenameTestHelpers - Private ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf.AddParts( + Private ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeatures.AddParts( GetType(MockDocumentNavigationServiceFactory), GetType(MockPreviewDialogService), GetType(TestWorkspaceConfigurationService)) diff --git a/src/EditorFeatures/Test2/Workspaces/WpfTextBufferVisibilityTrackerTests.vb b/src/EditorFeatures/Test2/Workspaces/WpfTextBufferVisibilityTrackerTests.vb index f4ac4dd16f61d..e0fb1b584f09c 100644 --- a/src/EditorFeatures/Test2/Workspaces/WpfTextBufferVisibilityTrackerTests.vb +++ b/src/EditorFeatures/Test2/Workspaces/WpfTextBufferVisibilityTrackerTests.vb @@ -9,7 +9,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Public Class WpfTextBufferVisibilityTrackerTests Public Sub TestMutationInCallback() - Using workspace = EditorTestWorkspace.CreateCSharp("", composition:=EditorTestCompositions.EditorFeaturesWpf) + Using workspace = EditorTestWorkspace.CreateCSharp("", composition:=EditorTestCompositions.EditorFeatures) Dim visibilityTracker = DirectCast(workspace.ExportProvider.GetExportedValue(Of ITextBufferVisibilityTracker), WpfTextBufferVisibilityTracker) Dim buffer = workspace.Documents.Single().GetTextBuffer() diff --git a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs index e9b98e3eac664..4417603df41c6 100644 --- a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs +++ b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs @@ -70,7 +70,7 @@ private void Test(string expected, string code, int position, bool useTabs, bool var markupCode = code[0..position] + "$$" + code[position..]; // WPF is required for some reason: https://github.com/dotnet/roslyn/issues/46286 - using var workspace = EditorTestWorkspace.Create(Language, compilationOptions: null, parseOptions: null, [markupCode], composition: EditorTestCompositions.EditorFeaturesWpf); + using var workspace = EditorTestWorkspace.Create(Language, compilationOptions: null, parseOptions: null, [markupCode], composition: EditorTestCompositions.EditorFeatures); var view = workspace.Documents.Single().GetTextView(); var buffer = workspace.Documents.Single().GetTextBuffer(); diff --git a/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs b/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs index 2ae952a65611b..2fdf5a8f5cc45 100644 --- a/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs +++ b/src/EditorFeatures/TestUtilities/EditorTestCompositions.cs @@ -66,11 +66,7 @@ public static class EditorTestCompositions typeof(VisualBasic.VBEditorResources).Assembly, typeof(LanguageServerProtocolResources).Assembly); - public static readonly TestComposition EditorFeaturesWpf = EditorFeatures - .AddAssemblies( - typeof(EditorFeaturesWpfResources).Assembly); - - public static readonly TestComposition InteractiveWindow = EditorFeaturesWpf + public static readonly TestComposition InteractiveWindow = EditorFeatures .AddAssemblies( typeof(IInteractiveWindow).Assembly) .AddParts( diff --git a/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj b/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj index 73a17ef2dd304..6d412bd59c5d0 100644 --- a/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj +++ b/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj @@ -22,9 +22,7 @@ - - diff --git a/src/EditorFeatures/TestUtilities/RefactoringHelpers/RefactoringHelpersTestBase.cs b/src/EditorFeatures/TestUtilities/RefactoringHelpers/RefactoringHelpersTestBase.cs index ab6f6bfb397e7..dabcb171b2a0d 100644 --- a/src/EditorFeatures/TestUtilities/RefactoringHelpers/RefactoringHelpersTestBase.cs +++ b/src/EditorFeatures/TestUtilities/RefactoringHelpers/RefactoringHelpersTestBase.cs @@ -41,7 +41,7 @@ protected async Task TestAsync(string text, Func predicate, protected async Task TestUnderselectedAsync(string text) where TNode : SyntaxNode { text = GetSelectionSpan(text, out var selection); - var resultNode = await GetNodeForSelectionAsync(text, selection, Functions.True).ConfigureAwait(false); + var resultNode = await GetNodeForSelectionAsync(text, selection, Functions.True).ConfigureAwait(false); Assert.NotNull(resultNode); Assert.True(CodeRefactoringHelpers.IsNodeUnderselected(resultNode, selection)); @@ -50,7 +50,7 @@ protected async Task TestUnderselectedAsync(string text) where TNode : Sy protected async Task TestNotUnderselectedAsync(string text) where TNode : SyntaxNode { text = GetSelectionAndResultSpans(text, out var selection, out var result); - var resultNode = await GetNodeForSelectionAsync(text, selection, Functions.True).ConfigureAwait(false); + var resultNode = await GetNodeForSelectionAsync(text, selection, Functions.True).ConfigureAwait(false); Assert.NotNull(resultNode); Assert.Equal(result, resultNode!.Span); @@ -64,7 +64,7 @@ protected async Task TestMissingAsync(string text, Func pred { text = GetSelectionSpan(text, out var selection); - var resultNode = await GetNodeForSelectionAsync(text, selection, predicate, allowEmptyNodes).ConfigureAwait(false); + var resultNode = await GetNodeForSelectionAsync(text, selection, predicate, allowEmptyNodes).ConfigureAwait(false); Assert.Null(resultNode); } diff --git a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs index e0318068db327..94106809939b4 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs @@ -23,7 +23,7 @@ public static class SquiggleUtilities internal static TestComposition CompositionWithSolutionCrawler = EditorTestCompositions.EditorFeatures .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); - internal static TestComposition WpfCompositionWithSolutionCrawler = EditorTestCompositions.EditorFeaturesWpf + internal static TestComposition WpfCompositionWithSolutionCrawler = EditorTestCompositions.EditorFeatures .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); internal static async Task>> GetTagSpansAsync( diff --git a/src/EditorFeatures/TestUtilities/StubLocalRegistry.cs b/src/EditorFeatures/TestUtilities/StubLocalRegistry.cs deleted file mode 100644 index 1da13ce4e0941..0000000000000 --- a/src/EditorFeatures/TestUtilities/StubLocalRegistry.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.Runtime.InteropServices.ComTypes; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests; - -internal sealed class StubLocalRegistry : ILocalRegistry5, ILocalRegistry4, ILocalRegistry3 -{ - int ILocalRegistry.CreateInstance(Guid clsid, object punkOuter, ref Guid riid, uint dwFlags, out IntPtr ppvObj) - { - throw new NotImplementedException(); - } - - int ILocalRegistry.GetTypeLibOfClsid(Guid clsid, out ITypeLib pptLib) - { - throw new NotImplementedException(); - } - - int ILocalRegistry.GetClassObjectOfClsid(ref Guid clsid, uint dwFlags, IntPtr lpReserved, ref Guid riid, out IntPtr ppvClassObject) - { - throw new NotImplementedException(); - } - - int ILocalRegistry2.CreateInstance(Guid clsid, object punkOuter, ref Guid riid, uint dwFlags, out IntPtr ppvObj) - { - throw new NotImplementedException(); - } - - int ILocalRegistry2.GetTypeLibOfClsid(Guid clsid, out ITypeLib pptLib) - { - throw new NotImplementedException(); - } - - int ILocalRegistry2.GetClassObjectOfClsid(ref Guid clsid, uint dwFlags, IntPtr lpReserved, ref Guid riid, IntPtr ppvClassObject) - { - throw new NotImplementedException(); - } - - int ILocalRegistry2.GetLocalRegistryRoot(out string pbstrRoot) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.CreateInstance(Guid clsid, object punkOuter, ref Guid riid, uint dwFlags, out IntPtr ppvObj) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.GetTypeLibOfClsid(Guid clsid, out ITypeLib pptLib) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.GetClassObjectOfClsid(ref Guid clsid, uint dwFlags, IntPtr lpReserved, ref Guid riid, IntPtr ppvClassObject) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.GetLocalRegistryRoot(out string pbstrRoot) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.CreateManagedInstance(string codeBase, string assemblyName, string typeName, ref Guid riid, out IntPtr ppvObj) - { - throw new NotImplementedException(); - } - - int ILocalRegistry3.GetClassObjectOfManagedClass(string codeBase, string assemblyName, string typeName, ref Guid riid, out IntPtr ppvClassObject) - { - throw new NotImplementedException(); - } - - int ILocalRegistry4.RegisterClassObject(ref Guid rclsid, out uint pdwCookie) - { - throw new NotImplementedException(); - } - - int ILocalRegistry4.RevokeClassObject(uint dwCookie) - { - throw new NotImplementedException(); - } - - int ILocalRegistry4.RegisterInterface(ref Guid riid) - { - throw new NotImplementedException(); - } - - int ILocalRegistry4.GetLocalRegistryRootEx(uint dwRegType, out uint pdwRegRootHandle, out string pbstrRoot) - { - pdwRegRootHandle = unchecked((uint)__VsLocalRegistryRootHandle.RegHandle_CurrentUser); - pbstrRoot = "Software\\Microsoft\\VisualStudio\\17.0_Test"; - return VSConstants.S_OK; - } - - int ILocalRegistry5.CreateAggregatedManagedInstance(string codeBase, string AssemblyName, string TypeName, IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObj) - { - throw new NotImplementedException(); - } -} diff --git a/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs b/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs deleted file mode 100644 index 3b49070da8dcc..0000000000000 --- a/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs +++ /dev/null @@ -1,188 +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.Generic; -using System.Collections.Immutable; -using System.ComponentModel; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.PlatformUI; -using Microsoft.VisualStudio.Settings; -using Microsoft.VisualStudio.Settings.Telemetry; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests; - -internal sealed class StubSettingsManagerHost : ISettingsManagerHost5 -{ - Task ISettingsManagerHost.AppInitCompletionTask => throw new NotImplementedException(); - - ISettingNameTranslator? ISettingsManagerHost.NameTranslator => null; - - IStringStorage ISettingsManagerHost.PrivateStorage { get; } = new StringStorage(); - - ISettingsLogger? ISettingsManagerHost.Logger => null; - - string ISettingsManagerHost.CollectionName => throw new NotImplementedException(); - - string? ISettingsManagerHost.TelemetrySettings => null; - - string ISettingsManagerHost.AppDir { get; } = Path.GetRandomFileName(); - - IRemoteDefaultsStore? ISettingsManagerHost3.RemoteDefaultsStore => null; - - string ISettingsManagerHost4.DurableHostIdentity => "roslyn-CI"; - - IStoreUpdateLogger? ISettingsManagerHost4.StoreUpdateLogger => null; - - bool ISettingsManagerHost4.IsRoamingEnabledByDefault => false; - - bool ISettingsManagerHost5.IsRoamingAndSharingAllowed => false; - - event EventHandler ISettingsManagerHost.IdleStateChanged - { - add - { - throw new NotImplementedException(); - } - - remove - { - throw new NotImplementedException(); - } - } - - event AsyncEventHandler ISettingsManagerHost.HostShuttingDown - { - add - { - throw new NotImplementedException(); - } - - remove - { - throw new NotImplementedException(); - } - } - - bool ISettingsManagerHost.IsSharedOrRoamedSetting(string settingName) - { - // Don't roam settings in tests - return false; - } - - Task ISettingsManagerHost2.GetTelemetrySettingsAsync() - { - throw new NotImplementedException(); - } - - Task ISettingsManagerHost5.GetServiceStreamAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - private sealed class StringStorage : IStringStorage2, IAsyncStringStorage - { - private ImmutableDictionary _values = ImmutableDictionary.Empty; - private PropertyChangedEventHandler? _propertyChanged; - private PropertyChangedAsyncEventHandler? _propertyChangedAsync; - - event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged - { - add => _propertyChanged += value; - remove => _propertyChanged -= value; - } - - event PropertyChangedAsyncEventHandler IStringStorage.PropertyChangedAsync - { - add => _propertyChangedAsync += value; - remove => _propertyChangedAsync -= value; - } - - event PropertyChangedAsyncEventHandler IAsyncStringStorage.PropertyChangedAsync - { - add => ((IStringStorage)this).PropertyChangedAsync += value; - remove => ((IStringStorage)this).PropertyChangedAsync -= value; - } - - event StoreUpdatedEventHandler IAsyncStringStorage.StoreUpdated - { - add - { - throw new NotImplementedException(); - } - - remove - { - throw new NotImplementedException(); - } - } - - private async Task FireChangeEventAsync(string name) - { - _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - await (_propertyChangedAsync?.RaiseEventAsync(this, new PropertyChangedEventArgs(name)) ?? Task.CompletedTask); - } - - Task IStringStorage.ClearAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - Task IAsyncStringStorage.ClearAsync(CancellationToken cancellationToken) - => ((IStringStorage)this).ClearAsync(cancellationToken); - - async Task IStringStorage.DeleteIfExistsAsync(string name, CancellationToken cancellationToken) - { - if (ImmutableInterlocked.TryRemove(ref _values, name, out _)) - { - await FireChangeEventAsync(name); - } - } - - Task IAsyncStringStorage.DeleteIfExistsAsync(string name, CancellationToken cancellationToken) - => ((IStringStorage)this).DeleteIfExistsAsync(name, cancellationToken); - - StringWithMachineLocalFlag? IStringStorage.Get(string name) - { - return _values.GetValueOrDefault(name); - } - - Task> IAsyncStringStorage.GetAllSinceVersionAsync(int modifiedAfterRevision, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - Task IAsyncStringStorage.GetAsync(string name, CancellationToken cancellationToken) - { - return Task.FromResult(_values.GetValueOrDefault(name)); - } - - Task IAsyncStringStorage.GetStoreIdentityAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - string[] IStringStorage.NamesStartingWith(string prefix) - { - throw new NotImplementedException(); - } - - Task IStringStorage.SetAsync(string name, StringWithMachineLocalFlag value, Action onBeforePropertyChanged, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - Task IAsyncStringStorage.SetAsync(NamedVersionedString value, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - void IStringStorage2.ForEachSettingNameStartingWith(string prefix, CharSpanProcessorDelegate processFunc) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/EditorFeatures/TestUtilities/StubVsEditorAdaptersFactoryService.cs b/src/EditorFeatures/TestUtilities/StubVsEditorAdaptersFactoryService.cs deleted file mode 100644 index d0832c21f13ff..0000000000000 --- a/src/EditorFeatures/TestUtilities/StubVsEditorAdaptersFactoryService.cs +++ /dev/null @@ -1,70 +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 System; -using System.ComponentModel.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; -using IServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests; - -[Export(typeof(IVsEditorAdaptersFactoryService))] -[PartNotDiscoverable] -internal sealed class StubVsEditorAdaptersFactoryService : IVsEditorAdaptersFactoryService -{ - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public StubVsEditorAdaptersFactoryService() - { - } - - public IVsCodeWindow CreateVsCodeWindowAdapter(IServiceProvider serviceProvider) - => throw new NotImplementedException(); - - public IVsTextBuffer CreateVsTextBufferAdapter(IServiceProvider serviceProvider) - => throw new NotImplementedException(); - - public IVsTextBuffer CreateVsTextBufferAdapter(IServiceProvider serviceProvider, IContentType contentType) - => throw new NotImplementedException(); - - public IVsTextBuffer CreateVsTextBufferAdapterForSecondaryBuffer(IServiceProvider serviceProvider, ITextBuffer secondaryBuffer) - => throw new NotImplementedException(); - - public IVsTextBufferCoordinator CreateVsTextBufferCoordinatorAdapter() - => throw new NotImplementedException(); - - public IVsTextView CreateVsTextViewAdapter(IServiceProvider serviceProvider) - => throw new NotImplementedException(); - - public IVsTextView CreateVsTextViewAdapter(IServiceProvider serviceProvider, ITextViewRoleSet roles) - => throw new NotImplementedException(); - - public IVsTextBuffer GetBufferAdapter(ITextBuffer textBuffer) - => throw new NotImplementedException(); - - public ITextBuffer GetDataBuffer(IVsTextBuffer bufferAdapter) - => throw new NotImplementedException(); - - public ITextBuffer GetDocumentBuffer(IVsTextBuffer bufferAdapter) - => throw new NotImplementedException(); - - public IVsTextView GetViewAdapter(ITextView textView) - => throw new NotImplementedException(); - - public IWpfTextView GetWpfTextView(IVsTextView viewAdapter) - => throw new NotImplementedException(); - - public IWpfTextViewHost GetWpfTextViewHost(IVsTextView viewAdapter) - => throw new NotImplementedException(); - - public void SetDataBuffer(IVsTextBuffer bufferAdapter, ITextBuffer dataBuffer) - => throw new NotImplementedException(); -} diff --git a/src/EditorFeatures/TestUtilities/StubVsServiceExporter`1.cs b/src/EditorFeatures/TestUtilities/StubVsServiceExporter`1.cs deleted file mode 100644 index 09445bdb880cb..0000000000000 --- a/src/EditorFeatures/TestUtilities/StubVsServiceExporter`1.cs +++ /dev/null @@ -1,29 +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.ComponentModel.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Threading; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests; - -[Export(typeof(IVsService<>))] -[PartCreationPolicy(CreationPolicy.NonShared)] -[PartNotDiscoverable] -internal sealed class StubVsServiceExporter : StubVsServiceExporter - where T : class -{ - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public StubVsServiceExporter( - [Import(typeof(SAsyncServiceProvider))] IAsyncServiceProvider2 asyncServiceProvider, - JoinableTaskContext joinableTaskContext) - : base(asyncServiceProvider, joinableTaskContext) - { - } -} diff --git a/src/EditorFeatures/TestUtilities/StubVsServiceExporter`2.cs b/src/EditorFeatures/TestUtilities/StubVsServiceExporter`2.cs deleted file mode 100644 index 34d32d140719f..0000000000000 --- a/src/EditorFeatures/TestUtilities/StubVsServiceExporter`2.cs +++ /dev/null @@ -1,74 +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.ComponentModel.Composition; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Threading; - -// Import Roslyn.Utilities with an alias to avoid conflicts with AsyncLazy. This implementation relies on -// AsyncLazy from vs-threading, and not the one from Roslyn. -using RoslynUtilities = Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests; - -[Export(typeof(IVsService<,>))] -[PartCreationPolicy(CreationPolicy.NonShared)] -[PartNotDiscoverable] -internal class StubVsServiceExporter : IVsService - where TService : class - where TInterface : class -{ - private readonly AsyncLazy _serviceGetter; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public StubVsServiceExporter( - [Import(typeof(SAsyncServiceProvider))] IAsyncServiceProvider2 asyncServiceProvider, - JoinableTaskContext joinableTaskContext) - { - _serviceGetter = new AsyncLazy(() => asyncServiceProvider.GetServiceAsync(throwOnFailure: true, CancellationToken.None)!, joinableTaskContext.Factory); - } - - /// - public Task GetValueAsync(CancellationToken cancellationToken) - => _serviceGetter.GetValueAsync(cancellationToken); - - /// - public Task GetValueOrNullAsync(CancellationToken cancellationToken) - { - var value = GetValueAsync(cancellationToken); - if (value.IsCompleted) - { - return TransformResult(value); - } - - return value.ContinueWith( - static t => TransformResult(t), - CancellationToken.None, // token is already passed to antecedent, and this is a tiny sync continuation, so no need to make it also cancelable. - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default).Unwrap(); - - static Task TransformResult(Task task) - { - Debug.Assert(task.IsCompleted); - if (task.Status == TaskStatus.Faulted) - { - // Our caller never wants exceptions, so return a cached null value - return RoslynUtilities::SpecializedTasks.Null(); - } - else - { - // Whether this is cancelled or ran to completion, we return the value as-is - return RoslynUtilities::SpecializedTasks.AsNullable(task); - } - } - } -} diff --git a/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs b/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs index 6457e6fe8dc25..f3e982aa8bf17 100644 --- a/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs +++ b/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs @@ -27,27 +27,6 @@ public TestExtensionErrorHandler() public void HandleError(object sender, Exception exception) { - // Work around bug that is fixed in https://devdiv.visualstudio.com/DevDiv/_git/VS-Platform/pullrequest/209513 - if (exception is NullReferenceException && - exception.StackTrace.Contains("SpanTrackingWpfToolTipPresenter")) - { - return; - } - - // Work around for https://github.com/dotnet/roslyn/issues/42982 - if (exception is NullReferenceException && - exception.StackTrace.Contains("Microsoft.CodeAnalysis.Completion.Providers.AbstractEmbeddedLanguageCompletionProvider.GetLanguageProviders")) - { - return; - } - - // Work around for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1091056 - if (exception is InvalidOperationException && - exception.StackTrace.Contains("Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation.CompletionTelemetryHost")) - { - return; - } - // This exception is unexpected and as such we want the containing test case to // fail. Unfortuntately throwing an exception here is not going to help because // the editor is going to catch and swallow it. Store it here and wait for the diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb index f434fdd59d673..1178e00fbc7fb 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb @@ -38,7 +38,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Protected ReadOnly CompleteStatementCommandHandler As CompleteStatementCommandHandler Private ReadOnly FormatCommandHandler As FormatCommandHandler - Public Shared ReadOnly CompositionWithoutCompletionTestParts As TestComposition = EditorTestCompositions.EditorFeaturesWpf. + Public Shared ReadOnly CompositionWithoutCompletionTestParts As TestComposition = EditorTestCompositions.EditorFeatures. AddExcludedPartTypes( GetType(IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession)), GetType(FormatCommandHandler)). diff --git a/src/EditorFeatures/VisualBasicTest/ChangeSignature/RemoveParametersTests.vb b/src/EditorFeatures/VisualBasicTest/ChangeSignature/RemoveParametersTests.vb index 32fd1ee558113..27db84f7619b8 100644 --- a/src/EditorFeatures/VisualBasicTest/ChangeSignature/RemoveParametersTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ChangeSignature/RemoveParametersTests.vb @@ -107,7 +107,7 @@ End Module , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb index 152222bb3a22f..22e86a294a7f6 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb @@ -21,7 +21,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Genera ' TODO Requires Wpf due to IInlineRenameService dependency (https: //github.com/dotnet/roslyn/issues/46153) Protected Overrides Function GetComposition() As TestComposition - Return EditorTestCompositions.EditorFeaturesWpf + Return EditorTestCompositions.EditorFeatures End Function Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/InitializeParameter/AddParameterCheckTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/InitializeParameter/AddParameterCheckTests.vb index 170396916438d..3c4dd2c73fe34 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/InitializeParameter/AddParameterCheckTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/InitializeParameter/AddParameterCheckTests.vb @@ -109,7 +109,7 @@ end class") Imports System class C - public sub new([||]i as integer) + public sub new([||]i as DateTime) end sub end class") End Function diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb index 056450b1ef0d3..152aa4e467335 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings Public Class PreviewTests Inherits AbstractVisualBasicCodeActionTest - Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf _ + Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeatures _ .AddParts( GetType(MockPreviewPaneService)) diff --git a/src/EditorFeatures/VisualBasicTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.vb index 54d0e6f4edc8f..6e3bfe2bf3382 100644 --- a/src/EditorFeatures/VisualBasicTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EncapsulateField/EncapsulateFieldCommandHandlerTests.vb @@ -145,7 +145,7 @@ End Class , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/EditorFeatures/VisualBasicTest/ExtractInterface/ExtractInterfaceTests.vb b/src/EditorFeatures/VisualBasicTest/ExtractInterface/ExtractInterfaceTests.vb index ec1f5bfb377b1..e5d600159edb4 100644 --- a/src/EditorFeatures/VisualBasicTest/ExtractInterface/ExtractInterfaceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ExtractInterface/ExtractInterfaceTests.vb @@ -1279,7 +1279,7 @@ End Namespace , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.vb b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.vb index 0ad346ff8ca09..54151135cd8ba 100644 --- a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.vb +++ b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.vb @@ -3352,7 +3352,7 @@ End Namespace" , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb index 123c74f17b5b5..2bca427694654 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb @@ -509,7 +509,7 @@ End Class End Function Private Shared Async Function TestThirdPartyCodeFixer(Of TCodefix As {CodeFixProvider, New}, TAnalyzer As {DiagnosticAnalyzer, New})(expected As String, code As String, Optional severity As DiagnosticSeverity = DiagnosticSeverity.Warning) As Task - Using workspace = TestWorkspace.CreateVisualBasic(code, composition:=EditorTestCompositions.EditorFeaturesWpf.AddParts(GetType(TCodefix))) + Using workspace = TestWorkspace.CreateVisualBasic(code, composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TCodefix))) Dim project = workspace.CurrentSolution.Projects.Single() Dim map = New Dictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)) From { @@ -559,7 +559,7 @@ End Class code As String, Optional systemImportsFirst As Boolean = True, Optional separateImportsGroups As Boolean = False) As Task - Using workspace = TestWorkspace.CreateVisualBasic(code, composition:=EditorTestCompositions.EditorFeaturesWpf) + Using workspace = TestWorkspace.CreateVisualBasic(code, composition:=EditorTestCompositions.EditorFeatures) workspace.SetAnalyzerFallbackOptions(New OptionsCollection(LanguageNames.VisualBasic) From { {GenerationOptions.SeparateImportDirectiveGroups, separateImportsGroups}, diff --git a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb index a59c2e7eeffb4..6957c7f027bf0 100644 --- a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb +++ b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitTestData.vb @@ -32,7 +32,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.LineCommit Private ReadOnly _inlineRenameService As InlineRenameServiceMock Public Shared Function Create(test As XElement) As CommitTestData - Dim workspace = EditorTestWorkspace.Create(test, composition:=EditorTestCompositions.EditorFeaturesWpf) + Dim workspace = EditorTestWorkspace.Create(test, composition:=EditorTestCompositions.EditorFeatures) Return New CommitTestData(workspace) End Function diff --git a/src/EditorFeatures/VisualBasicTest/Organizing/OrganizeTypeDeclarationTests.vb b/src/EditorFeatures/VisualBasicTest/Organizing/OrganizeTypeDeclarationTests.vb index 14bccd69d039a..3d54ab2e13a6d 100644 --- a/src/EditorFeatures/VisualBasicTest/Organizing/OrganizeTypeDeclarationTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Organizing/OrganizeTypeDeclarationTests.vb @@ -935,7 +935,7 @@ End Namespace , workspaceKind:=WorkspaceKind.Interactive, - composition:=EditorTestCompositions.EditorFeaturesWpf) + composition:=EditorTestCompositions.EditorFeatures) ' Force initialization. workspace.GetOpenDocumentIds().Select(Function(id) workspace.GetTestDocument(id).GetTextView()).ToList() diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs index 62b99076757e4..e5e5f299ddb32 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs @@ -13,11 +13,13 @@ using Microsoft.CodeAnalysis.ChangeNamespace; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Simplification; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -27,14 +29,18 @@ namespace Microsoft.CodeAnalysis.CSharp.ChangeNamespace; using static SyntaxFactory; [ExportLanguageService(typeof(IChangeNamespaceService), LanguageNames.CSharp), Shared] -internal sealed class CSharpChangeNamespaceService : - AbstractChangeNamespaceService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpChangeNamespaceService() : + AbstractChangeNamespaceService< + CompilationUnitSyntax, + MemberDeclarationSyntax, + BaseNamespaceDeclarationSyntax, + NameSyntax, + SimpleNameSyntax, + QualifiedCrefSyntax> { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpChangeNamespaceService() - { - } + public override AbstractReducer NameReducer { get; } = new CSharpNameReducer(); protected override async Task> GetValidContainersFromAllLinkedDocumentsAsync( Document document, diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs index 0dcbb8db822a5..2ebf4708298b3 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpSyncNamespaceCodeRefactoringProvider.cs @@ -17,15 +17,11 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.SyncNamespace; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.SyncNamespace), Shared] -internal sealed class CSharpSyncNamespaceCodeRefactoringProvider - : AbstractSyncNamespaceCodeRefactoringProvider +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class CSharpSyncNamespaceCodeRefactoringProvider() + : AbstractSyncNamespaceCodeRefactoringProvider { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CSharpSyncNamespaceCodeRefactoringProvider() - { - } - protected override async Task TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken) { if (!span.IsEmpty) @@ -58,7 +54,4 @@ public CSharpSyncNamespaceCodeRefactoringProvider() return null; } - - protected override string EscapeIdentifier(string identifier) - => identifier.EscapeIdentifier(); } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 76cdcd8c624fc..214209a105eb6 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -31,9 +30,6 @@ internal sealed class AwaitCompletionProvider() : AbstractAwaitCompletionProvide public override ImmutableHashSet TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList; - protected override bool IsAwaitKeywordContext(SyntaxContext syntaxContext) - => base.IsAwaitKeywordContext(syntaxContext); - /// /// Gets the span start where async keyword should go. /// @@ -86,8 +82,13 @@ protected override int GetAsyncKeywordInsertionPosition(SyntaxNode declaration) // Don't change the return type if we don't understand it, or it already seems task-like. var taskLikeTypes = new KnownTaskTypes(semanticModel.Compilation); var returnType = semanticModel.GetTypeInfo(existingReturnType, cancellationToken).Type; - if (returnType is null or IErrorTypeSymbol || taskLikeTypes.IsTaskLike(returnType)) + if (returnType is null or IErrorTypeSymbol || + taskLikeTypes.IsTaskLike(returnType) || + returnType.OriginalDefinition.Equals(taskLikeTypes.IAsyncEnumerableOfTType) || + returnType.OriginalDefinition.Equals(taskLikeTypes.IAsyncEnumeratorOfTType)) + { return null; + } return $"{nameof(Task)}<{existingReturnType}>"; } diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs index 637a5ecff3a2b..55f612a4b698d 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs @@ -105,7 +105,10 @@ internal sealed class CSharpInlineTypeHintsService() : AbstractInlineTypeHintsSe if (IsValidType(type)) { var span = new TextSpan(collectionExpression.OpenBracketToken.SpanStart, 0); - return new(type, span, new TextChange(span, GetTypeDisplayString(type)), leadingSpace: true); + + // We pass null for the TextChange in collection expressions because + // inserting with the type is incorrect and will make the code uncompilable. + return new(type, span, textChange: null, leadingSpace: true); } } } diff --git a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj index 0b4b4acdfff34..b99a679a92aa3 100644 --- a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj +++ b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj @@ -48,6 +48,7 @@ + diff --git a/src/Features/CSharp/Portable/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProvider.cs b/src/Features/CSharp/Portable/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProvider.cs new file mode 100644 index 0000000000000..ceb7dd4c610f9 --- /dev/null +++ b/src/Features/CSharp/Portable/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProvider.cs @@ -0,0 +1,470 @@ +// 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.Composition; +using System.Diagnostics; +using System.Reflection.Metadata; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.SolutionExplorer; +using static Microsoft.CodeAnalysis.CSharp.FindSymbols.FindSymbolsUtilities; + +namespace Microsoft.CodeAnalysis.CSharp.SolutionExplorer; + +[ExportLanguageService(typeof(ISolutionExplorerSymbolTreeItemProvider), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpSolutionExplorerSymbolTreeItemProvider() + : AbstractSolutionExplorerSymbolTreeItemProvider< + CompilationUnitSyntax, + MemberDeclarationSyntax, + BaseNamespaceDeclarationSyntax, + EnumDeclarationSyntax, + TypeDeclarationSyntax> +{ + protected override SyntaxList GetMembers(CompilationUnitSyntax root) + => root.Members; + + protected override SyntaxList GetMembers(BaseNamespaceDeclarationSyntax baseNamespace) + => baseNamespace.Members; + + protected override SyntaxList GetMembers(TypeDeclarationSyntax typeDeclaration) + => typeDeclaration.Members; + + protected override bool TryAddType( + DocumentId documentId, MemberDeclarationSyntax member, ArrayBuilder items, StringBuilder nameBuilder) + { + switch (member) + { + case ExtensionBlockDeclarationSyntax extensionBlock: + AddExtensionBlock(extensionBlock); + return true; + + case TypeDeclarationSyntax typeDeclaration: + AddTypeDeclaration(typeDeclaration); + return true; + + case EnumDeclarationSyntax enumDeclaration: + AddEnumDeclaration(enumDeclaration); + return true; + + case DelegateDeclarationSyntax delegateDeclaration: + AddDelegateDeclaration(delegateDeclaration); + return true; + } + + return false; + + void AddExtensionBlock(ExtensionBlockDeclarationSyntax extensionBlock) + { + nameBuilder.Append("extension"); + AppendTypeParameterList(nameBuilder, extensionBlock.TypeParameterList); + AppendParameterList(nameBuilder, extensionBlock.ParameterList); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + Glyph.ClassPublic, + hasItems: extensionBlock.Members.Count > 0, + extensionBlock, + extensionBlock.Keyword)); + } + + void AddTypeDeclaration(TypeDeclarationSyntax typeDeclaration) + { + nameBuilder.Append(typeDeclaration.Identifier.ValueText); + AppendTypeParameterList(nameBuilder, typeDeclaration.TypeParameterList); + + var glyph = GlyphExtensions.GetGlyph( + GetDeclaredSymbolInfoKind(typeDeclaration), + GetAccessibility(typeDeclaration.GetRequiredParent(), typeDeclaration.Modifiers)); + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: typeDeclaration.Members.Count > 0, + typeDeclaration, + typeDeclaration.Identifier)); + } + + void AddEnumDeclaration(EnumDeclarationSyntax enumDeclaration) + { + var glyph = GlyphExtensions.GetGlyph( + DeclaredSymbolInfoKind.Enum, GetAccessibility(enumDeclaration.GetRequiredParent(), enumDeclaration.Modifiers)); + + items.Add(new( + documentId, + enumDeclaration.Identifier.ValueText, + glyph, + hasItems: enumDeclaration.Members.Count > 0, + enumDeclaration, + enumDeclaration.Identifier)); + } + + void AddDelegateDeclaration(DelegateDeclarationSyntax delegateDeclaration) + { + nameBuilder.Append(delegateDeclaration.Identifier.ValueText); + AppendTypeParameterList(nameBuilder, delegateDeclaration.TypeParameterList); + AppendParameterList(nameBuilder, delegateDeclaration.ParameterList); + + nameBuilder.Append(" : "); + AppendType(delegateDeclaration.ReturnType, nameBuilder); + + var glyph = GlyphExtensions.GetGlyph( + DeclaredSymbolInfoKind.Delegate, GetAccessibility(delegateDeclaration.GetRequiredParent(), delegateDeclaration.Modifiers)); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + delegateDeclaration, + delegateDeclaration.Identifier)); + } + } + + protected override void AddMemberDeclaration( + DocumentId documentId, MemberDeclarationSyntax member, ArrayBuilder items, StringBuilder nameBuilder) + { + switch (member) + { + case BaseFieldDeclarationSyntax fieldDeclaration: + AddFieldDeclaration(fieldDeclaration); + return; + + case MethodDeclarationSyntax methodDeclaration: + AddMethodDeclaration(methodDeclaration); + return; + + case OperatorDeclarationSyntax operatorDeclaration: + AddOperatorDeclaration(operatorDeclaration); + return; + + case ConversionOperatorDeclarationSyntax conversionOperatorDeclaration: + AddConversionOperatorDeclaration(conversionOperatorDeclaration); + return; + + case ConstructorDeclarationSyntax constructorDeclaration: + AddConstructorOrDestructorDeclaration(constructorDeclaration, constructorDeclaration.Identifier); + return; + + case DestructorDeclarationSyntax destructorDeclaration: + AddConstructorOrDestructorDeclaration(destructorDeclaration, destructorDeclaration.Identifier); + return; + + case PropertyDeclarationSyntax propertyDeclaration: + AddPropertyDeclaration(propertyDeclaration); + return; + + case EventDeclarationSyntax eventDeclaration: + AddEventDeclaration(eventDeclaration); + return; + + case IndexerDeclarationSyntax indexerDeclaration: + AddIndexerDeclaration(indexerDeclaration); + return; + } + + void AddMethodDeclaration(MethodDeclarationSyntax methodDeclaration) + { + nameBuilder.Append(methodDeclaration.Identifier.ValueText); + AppendTypeParameterList(nameBuilder, methodDeclaration.TypeParameterList); + AppendParameterList(nameBuilder, methodDeclaration.ParameterList); + nameBuilder.Append(" : "); + AppendType(methodDeclaration.ReturnType, nameBuilder); + + var accessibility = GetAccessibility(methodDeclaration.GetRequiredParent(), methodDeclaration.Modifiers); + var isExtension = methodDeclaration.IsParentKind(SyntaxKind.ExtensionBlockDeclaration) || + (methodDeclaration.ParameterList is { Parameters: [var parameter, ..] } && parameter.Modifiers.Any(SyntaxKind.ThisKeyword)); + var glyph = GlyphExtensions.GetGlyph( + isExtension ? DeclaredSymbolInfoKind.ExtensionMethod : DeclaredSymbolInfoKind.Method, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + methodDeclaration, + methodDeclaration.Identifier)); + } + + void AddFieldDeclaration(BaseFieldDeclarationSyntax fieldDeclaration) + { + foreach (var variable in fieldDeclaration.Declaration.Variables) + { + nameBuilder.Append(variable.Identifier.ValueText); + nameBuilder.Append(" : "); + AppendType(fieldDeclaration.Declaration.Type, nameBuilder); + + var accessibility = GetAccessibility(fieldDeclaration.GetRequiredParent(), fieldDeclaration.Modifiers); + var kind = fieldDeclaration is EventFieldDeclarationSyntax + ? DeclaredSymbolInfoKind.Event + : DeclaredSymbolInfoKind.Field; + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(kind, accessibility), + hasItems: false, + variable, + variable.Identifier)); + } + } + + void AddOperatorDeclaration(OperatorDeclarationSyntax operatorDeclaration) + { + nameBuilder.Append("operator "); + nameBuilder.Append(operatorDeclaration.OperatorToken.ToString()); + AppendParameterList(nameBuilder, operatorDeclaration.ParameterList); + nameBuilder.Append(" : "); + AppendType(operatorDeclaration.ReturnType, nameBuilder); + + var accessibility = GetAccessibility(operatorDeclaration.GetRequiredParent(), operatorDeclaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Operator, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + operatorDeclaration, + operatorDeclaration.OperatorToken)); + } + + void AddConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax operatorDeclaration) + { + nameBuilder.Append(operatorDeclaration.ImplicitOrExplicitKeyword.Kind() == SyntaxKind.ImplicitKeyword + ? "implicit operator " + : "explicit operator "); + AppendType(operatorDeclaration.Type, nameBuilder); + AppendParameterList(nameBuilder, operatorDeclaration.ParameterList); + + var accessibility = GetAccessibility(operatorDeclaration.GetRequiredParent(), operatorDeclaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Operator, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + operatorDeclaration, + operatorDeclaration.Type.GetFirstToken())); + } + + void AddConstructorOrDestructorDeclaration(BaseMethodDeclarationSyntax declaration, SyntaxToken identifier) + { + if (declaration.Kind() == SyntaxKind.DestructorDeclaration) + nameBuilder.Append('~'); + + nameBuilder.Append(identifier.ValueText); + AppendParameterList(nameBuilder, declaration.ParameterList); + + var accessibility = GetAccessibility(declaration.GetRequiredParent(), declaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Constructor, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + declaration, + identifier)); + } + + void AddPropertyDeclaration(PropertyDeclarationSyntax propertyDeclaration) + { + nameBuilder.Append(propertyDeclaration.Identifier.ValueText); + nameBuilder.Append(" : "); + AppendType(propertyDeclaration.Type, nameBuilder); + + var accessibility = GetAccessibility(propertyDeclaration.GetRequiredParent(), propertyDeclaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Property, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + propertyDeclaration, + propertyDeclaration.Identifier)); + } + + void AddEventDeclaration(EventDeclarationSyntax eventDeclaration) + { + nameBuilder.Append(eventDeclaration.Identifier.ValueText); + nameBuilder.Append(" : "); + AppendType(eventDeclaration.Type, nameBuilder); + + var accessibility = GetAccessibility(eventDeclaration.GetRequiredParent(), eventDeclaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Event, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + eventDeclaration, + eventDeclaration.Identifier)); + } + + void AddIndexerDeclaration(IndexerDeclarationSyntax indexerDeclaration) + { + nameBuilder.Append("this"); + AppendCommaSeparatedList( + nameBuilder, "[", "]", + indexerDeclaration.ParameterList.Parameters, + static (parameter, nameBuilder) => AppendType(parameter.Type, nameBuilder)); + nameBuilder.Append(" : "); + AppendType(indexerDeclaration.Type, nameBuilder); + + var accessibility = GetAccessibility(indexerDeclaration.GetRequiredParent(), indexerDeclaration.Modifiers); + var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Indexer, accessibility); + + items.Add(new( + documentId, + nameBuilder.ToStringAndClear(), + glyph, + hasItems: false, + indexerDeclaration, + indexerDeclaration.ThisKeyword)); + } + } + + protected override void AddEnumDeclarationMembers( + DocumentId documentId, + EnumDeclarationSyntax enumDeclaration, + ArrayBuilder items, + CancellationToken cancellationToken) + { + foreach (var member in enumDeclaration.Members) + { + cancellationToken.ThrowIfCancellationRequested(); + items.Add(new( + documentId, + member.Identifier.ValueText, + Glyph.EnumMemberPublic, + hasItems: false, + member, + member.Identifier)); + } + } + + private static void AppendTypeParameterList( + StringBuilder builder, + TypeParameterListSyntax? typeParameterList) + { + AppendCommaSeparatedList( + builder, "<", ">", typeParameterList, + static typeParameterList => typeParameterList.Parameters, + static (parameter, builder) => builder.Append(parameter.Identifier.ValueText)); + } + + private static void AppendParameterList( + StringBuilder builder, + ParameterListSyntax? parameterList) + { + AppendCommaSeparatedList( + builder, "(", ")", parameterList, + static parameterList => parameterList.Parameters, + static (parameter, builder) => AppendType(parameter.Type, builder)); + } + + private static void AppendType(TypeSyntax? type, StringBuilder builder) + { + if (type is null) + return; + + if (type is ArrayTypeSyntax arrayType) + { + AppendType(arrayType.ElementType, builder); + foreach (var rankSpecifier in arrayType.RankSpecifiers) + { + builder.Append('['); + AppendCommaSeparatedList( + builder, "", "", rankSpecifier.Sizes, + static (_, _) => { }, ","); + builder.Append(']'); + } + } + else if (type is PointerTypeSyntax pointerType) + { + AppendType(pointerType.ElementType, builder); + builder.Append('*'); + } + else if (type is NullableTypeSyntax nullableType) + { + AppendType(nullableType.ElementType, builder); + builder.Append('?'); + } + else if (type is TupleTypeSyntax tupleType) + { + AppendCommaSeparatedList( + builder, "(", ")", tupleType.Elements, AppendTupleElement); + } + else if (type is RefTypeSyntax refType) + { + builder.Append("ref "); + AppendType(refType.Type, builder); + } + else if (type is ScopedTypeSyntax scopedType) + { + builder.Append("scoped "); + AppendType(scopedType.Type, builder); + } + else if (type is PredefinedTypeSyntax predefinedType) + { + builder.Append(predefinedType.ToString()); + } + else if (type is FunctionPointerTypeSyntax functionPointerType) + { + builder.Append("delegate*"); + AppendCommaSeparatedList( + builder, "<", ">", functionPointerType.ParameterList.Parameters, + static (parameter, builder) => AppendType(parameter.Type, builder)); + } + else if (type is OmittedTypeArgumentSyntax) + { + // nothing to do here. + } + else if (type is QualifiedNameSyntax qualifiedName) + { + AppendType(qualifiedName.Right, builder); + } + else if (type is AliasQualifiedNameSyntax aliasQualifiedName) + { + AppendType(aliasQualifiedName.Name, builder); + } + else if (type is IdentifierNameSyntax identifierName) + { + builder.Append(identifierName.Identifier.ValueText); + } + else if (type is GenericNameSyntax genericName) + { + builder.Append(genericName.Identifier.ValueText); + AppendCommaSeparatedList( + builder, "<", ">", genericName.TypeArgumentList.Arguments, AppendType); + } + else + { + Debug.Fail("Unhandled type: " + type.GetType().FullName); + } + } + + private static void AppendTupleElement(TupleElementSyntax tupleElement, StringBuilder builder) + { + AppendType(tupleElement.Type, builder); + if (tupleElement.Identifier != default) + { + builder.Append(' '); + builder.Append(tupleElement.Identifier.ValueText); + } + } +} diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs index ae41ca3c4801f..3271d0b4b302e 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs @@ -461,6 +461,156 @@ void M() |]", new TestParameters(options: options)); } + [Theory] + [InlineData("event", "EventHandler")] + [InlineData("static", "int")] + [WorkItem("https://github.com/dotnet/roslyn/issues/78786")] + public async Task TestRemoveDiagnosticSuppression_Attribute_MultiVariableDeclaration(string keyword, string type) + { + await TestInRegularAndScript1Async( + $$""" + public class C + { + [|[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "")]|] + public {{keyword}} {{type}} A, B; + } + """, + $$""" + public class C + { + public {{keyword}} {{type}} A, B; + } + """); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78786")] + public async Task TestRemoveDiagnosticSuppression_Attribute_PartialMethodDefinition() + { + await TestInRegularAndScript1Async( + """ + public partial class C + { + [|[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "")]|] + partial void M(); + } + + public partial class C + { + partial void M() + { + } + } + """, + """ + public partial class C + { + partial void M(); + } + + public partial class C + { + partial void M() + { + } + } + """); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78786")] + public async Task TestRemoveDiagnosticSuppression_Attribute_PartialMethodImplementation() + { + await TestInRegularAndScript1Async( + """ + public partial class C + { + partial void M(); + } + + public partial class C + { + [|[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "")]|] + partial void M() + { + } + } + """, + """ + public partial class C + { + partial void M(); + } + + public partial class C + { + partial void M() + { + } + } + """); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78786")] + public async Task TestRemoveDiagnosticSuppression_Attribute_PartialPropertyDefinition() + { + await TestInRegularAndScript1Async( + """ + public partial class C + { + [|[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "")]|] + partial int P { get; } + } + + public partial class C + { + partial int P => 5230; + } + """, + """ + public partial class C + { + partial int P { get; } + } + + public partial class C + { + partial int P => 5230; + } + """); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78786")] + public async Task TestRemoveDiagnosticSuppression_Attribute_PartialPropertyImplementation() + { + await TestInRegularAndScript1Async( + """ + public partial class C + { + partial int P { get; } + } + + public partial class C + { + [|[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "")]|] + partial int P => 5230; + } + """, + """ + public partial class C + { + partial int P { get; } + } + + public partial class C + { + partial int P => 5230; + } + """); + } + [Fact] public async Task TestDoNotRemoveDiagnosticSuppression_Attribute_OnPartialDeclarations() { diff --git a/src/Features/CSharpTest/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProviderTests.cs b/src/Features/CSharpTest/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProviderTests.cs new file mode 100644 index 0000000000000..8cf2b5896f5d8 --- /dev/null +++ b/src/Features/CSharpTest/SolutionExplorer/CSharpSolutionExplorerSymbolTreeItemProviderTests.cs @@ -0,0 +1,316 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.SolutionExplorer; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SolutionExplorer; + +[UseExportProvider, Trait(Traits.Feature, Traits.Features.SolutionExplorer)] +public sealed class CSharpSolutionExplorerSymbolTreeItemProviderTests + : AbstractSolutionExplorerSymbolTreeItemProviderTests +{ + protected override TestWorkspace CreateWorkspace(string code) + { + return TestWorkspace.CreateCSharp( + code, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)); + } + + private Task TestCompilationUnit( + string code, string expected) + { + return TestNode(code, expected); + } + + [Fact] + public async Task TestEmptyFile() + { + await TestCompilationUnit("", ""); + } + + [Fact] + public async Task TestTopLevelClass() + { + await TestCompilationUnit(""" + class [|C|] + { + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + """); + } + + [Fact] + public async Task TestTwoTopLevelTypes() + { + await TestCompilationUnit(""" + class [|C|] + { + } + + class [|D|] + { + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + Name="D" Glyph=ClassInternal HasItems=False + """); + } + + [Fact] + public async Task TestDelegatesAndEnums() + { + await TestCompilationUnit(""" + delegate string [|D|](int x); + + enum [|E|] + { + } + """, """ + Name="D(int) : string" Glyph=DelegateInternal HasItems=False + Name="E" Glyph=EnumInternal HasItems=False + """); + } + + [Fact] + public async Task TestTypesInBlockNamespace() + { + await TestCompilationUnit(""" + namespace N + { + class [|C|] + { + } + + class [|D|] + { + } + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + Name="D" Glyph=ClassInternal HasItems=False + """); + } + + [Fact] + public async Task TestTypesInFileScopedNamespace() + { + await TestCompilationUnit(""" + namespace N; + + class [|C|] + { + } + + class [|D|] + { + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + Name="D" Glyph=ClassInternal HasItems=False + """); + } + + [Fact] + public async Task TestTypesAcrossNamespaces() + { + await TestCompilationUnit(""" + class [|C|] + { + } + + namespace N + { + class [|D|] + { + } + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + Name="D" Glyph=ClassInternal HasItems=False + """); + } + + [Theory, CombinatorialData] + public async Task TestTypePermutations( + [CombinatorialValues("Public", "Private", "Protected", "Internal")] string accessibility, + [CombinatorialValues("Record", "Class", "Interface", "Struct")] string type) + { + await TestCompilationUnit($$""" + {{accessibility.ToLowerInvariant()}} {{type.ToLowerInvariant()}} [|C|] + { + } + """, $$""" + Name="C" Glyph={{type switch { "Record" => "Class", "Struct" => "Structure", _ => type }}}{{accessibility}} HasItems=False + """); + } + + [Theory, CombinatorialData] + public async Task TestTypeHasItems( + [CombinatorialValues("Record", "Class", "Interface", "Struct")] string type) + { + await TestCompilationUnit($$""" + {{type.ToLowerInvariant()}} [|C|] + { + int i; + } + """, $$""" + Name="C" Glyph={{type switch { "Record" => "Class", "Struct" => "Structure", _ => type }}}Internal HasItems=True + """); + } + + [Fact] + public async Task TestEnumHasItems() + { + await TestCompilationUnit(""" + enum [|E|] + { + A, + B, + C + } + """, """ + Name="E" Glyph=EnumInternal HasItems=True + """); + } + + [Theory] + [InlineData("int", "int")] + [InlineData("int[]", "int[]")] + [InlineData("int[][]", "int[][]")] + [InlineData("int[,][,,]", "int[,][,,]")] + [InlineData("int*", "int*")] + [InlineData("int?", "int?")] + [InlineData("(int, string)", "(int, string)")] + [InlineData("(int a, string b)", "(int a, string b)")] + [InlineData("delegate*unmanaged[a]", "delegate*")] + [InlineData("A.B", "B")] + [InlineData("A::B", "B")] + [InlineData("A::B.C", "C")] + [InlineData("A", "A")] + [InlineData("A.B>", "B>")] + public async Task TestTypes( + string parameterType, string resultType) + { + await TestCompilationUnit($$""" + delegate void [|D|]({{parameterType}} x); + """, $$""" + Name="D({{resultType}}) : void" Glyph=DelegateInternal HasItems=False + """); + } + + [Fact] + public async Task TestGenericClass() + { + await TestCompilationUnit(""" + class [|C|] + { + } + """, """ + Name="C" Glyph=ClassInternal HasItems=False + """); + } + + [Fact] + public async Task TestGenericDelegate() + { + await TestCompilationUnit(""" + delegate void [|D|](); + """, """ + Name="D() : void" Glyph=DelegateInternal HasItems=False + """); + } + + [Fact] + public async Task TestEnumMembers() + { + await TestNode(""" + enum E + { + [|A|], [|B|], [|C|] + } + """, """ + Name="A" Glyph=EnumMemberPublic HasItems=False + Name="B" Glyph=EnumMemberPublic HasItems=False + Name="C" Glyph=EnumMemberPublic HasItems=False + """); + } + + [Fact] + public async Task TestClassMembers() + { + await TestNode(""" + class C + { + private int [|a|], [|b|]; + public P [|Prop|] => default; + internal [|C|]() { } + ~[|C|]() { } + + protected R [|this|][string s] => default; + private event Action [|A|] { } + public event Action [|B|], [|C|]; + + void [|M|](int a) { } + public void IInterface.[|O|]() { } + + public static C operator [|+|](C c1, int a) => default; + + internal static implicit operator [|int|](C c1) => default; + } + """, """ + Name="a : int" Glyph=FieldPrivate HasItems=False + Name="b : int" Glyph=FieldPrivate HasItems=False + Name="Prop : P" Glyph=PropertyPublic HasItems=False + Name="C()" Glyph=MethodInternal HasItems=False + Name="~C()" Glyph=MethodPrivate HasItems=False + Name="this[string] : R" Glyph=PropertyProtected HasItems=False + Name="A : Action" Glyph=EventPrivate HasItems=False + Name="B : Action" Glyph=EventPublic HasItems=False + Name="C : Action" Glyph=EventPublic HasItems=False + Name="M(int) : void" Glyph=MethodPrivate HasItems=False + Name="O() : void" Glyph=MethodPublic HasItems=False + Name="operator +(C, int) : C" Glyph=OperatorPublic HasItems=False + Name="implicit operator int(C)" Glyph=OperatorInternal HasItems=False + """); + } + + [Fact] + public async Task TestExtension1() + { + await TestNode(""" + static class C + { + [|extension|](int i) + { + } + + public static void [|M|](this int i) {} + } + """, """ + Name="extension(int)" Glyph=ClassPublic HasItems=False + Name="M(int) : void" Glyph=ExtensionMethodPublic HasItems=False + """); + } + + [Fact] + public async Task TestExtension2() + { + await TestNode(""" + static class C + { + extension(int i) + { + public void [|M|]() { } + } + } + """, """ + Name="M() : void" Glyph=ExtensionMethodPublic HasItems=False + """); + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index d78c5d574e0e7..2684264135022 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -34,7 +34,7 @@ namespace Microsoft.CodeAnalysis.ChangeNamespace; /// /// This intermediate class is used to hide method `TryGetReplacementReferenceSyntax` from . /// -internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService +internal abstract partial class AbstractChangeNamespaceService : IChangeNamespaceService { public abstract Task CanChangeNamespaceAsync(Document document, SyntaxNode container, CancellationToken cancellationToken); @@ -42,6 +42,8 @@ internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService public abstract Task TryChangeTopLevelNamespacesAsync(Document document, string targetNamespace, CancellationToken cancellationToken); + public abstract AbstractReducer NameReducer { get; } + /// /// Try to get a new node to replace given node, which is a reference to a top-level type declared inside the /// namespace to be changed. If this reference is the right side of a qualified name, the new node returned would @@ -57,11 +59,20 @@ internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService public abstract bool TryGetReplacementReferenceSyntax(SyntaxNode reference, ImmutableArray newNamespaceParts, ISyntaxFactsService syntaxFacts, [NotNullWhen(returnValue: true)] out SyntaxNode? old, [NotNullWhen(returnValue: true)] out SyntaxNode? @new); } -internal abstract class AbstractChangeNamespaceService +internal abstract partial class AbstractChangeNamespaceService< + TCompilationUnitSyntax, + TMemberDeclarationSyntax, + TNamespaceDeclarationSyntax, + TNameSyntax, + TSimpleNameSyntax, + TCrefSyntax> : AbstractChangeNamespaceService - where TNamespaceDeclarationSyntax : SyntaxNode where TCompilationUnitSyntax : SyntaxNode where TMemberDeclarationSyntax : SyntaxNode + where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax + where TNameSyntax : SyntaxNode + where TSimpleNameSyntax : TNameSyntax + where TCrefSyntax : SyntaxNode { private static readonly char[] s_dotSeparator = ['.']; @@ -125,9 +136,7 @@ public override async Task CanChangeNamespaceAsync(Document document, Synt var originalNamespaceDeclarations = await GetTopLevelNamespacesAsync(document, cancellationToken).ConfigureAwait(false); if (originalNamespaceDeclarations.Length == 0) - { return null; - } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var originalNamespaceName = semanticModel.GetRequiredDeclaredSymbol(originalNamespaceDeclarations.First(), cancellationToken).ToDisplayString(); @@ -139,12 +148,10 @@ public override async Task CanChangeNamespaceAsync(Document document, Synt for (var i = 0; i < originalNamespaceDeclarations.Length; i++) { var namespaceName = semanticModel.GetRequiredDeclaredSymbol(originalNamespaceDeclarations[i], cancellationToken).ToDisplayString(); + + // Skip all namespaces that didn't match the original namespace name that we were syncing. if (namespaceName != originalNamespaceName) - { - // Skip all namespaces that didn't match the original namespace name that - // we were syncing. continue; - } syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -194,16 +201,12 @@ public override async Task ChangeNamespaceAsync( var containersFromAllDocuments = await GetValidContainersFromAllLinkedDocumentsAsync(document, container, cancellationToken).ConfigureAwait(false); if (containersFromAllDocuments.IsDefault) - { return solution; - } // No action required if declared namespace already matches target. var declaredNamespace = GetDeclaredNamespace(container); if (syntaxFacts.StringComparer.Equals(targetNamespace, declaredNamespace)) - { return solution; - } // Annotate the container nodes so we can still find and modify them after syntax tree has changed. var annotatedSolution = await AnnotateContainersAsync(solution, containersFromAllDocuments, cancellationToken).ConfigureAwait(false); @@ -222,9 +225,8 @@ public override async Task ChangeNamespaceAsync( foreach (var documentId in documentIds) { - var (newSolution, refDocumentIds) = - await ChangeNamespaceInSingleDocumentAsync(solutionAfterNamespaceChange, documentId, declaredNamespace, targetNamespace, cancellationToken) - .ConfigureAwait(false); + var (newSolution, refDocumentIds) = await ChangeNamespaceInSingleDocumentAsync( + solutionAfterNamespaceChange, documentId, declaredNamespace, targetNamespace, cancellationToken).ConfigureAwait(false); solutionAfterNamespaceChange = newSolution; referenceDocuments.AddRange(refDocumentIds); } @@ -463,8 +465,8 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n } } - var documentWithNewNamespace = await FixDeclarationDocumentAsync(document, refLocationsInCurrentDocument, oldNamespace, newNamespace, cancellationToken) - .ConfigureAwait(false); + var documentWithNewNamespace = await FixDeclarationDocumentAsync( + document, refLocationsInCurrentDocument, oldNamespace, newNamespace, cancellationToken).ConfigureAwait(false); var solutionWithChangedNamespace = documentWithNewNamespace.Project.Solution; var refLocationsInSolution = refLocationsInOtherDocuments @@ -501,15 +503,6 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n return (solutionWithFixedReferences, refLocationGroups.SelectAsArray(g => g.Key)); } - private readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod) - { - public ReferenceLocation ReferenceLocation { get; } = location; - - public bool IsReferenceToExtensionMethod { get; } = isReferenceToExtensionMethod; - - public Document Document => ReferenceLocation.Document; - } - private static async Task> FindReferenceLocationsForSymbolAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) { @@ -631,9 +624,49 @@ private async Task FixDeclarationDocumentAsync( var services = documentWithAddedImports.Project.Solution.Services; root = Formatter.Format(root, Formatter.Annotation, services, documentOptions.FormattingOptions, cancellationToken); - root = root.WithAdditionalAnnotations(Simplifier.Annotation); + using var _ = PooledHashSet.GetInstance(out var allNamespaceNameParts); + allNamespaceNameParts.AddRange(oldNamespaceParts); + allNamespaceNameParts.AddRange(newNamespaceParts); + + var syntaxFacts = document.GetRequiredLanguageService(); + root = AddSimplifierAnnotationToPotentialReferences(syntaxFacts, root, allNamespaceNameParts); + var formattedDocument = documentWithAddedImports.WithSyntaxRoot(root); - return await Simplifier.ReduceAsync(formattedDocument, documentOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); + return await SimplifyTypeNamesAsync(formattedDocument, documentOptions, cancellationToken).ConfigureAwait(false); + } + + private static SyntaxNode AddSimplifierAnnotationToPotentialReferences( + ISyntaxFactsService syntaxFacts, SyntaxNode root, HashSet allNamespaceNameParts) + { + // Find all identifiers in this tree that use at least one of the namespace names of either the old or new + // namespace. Mark those as needing potential complexification/simplification to preserve meaning. + // + // Note: we could go further here and actually bind these nodes to make sure they are actually references + // to one of the namespaces in question. But that doesn't seem super necessary as the chance that these names + // are actually to something else *and* they would reduce without issue seems very low. This can be revisited + // if we get feedback on this. + + using var _ = PooledHashSet.GetInstance(out var namesToUpdate); + foreach (var descendent in root.DescendantNodes(descendIntoTrivia: true)) + { + if (descendent is TSimpleNameSyntax simpleName && + allNamespaceNameParts.Contains(syntaxFacts.GetIdentifierOfSimpleName(simpleName).ValueText)) + { + namesToUpdate.Add(GetHighestNameOrCref(simpleName)); + } + } + + return root.ReplaceNodes( + namesToUpdate, + (_, current) => current.WithAdditionalAnnotations(Simplifier.Annotation)); + + static SyntaxNode GetHighestNameOrCref(TNameSyntax name) + { + while (name.Parent is TNameSyntax parentName) + name = parentName; + + return name.Parent is TCrefSyntax ? name.Parent : name; + } } private static async Task FixReferencingDocumentAsync( @@ -651,9 +684,8 @@ private static async Task FixReferencingDocumentAsync( var newNamespaceParts = GetNamespaceParts(newNamespace); - var (documentWithRefFixed, containers) = - await FixReferencesAsync(document, changeNamespaceService, addImportService, refLocations, newNamespaceParts, cancellationToken) - .ConfigureAwait(false); + var (documentWithRefFixed, containers) = await FixReferencesAsync( + document, changeNamespaceService, addImportService, refLocations, newNamespaceParts, cancellationToken).ConfigureAwait(false); var documentOptions = await document.GetCodeCleanupOptionsAsync(cancellationToken).ConfigureAwait(false); @@ -666,10 +698,24 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref cancellationToken).ConfigureAwait(false); // Need to invoke formatter explicitly since we are doing the diff merge ourselves. - var formattedDocument = await Formatter.FormatAsync(documentWithAdditionalImports, Formatter.Annotation, documentOptions.FormattingOptions, cancellationToken) - .ConfigureAwait(false); + var formattedDocument = await Formatter.FormatAsync( + documentWithAdditionalImports, Formatter.Annotation, documentOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); - return await Simplifier.ReduceAsync(formattedDocument, documentOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); + return await SimplifyTypeNamesAsync(formattedDocument, documentOptions, cancellationToken).ConfigureAwait(false); + } + + private static async Task SimplifyTypeNamesAsync( + Document document, CodeCleanupOptions documentOptions, CancellationToken cancellationToken) + { + var changeNamespaceService = document.GetRequiredLanguageService(); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var service = document.GetRequiredLanguageService(); + return await service.ReduceAsync( + document, + [new TextSpan(0, text.Length)], + documentOptions.SimplifierOptions, + [changeNamespaceService.NameReducer], + cancellationToken).ConfigureAwait(false); } /// @@ -708,9 +754,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref // it will be handled properly because it is one of the reference to the type symbol. Otherwise, we don't // attempt to make a potential fix, and user might end up with errors as a result. if (refLoc.ReferenceLocation.Alias != null) - { continue; - } // Other documents in the solution might have changed after we calculated those ReferenceLocation, // so we can't trust anything to be still up-to-date except their spans. @@ -743,9 +787,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref } foreach (var container in containers) - { editor.TrackNode(container); - } var fixedDocument = editor.GetChangedDocument(); root = await fixedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -858,7 +900,7 @@ private SyntaxNodeSpanStartComparer() { } - public static SyntaxNodeSpanStartComparer Instance { get; } = new SyntaxNodeSpanStartComparer(); + public static SyntaxNodeSpanStartComparer Instance { get; } = new(); public int Compare(SyntaxNode? x, SyntaxNode? y) { diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.cs index 02e2bb37a7f5a..aa2df2e5388e1 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.cs @@ -17,6 +17,18 @@ internal abstract partial class AbstractSyncNamespaceCodeRefactoringProvider + /// Try to get the node that can be used to trigger the refactoring based on current cursor position. + /// + /// + /// (1) a node of type node, if cursor in the name and it's the + /// only namespace declaration in the document. + /// (2) a node of type node, if the cursor is in the name of first + /// declaration in global namespace and there's no namespace declaration in this document. + /// (3) otherwise, null. + /// + protected abstract Task TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken); + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; @@ -28,9 +40,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var state = await State.CreateAsync(this, document, textSpan, cancellationToken).ConfigureAwait(false); if (state == null) - { return; - } // No move file action if rootnamespace isn't a prefix of current declared namespace if (state.RelativeDeclaredNamespace != null) @@ -79,18 +89,4 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactoring(solutionChangeAction, textSpan); } } - - /// - /// Try to get the node that can be used to trigger the refactoring based on current cursor position. - /// - /// - /// (1) a node of type node, if cursor in the name and it's the - /// only namespace declaration in the document. - /// (2) a node of type node, if the cursor is in the name of first - /// declaration in global namespace and there's no namespace declaration in this document. - /// (3) otherwise, null. - /// - protected abstract Task TryGetApplicableInvocationNodeAsync(Document document, TextSpan span, CancellationToken cancellationToken); - - protected abstract string EscapeIdentifier(string identifier); } diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/LocationForAffectedSymbol.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/LocationForAffectedSymbol.cs new file mode 100644 index 0000000000000..7832d47981ee0 --- /dev/null +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/LocationForAffectedSymbol.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.CodeAnalysis.FindSymbols; + +namespace Microsoft.CodeAnalysis.ChangeNamespace; + +internal abstract partial class AbstractChangeNamespaceService +{ + protected readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod) + { + public ReferenceLocation ReferenceLocation { get; } = location; + + public bool IsReferenceToExtensionMethod { get; } = isReferenceToExtensionMethod; + + public Document Document => ReferenceLocation.Document; + } +} diff --git a/src/Features/Core/Portable/Common/GlyphExtensions.cs b/src/Features/Core/Portable/Common/GlyphExtensions.cs index 8960874c2f5a0..413947c8e76d3 100644 --- a/src/Features/Core/Portable/Common/GlyphExtensions.cs +++ b/src/Features/Core/Portable/Common/GlyphExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Tags; @@ -244,4 +245,55 @@ public static Accessibility GetAccessibility(ReadOnlySpan tags) return Accessibility.NotApplicable; } + + public static Glyph GetGlyph(DeclaredSymbolInfoKind kind, Accessibility accessibility) + { + // Glyphs are stored in this order: + // ClassPublic, + // ClassProtected, + // ClassPrivate, + // ClassInternal, + + var rawGlyph = GetPublicGlyph(kind); + + switch (accessibility) + { + case Accessibility.Private: + rawGlyph += (Glyph.ClassPrivate - Glyph.ClassPublic); + break; + case Accessibility.Internal: + rawGlyph += (Glyph.ClassInternal - Glyph.ClassPublic); + break; + case Accessibility.Protected: + case Accessibility.ProtectedOrInternal: + case Accessibility.ProtectedAndInternal: + rawGlyph += (Glyph.ClassProtected - Glyph.ClassPublic); + break; + } + + return rawGlyph; + } + + private static Glyph GetPublicGlyph(DeclaredSymbolInfoKind kind) + => kind switch + { + DeclaredSymbolInfoKind.Class => Glyph.ClassPublic, + DeclaredSymbolInfoKind.Constant => Glyph.ConstantPublic, + DeclaredSymbolInfoKind.Constructor => Glyph.MethodPublic, + DeclaredSymbolInfoKind.Delegate => Glyph.DelegatePublic, + DeclaredSymbolInfoKind.Enum => Glyph.EnumPublic, + DeclaredSymbolInfoKind.EnumMember => Glyph.EnumMemberPublic, + DeclaredSymbolInfoKind.Event => Glyph.EventPublic, + DeclaredSymbolInfoKind.ExtensionMethod => Glyph.ExtensionMethodPublic, + DeclaredSymbolInfoKind.Field => Glyph.FieldPublic, + DeclaredSymbolInfoKind.Indexer => Glyph.PropertyPublic, + DeclaredSymbolInfoKind.Interface => Glyph.InterfacePublic, + DeclaredSymbolInfoKind.Method => Glyph.MethodPublic, + DeclaredSymbolInfoKind.Module => Glyph.ModulePublic, + DeclaredSymbolInfoKind.Operator => Glyph.OperatorPublic, + DeclaredSymbolInfoKind.Property => Glyph.PropertyPublic, + DeclaredSymbolInfoKind.Struct => Glyph.StructurePublic, + DeclaredSymbolInfoKind.RecordStruct => Glyph.StructurePublic, + _ => Glyph.ClassPublic, + }; } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs index d8cae4d6fa84e..4d81d2c136d3a 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs @@ -64,9 +64,6 @@ protected AbstractAwaitCompletionProvider(ISyntaxFacts syntaxFacts) protected abstract SyntaxNode? GetExpressionToPlaceAwaitInFrontOf(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken); protected abstract SyntaxToken? GetDotTokenLeftOfPosition(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken); - protected virtual bool IsAwaitKeywordContext(SyntaxContext syntaxContext) - => syntaxContext.IsAwaitKeywordContext; - private static bool IsConfigureAwaitable(Compilation compilation, ITypeSymbol symbol) { var originalDefinition = symbol.OriginalDefinition; @@ -89,7 +86,7 @@ public sealed override async Task ProvideCompletionsAsync(CompletionContext cont var syntaxContext = await context.GetSyntaxContextWithExistingSpeculativeModelAsync(document, cancellationToken).ConfigureAwait(false); - var isAwaitKeywordContext = IsAwaitKeywordContext(syntaxContext); + var isAwaitKeywordContext = syntaxContext.IsAwaitKeywordContext; var dotAwaitContext = GetDotAwaitKeywordContext(syntaxContext, cancellationToken); if (!isAwaitKeywordContext && dotAwaitContext == DotAwaitContext.None) return; diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs index 2d25f11a38cf6..34a71d8cdeb63 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers; internal static partial class ExtensionMethodImportCompletionHelper { - private sealed class SymbolComputer + private sealed partial class SymbolComputer { private readonly int _position; private readonly Document _originatingDocument; @@ -305,7 +305,7 @@ private void GetExtensionMethodsForSymbolsFromSameCompilation( // to the given receiver type and save the result. if (!cachedResult) { - var reducedMethodSymbol = methodSymbols.First().ReduceExtensionMethod(_receiverTypeSymbol); + var reducedMethodSymbol = TryReduceExtensionMethod(methodSymbols.First(), _receiverTypeSymbol); cachedResult = reducedMethodSymbol != null; _checkedReceiverTypes[receiverType] = cachedResult; } @@ -323,6 +323,25 @@ private void GetExtensionMethodsForSymbolsFromSameCompilation( } } + private static IMethodSymbol? TryReduceExtensionMethod(IMethodSymbol methodSymbol, ITypeSymbol receiverTypeSymbol) + { + // First defer to compiler to try to reduce this. + var reduced = methodSymbol.ReduceExtensionMethod(receiverTypeSymbol); + if (reduced is null) + return null; + + // Compiler is sometimes lenient with reduction, especially in cases of generic. Do another pass ourselves + // to see if we should filter this out. + if (methodSymbol.Parameters is [var extensionParameter, ..] && + extensionParameter.Type is ITypeParameterSymbol { TypeParameterKind: TypeParameterKind.Method } typeParameter) + { + if (!CheckConstraints(receiverTypeSymbol, typeParameter)) + return null; + } + + return reduced; + } + private MultiDictionary GetPotentialMatchingSymbolsFromAssembly( IAssemblySymbol assembly, MultiDictionary extensionMethodFilter, diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer_Constraints.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer_Constraints.cs new file mode 100644 index 0000000000000..9eabfb6145581 --- /dev/null +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer_Constraints.cs @@ -0,0 +1,120 @@ +// 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 Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.Completion.Providers; + +internal static partial class ExtensionMethodImportCompletionHelper +{ + private sealed partial class SymbolComputer + { + private static bool CheckConstraints(ITypeSymbol receiverTypeSymbol, ITypeParameterSymbol typeParameter) + { + // An extension on a method type parameter. These often have constraints on them. Ensure that the + // receiver feels at least plausibly usable as the argument. + if (!SatisfiesBaseTypeConstraint(receiverTypeSymbol, typeParameter)) + return false; + + if (!SatisfiesInterfaceConstraint(receiverTypeSymbol, typeParameter)) + return false; + + // Note: we could add more checks here. Like class/struct/new()/unmanaged/etc. constraints + return true; + } + + private static bool SatisfiesInterfaceConstraint(ITypeSymbol receiverTypeSymbol, ITypeParameterSymbol typeParameter) + => CheckConstraints(receiverTypeSymbol, typeParameter, TypeKind.Interface, static type => type.GetAllInterfacesIncludingThis()); + + private static bool SatisfiesBaseTypeConstraint(ITypeSymbol receiverTypeSymbol, ITypeParameterSymbol typeParameter) + => CheckConstraints(receiverTypeSymbol, typeParameter, TypeKind.Class, static type => type.GetBaseTypesAndThis()); + + private static IEnumerable GetAllTypeParameterConstraintTypes( + ITypeParameterSymbol typeParameter, + TypeKind typeKind, + Func> getInheritanceTypes) + { + using var _ = ArrayBuilder.GetInstance(out var typeParameterStack); + typeParameterStack.Push(typeParameter); + + while (typeParameterStack.TryPop(out var currentTypeParameter)) + { + foreach (var constraintType in currentTypeParameter.ConstraintTypes) + { + // Type parameter is constrained to another type parameter, add that other parameter to the list + // to check after this one. + if (constraintType is ITypeParameterSymbol toCheck) + { + typeParameterStack.Push(toCheck); + continue; + } + + if (constraintType.TypeKind == typeKind) + { + foreach (var baseType in GetAllTypes(constraintType, typeKind, getInheritanceTypes)) + yield return baseType; + } + } + } + } + + private static bool CheckConstraints( + ITypeSymbol receiverTypeSymbol, + ITypeParameterSymbol typeParameter, + TypeKind constraintTypeKind, + Func> getInheritanceTypes) + { + using var _ = ArrayBuilder.GetInstance(out var typeParameterStack); + typeParameterStack.Push(typeParameter); + + while (typeParameterStack.TryPop(out var currentTypeParameter)) + { + foreach (var constraintType in currentTypeParameter.ConstraintTypes) + { + // Type parameter is constrained to another type parameter, add that other parameter to the list + // to check after this one. + if (constraintType is ITypeParameterSymbol toCheck) + { + typeParameterStack.Push(toCheck); + continue; + } + + if (constraintType.TypeKind == constraintTypeKind) + { + var originalConstraintType = constraintType.OriginalDefinition; + foreach (var type in GetAllTypes(receiverTypeSymbol, constraintTypeKind, getInheritanceTypes)) + { + if (type.OriginalDefinition.Equals(originalConstraintType)) + return true; + } + + // Receiver type didn't derive from (and wasn't) the constraint type. + return false; + } + } + } + + return true; + } + + private static IEnumerable GetAllTypes( + ITypeSymbol type, TypeKind typeKind, Func> getInheritanceTypes) + { + if (type is ITypeParameterSymbol typeParameter) + { + // We have a a type parameter. We have to walk through its constraints (which may be other type parameters) + // to find all the named types in its inheritance hierarchy. + return GetAllTypeParameterConstraintTypes(typeParameter, typeKind, getInheritanceTypes); + } + else + { + // We have a named type. Just get the inheritance types directly from it. + return getInheritanceTypes(type); + } + } + } +} diff --git a/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs b/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs index f673a7957f21a..7fd7b7e09c751 100644 --- a/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs +++ b/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs @@ -8,11 +8,17 @@ namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue; [DataContract] -internal readonly struct ManagedHotReloadUpdates(ImmutableArray updates, ImmutableArray diagnostics) +internal readonly struct ManagedHotReloadUpdates(ImmutableArray updates, ImmutableArray diagnostics, ImmutableArray projectsToRebuild, ImmutableArray projectsToRestart) { [DataMember(Name = "updates")] public ImmutableArray Updates { get; } = updates; [DataMember(Name = "diagnostics")] public ImmutableArray Diagnostics { get; } = diagnostics; + + [DataMember(Name = "projectsToRebuild")] + public ImmutableArray ProjectsToRebuild { get; } = projectsToRebuild; + + [DataMember(Name = "projectsToRestart")] + public ImmutableArray ProjectsToRestart { get; } = projectsToRestart; } diff --git a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs index 8df5f9a04b589..62063e0fbca41 100644 --- a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs +++ b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs @@ -55,4 +55,6 @@ internal readonly record struct CopilotCodeFixAnalysis( [property: DataMember(Order = 3)] Dictionary DiagnosticIdToApplicationTime, [property: DataMember(Order = 4)] Dictionary> DiagnosticIdToProviderName, [property: DataMember(Order = 5)] Dictionary ProviderNameToApplicationTime, - [property: DataMember(Order = 6)] Dictionary ProviderNameToHasConflict); + [property: DataMember(Order = 6)] Dictionary ProviderNameToHasConflict, + [property: DataMember(Order = 7)] Dictionary ProviderNameToTotalCount, + [property: DataMember(Order = 8)] Dictionary ProviderNameToSuccessCount); diff --git a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs index 81eb127658742..832f1b7115d8d 100644 --- a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs +++ b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs @@ -120,6 +120,8 @@ public static IDisposable LogCopilotChangeAnalysis( d["CodeFixAnalysis_DiagnosticIdToProviderName"] = StringifyDictionary(analysisResult.CodeFixAnalysis.DiagnosticIdToProviderName); d["CodeFixAnalysis_ProviderNameToApplicationTime"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToApplicationTime); d["CodeFixAnalysis_ProviderNameToHasConflict"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToHasConflict); + d["CodeFixAnalysis_ProviderNameToTotalCount"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToTotalCount); + d["CodeFixAnalysis_ProviderNameToSuccessCount"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToSuccessCount); }, args: (featureId, accepted, proposalId, analysisResult)), cancellationToken); } diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index f5019cf430543..9187b29aa4500 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; @@ -255,22 +256,36 @@ private async Task ComputeCodeFixAnalysisAsync( var diagnosticIdToProviderName = new Dictionary>(); var providerNameToApplicationTime = new Dictionary(); var providerNameToHasConflict = new Dictionary(); + var providerNameToTotalCount = new Dictionary(); + var providerNameToSuccessCount = new Dictionary(); var totalApplicationTimeStopWatch = SharedStopwatch.StartNew(); - await ProducerConsumer<(CodeFixCollection collection, TimeSpan elapsedTime)>.RunParallelAsync( + await ProducerConsumer<(CodeFixCollection collection, bool success, TimeSpan elapsedTime)>.RunParallelAsync( codeFixCollections, produceItems: static async (codeFixCollection, callback, args, cancellationToken) => { - var (@this, solution, _, _, _, _, _) = args; + var (@this, solution, _, _, _, _, _, _, _) = args; var firstAction = GetFirstAction(codeFixCollection.Fixes[0]); var applicationTimeStopWatch = SharedStopwatch.StartNew(); - var result = await firstAction.GetPreviewOperationsAsync(solution, cancellationToken).ConfigureAwait(false); - callback((codeFixCollection, applicationTimeStopWatch.Elapsed)); + var success = true; + try + { + await firstAction + .GetPreviewOperationsAsync(solution, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + success = false; + } + + callback((codeFixCollection, success, applicationTimeStopWatch.Elapsed)); }, consumeItems: static async (values, args, cancellationToken) => { - var (@this, solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, providerNameToApplicationTime, providerNameToHasConflict) = args; + var (@this, solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, + providerNameToApplicationTime, providerNameToHasConflict, providerNameToTotalCount, providerNameToSuccessCount) = args; // Track which text span each code fix says it will be fixing. We can use this to efficiently determine // which codefixes 'conflict' with some other codefix (in that that multiple features think they can fix @@ -278,7 +293,7 @@ private async Task ComputeCodeFixAnalysisAsync( // order to have a good experience in such a case. var intervalTree = new SimpleMutableIntervalTree(new CodeFixCollectionIntervalIntrospector()); - await foreach (var (codeFixCollection, applicationTime) in values) + await foreach (var (codeFixCollection, success, applicationTime) in values) { var diagnosticId = codeFixCollection.FirstDiagnostic.Id; var providerName = GetProviderName(codeFixCollection); @@ -287,6 +302,10 @@ private async Task ComputeCodeFixAnalysisAsync( IncrementElapsedTime(diagnosticIdToApplicationTime, diagnosticId, applicationTime); diagnosticIdToProviderName.MultiAdd(diagnosticId, providerName); IncrementElapsedTime(providerNameToApplicationTime, providerName, applicationTime); + IncrementCount(providerNameToTotalCount, providerName); + + if (success) + IncrementCount(providerNameToSuccessCount, providerName); intervalTree.AddIntervalInPlace(codeFixCollection); } @@ -311,7 +330,8 @@ private async Task ComputeCodeFixAnalysisAsync( providerNameToHasConflict[providerName] = storedHasConflictValue || newHasConflictValue; } }, - args: (@this: this, newDocument.Project.Solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, providerNameToApplicationTime, providerNameToHasConflict), + args: (@this: this, newDocument.Project.Solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, + providerNameToApplicationTime, providerNameToHasConflict, providerNameToTotalCount, providerNameToSuccessCount), cancellationToken).ConfigureAwait(false); var totalApplicationTime = totalApplicationTimeStopWatch.Elapsed; @@ -322,7 +342,9 @@ private async Task ComputeCodeFixAnalysisAsync( diagnosticIdToApplicationTime, diagnosticIdToProviderName, providerNameToApplicationTime, - providerNameToHasConflict); + providerNameToHasConflict, + providerNameToTotalCount, + providerNameToSuccessCount); Task> ComputeCodeFixCollectionsAsync() { diff --git a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs index f1e7460824dba..c23829e2ebc11 100644 --- a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs +++ b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs @@ -457,9 +457,9 @@ await TryGetMatchingSourceTextAsync(log, sourceText, sourceFilePath, currentDocu } } - public void CommitChanges(Solution solution, ImmutableArray projectsToStale, ImmutableArray projectsToUnstale) + public void CommitChanges(Solution solution, ImmutableArray projectsToStale, IReadOnlyCollection projectsToUnstale) { - Contract.ThrowIfFalse(projectsToStale is [] || projectsToUnstale is []); + Debug.Assert(projectsToStale.Intersect(projectsToUnstale).IsEmpty()); lock (_guard) { diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index c1b4eabc70ff4..5bbc2faf124d9 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -82,7 +82,7 @@ internal sealed class DebuggingSession : IDisposable /// read lock is acquired before every operation that may access a baseline module/symbol reader /// and write lock when the baseline readers are being disposed. /// - private readonly ReaderWriterLockSlim _baselineAccessLock = new(); + private readonly ReaderWriterLockSlim _baselineContentAccessLock = new(); private bool _isDisposed; internal EditSession EditSession { get; private set; } @@ -168,7 +168,7 @@ public void Dispose() _cancellationSource.Dispose(); // Wait for all operations on baseline to finish before we dispose the readers. - _baselineAccessLock.EnterWriteLock(); + _baselineContentAccessLock.EnterWriteLock(); lock (_projectEmitBaselinesGuard) { @@ -179,8 +179,8 @@ public void Dispose() } } - _baselineAccessLock.ExitWriteLock(); - _baselineAccessLock.Dispose(); + _baselineContentAccessLock.ExitWriteLock(); + _baselineContentAccessLock.Dispose(); if (Interlocked.Exchange(ref _pendingUpdate, null) != null) { @@ -309,27 +309,26 @@ internal ImmutableList GetOrCreateEmitBaselines( Guid moduleId, Project baselineProject, Compilation baselineCompilation, - out ImmutableArray errors, + ArrayBuilder diagnostics, out ReaderWriterLockSlim baselineAccessLock) { - baselineAccessLock = _baselineAccessLock; + baselineAccessLock = _baselineContentAccessLock; ImmutableList? existingBaselines; lock (_projectEmitBaselinesGuard) { if (TryGetBaselinesContainingModuleVersion(moduleId, out existingBaselines)) { - errors = []; return existingBaselines; } } var outputs = GetCompilationOutputs(baselineProject); - if (!TryCreateInitialBaseline(baselineCompilation, outputs, baselineProject.Id, out errors, out var initialBaseline, out var debugInfoReaderProvider, out var metadataReaderProvider)) + if (!TryCreateInitialBaseline(baselineCompilation, outputs, baselineProject.Id, diagnostics, out var initialBaseline, out var debugInfoReaderProvider, out var metadataReaderProvider)) { // Unable to read the DLL/PDB at this point (it might be open by another process). // Don't cache the failure so that the user can attempt to apply changes again. - return existingBaselines ?? []; + return []; } lock (_projectEmitBaselinesGuard) @@ -359,7 +358,7 @@ private unsafe bool TryCreateInitialBaseline( Compilation compilation, CompilationOutputs compilationOutputs, ProjectId projectId, - out ImmutableArray errors, + ArrayBuilder diagnostics, [NotNullWhen(true)] out EmitBaseline? baseline, [NotNullWhen(true)] out DebugInformationReaderProvider? debugInfoReaderProvider, [NotNullWhen(true)] out MetadataReaderProvider? metadataReaderProvider) @@ -370,7 +369,6 @@ private unsafe bool TryCreateInitialBaseline( // Alternatively, we could drop the data once we are done with emitting the delta and re-emit the baseline again // when we need it next time and the module is loaded. - errors = []; baseline = null; debugInfoReaderProvider = null; metadataReaderProvider = null; @@ -413,7 +411,7 @@ private unsafe bool TryCreateInitialBaseline( SessionLog.Write($"Failed to create baseline for '{projectId.DebugName}': {e.Message}", LogMessageSeverity.Error); var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); - errors = [Diagnostic.Create(descriptor, Location.None, [fileBeingRead, e.Message])]; + diagnostics.Add(Diagnostic.Create(descriptor, Location.None, [fileBeingRead, e.Message])); } finally { @@ -523,7 +521,9 @@ public async ValueTask EmitSolutionUpdateAsync( // Make sure the solution snapshot has all source-generated documents up-to-date. solution = solution.WithUpToDateSourceGeneratorDocuments(solution.ProjectIds); - var solutionUpdate = await EditSession.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, updateId, cancellationToken).ConfigureAwait(false); + var solutionUpdate = await EditSession.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, updateId, runningProjects, cancellationToken).ConfigureAwait(false); + + var allowPartialUpdates = runningProjects.Any(p => p.Value.AllowPartialUpdate); solutionUpdate.Log(SessionLog, updateId); _lastModuleUpdatesLog = solutionUpdate.ModuleUpdates.Updates; @@ -531,14 +531,34 @@ public async ValueTask EmitSolutionUpdateAsync( switch (solutionUpdate.ModuleUpdates.Status) { case ModuleUpdateStatus.Ready: - // We have updates to be applied. The debugger will call Commit/Discard on the solution + Contract.ThrowIfTrue(solutionUpdate.ModuleUpdates.Updates.IsEmpty && solutionUpdate.ProjectsToRebuild.IsEmpty); + + // We have updates to be applied or processes to restart. The debugger will call Commit/Discard on the solution // based on whether the updates will be applied successfully or not. - StorePendingUpdate(new PendingSolutionUpdate( - solution, - solutionUpdate.ProjectsToStale, - solutionUpdate.ProjectBaselines, - solutionUpdate.ModuleUpdates.Updates, - solutionUpdate.NonRemappableRegions)); + + if (allowPartialUpdates) + { + StorePendingUpdate(new PendingSolutionUpdate( + solution, + solutionUpdate.ProjectsToStale, + solutionUpdate.ProjectsToRebuild, + solutionUpdate.ProjectBaselines, + solutionUpdate.ModuleUpdates.Updates, + solutionUpdate.NonRemappableRegions)); + } + else if (solutionUpdate.ProjectsToRebuild.IsEmpty) + { + // no rude edits + + StorePendingUpdate(new PendingSolutionUpdate( + solution, + solutionUpdate.ProjectsToStale, + // if partial updates are not allowed we don't treat rebuild as part of solution update: + projectsToRebuild: [], + solutionUpdate.ProjectBaselines, + solutionUpdate.ModuleUpdates.Updates, + solutionUpdate.NonRemappableRegions)); + } break; @@ -546,42 +566,29 @@ public async ValueTask EmitSolutionUpdateAsync( Contract.ThrowIfFalse(solutionUpdate.ModuleUpdates.Updates.IsEmpty); Contract.ThrowIfFalse(solutionUpdate.NonRemappableRegions.IsEmpty); + // Insignificant changes should not cause rebuilds/restarts: + Contract.ThrowIfFalse(solutionUpdate.ProjectsToRestart.IsEmpty); + Contract.ThrowIfFalse(solutionUpdate.ProjectsToRebuild.IsEmpty); + // No significant changes have been made. // Commit the solution to apply any insignificant changes that do not generate updates. LastCommittedSolution.CommitChanges(solution, projectsToStale: solutionUpdate.ProjectsToStale, projectsToUnstale: []); break; } - using var _ = ArrayBuilder.GetInstance(out var rudeEditDiagnostics); - foreach (var (projectId, documentsWithRudeEdits) in solutionUpdate.DocumentsWithRudeEdits.GroupBy(static e => e.Id.ProjectId).OrderBy(static id => id)) - { - foreach (var documentWithRudeEdits in documentsWithRudeEdits) - { - var document = await solution.GetDocumentAsync(documentWithRudeEdits.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - var tree = (document != null) ? await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false) : null; - rudeEditDiagnostics.Add(new(projectId, documentWithRudeEdits.RudeEdits.SelectAsArray(static (rudeEdit, tree) => rudeEdit.ToDiagnostic(tree), tree))); - } - } - - EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( - solution, - solutionUpdate.ModuleUpdates, - rudeEditDiagnostics, - runningProjects, - out var projectsToRestart, - out var projectsToRebuild); - // Note that we may return empty deltas if all updates have been deferred. // The debugger will still call commit or discard on the update batch. return new EmitSolutionUpdateResults() { Solution = solution, - ModuleUpdates = solutionUpdate.ModuleUpdates, + // If partial updates are disabled the debugger does not expect module updates when rude edits are reported: + ModuleUpdates = allowPartialUpdates || solutionUpdate.ProjectsToRebuild.IsEmpty + ? solutionUpdate.ModuleUpdates + : new ModuleUpdates(solutionUpdate.ModuleUpdates.Status, []), Diagnostics = solutionUpdate.Diagnostics, - RudeEdits = rudeEditDiagnostics.ToImmutable(), SyntaxError = solutionUpdate.SyntaxError, - ProjectsToRestart = projectsToRestart, - ProjectsToRebuild = projectsToRebuild + ProjectsToRestart = solutionUpdate.ProjectsToRestart, + ProjectsToRebuild = solutionUpdate.ProjectsToRebuild }; } @@ -590,6 +597,7 @@ public void CommitSolutionUpdate() ThrowIfDisposed(); ImmutableDictionary>? newNonRemappableRegions = null; + using var _ = PooledHashSet.GetInstance(out var projectsToRebuildTransitive); var pendingUpdate = RetrievePendingUpdate(); if (pendingUpdate is PendingSolutionUpdate pendingSolutionUpdate) @@ -605,18 +613,44 @@ from region in moduleRegions.Regions if (newNonRemappableRegions.IsEmpty) newNonRemappableRegions = null; - LastCommittedSolution.CommitChanges(pendingSolutionUpdate.Solution, projectsToStale: pendingSolutionUpdate.ProjectsToStale, projectsToUnstale: []); + var solution = pendingSolutionUpdate.Solution; + + // Once the project is rebuilt all its dependencies are going to be up-to-date. + var dependencyGraph = solution.GetProjectDependencyGraph(); + foreach (var projectId in pendingSolutionUpdate.ProjectsToRebuild) + { + projectsToRebuildTransitive.Add(projectId); + projectsToRebuildTransitive.AddRange(dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(projectId)); + } + + // Unstale all projects that will be up-to-date after rebuild. + LastCommittedSolution.CommitChanges(solution, projectsToStale: pendingSolutionUpdate.ProjectsToStale, projectsToUnstale: projectsToRebuildTransitive); + + foreach (var projectId in projectsToRebuildTransitive) + { + _editSessionTelemetry.LogUpdatedBaseline(solution.GetRequiredProject(projectId).State.ProjectInfo.Attributes.TelemetryId); + } } // update baselines: + + // Wait for all operations on baseline content to finish before we dispose the readers. + _baselineContentAccessLock.EnterWriteLock(); + lock (_projectEmitBaselinesGuard) { foreach (var updatedBaseline in pendingUpdate.ProjectBaselines) { _projectBaselines[updatedBaseline.ProjectId] = [.. _projectBaselines[updatedBaseline.ProjectId].Select(existingBaseline => existingBaseline.ModuleId == updatedBaseline.ModuleId ? updatedBaseline : existingBaseline)]; } + + // Discard any open baseline readers for projects that need to be rebuilt, + // so that the build can overwrite the underlying files. + DiscardProjectBaselinesNoLock(projectsToRebuildTransitive); } + _baselineContentAccessLock.ExitWriteLock(); + _editSessionTelemetry.LogCommitted(); // Restart edit session with no active statements (switching to run mode). @@ -629,6 +663,28 @@ public void DiscardSolutionUpdate() _ = RetrievePendingUpdate(); } + private void DiscardProjectBaselinesNoLock(IEnumerable projects) + { + foreach (var projectId in projects) + { + if (_projectBaselines.TryGetValue(projectId, out var projectBaselines)) + { + // remove all versions of modules associated with the project: + _projectBaselines.Remove(projectId); + + foreach (var projectBaseline in projectBaselines) + { + var (metadata, pdb) = _initialBaselineModuleReaders[projectBaseline.ModuleId]; + metadata.Dispose(); + pdb.Dispose(); + + _initialBaselineModuleReaders.Remove(projectBaseline.ModuleId); + } + } + } + } + + // TODO: remove once the debugger implements https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2459003 public void UpdateBaselines(Solution solution, ImmutableArray rebuiltProjects) { ThrowIfDisposed(); @@ -639,30 +695,15 @@ public void UpdateBaselines(Solution solution, ImmutableArray rebuilt LastCommittedSolution.CommitChanges(solution, projectsToStale: [], projectsToUnstale: rebuiltProjects); // Wait for all operations on baseline to finish before we dispose the readers. - _baselineAccessLock.EnterWriteLock(); + + _baselineContentAccessLock.EnterWriteLock(); lock (_projectEmitBaselinesGuard) { - foreach (var projectId in rebuiltProjects) - { - if (_projectBaselines.TryGetValue(projectId, out var projectBaselines)) - { - // remove all versions of modules associated with the project: - _projectBaselines.Remove(projectId); - - foreach (var projectBaseline in projectBaselines) - { - var (metadata, pdb) = _initialBaselineModuleReaders[projectBaseline.ModuleId]; - metadata.Dispose(); - pdb.Dispose(); - - _initialBaselineModuleReaders.Remove(projectBaseline.ModuleId); - } - } - } + DiscardProjectBaselinesNoLock(rebuiltProjects); } - _baselineAccessLock.ExitWriteLock(); + _baselineContentAccessLock.ExitWriteLock(); foreach (var projectId in rebuiltProjects) { diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs index 255cb5b4d1728..30ea313eb36ed 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs @@ -106,7 +106,7 @@ public static void Log(Data data, Action log, Func map["RudeEditsCount"] = editSessionData.RudeEdits.Length; - // Number of emit errors. + // Number of emit errors. These are any errors only produced during emitting deltas and do not include document analysis errors. map["EmitDeltaErrorIdCount"] = editSessionData.EmitErrorIds.Length; // False for Hot Reload session, true or missing for EnC session (missing in older data that did not have this property). diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentWithRudeEdits.cs b/src/Features/Core/Portable/EditAndContinue/DocumentWithRudeEdits.cs deleted file mode 100644 index 5759ea8026e56..0000000000000 --- a/src/Features/Core/Portable/EditAndContinue/DocumentWithRudeEdits.cs +++ /dev/null @@ -1,13 +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.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.EditAndContinue; - -internal readonly struct DocumentWithRudeEdits(DocumentId id, ImmutableArray rudeEdits) -{ - public DocumentId Id { get; } = id; - public ImmutableArray RudeEdits { get; } = rudeEdits; -} diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 65a6f9c4410dd..42ff3017dc2f3 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.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. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; @@ -14,18 +14,24 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; internal static class EditAndContinueDiagnosticDescriptors { - private const int GeneralDiagnosticBaseId = 1000; - private const int ModuleDiagnosticBaseId = 2000; - private static readonly int s_diagnosticBaseIndex; + private const string EncDiagnosticIdPrefix = "ENC"; + private const string RudeEditDiagnosticIdPrefix = EncDiagnosticIdPrefix + "0"; + private const string GeneralDiagnosticIdPrefix = EncDiagnosticIdPrefix + "1"; + private const string ModuleDiagnosticIdPrefix = EncDiagnosticIdPrefix + "2"; + + private static readonly int s_generalDiagnosticBaseIndex; private static readonly LocalizableResourceString s_rudeEditLocString; private static readonly LocalizableResourceString s_encLocString; private static readonly LocalizableResourceString s_encDisallowedByProjectLocString; private static readonly ImmutableArray s_descriptors; + private static readonly ImmutableHashSet s_noEffectDiagnosticIds; + + private static readonly string s_documentReadErrorId = GetDiagnosticId(EditAndContinueErrorCode.UnableToReadSourceFileOrPdb); // descriptors for diagnostics reported by the debugger: - private static Dictionary s_lazyModuleDiagnosticDescriptors; + private static Dictionary? s_lazyModuleDiagnosticDescriptors; private static readonly object s_moduleDiagnosticDescriptorsGuard; static EditAndContinueDiagnosticDescriptors() @@ -37,29 +43,40 @@ static EditAndContinueDiagnosticDescriptors() s_encDisallowedByProjectLocString = new LocalizableResourceString(nameof(FeaturesResources.EditAndContinueDisallowedByProject), FeaturesResources.ResourceManager, typeof(FeaturesResources)); var builder = ImmutableArray.CreateBuilder(); + var noEffectDiagnosticIds = ImmutableHashSet.CreateBuilder(); - void add(int index, int id, string resourceName, LocalizableResourceString title, DiagnosticSeverity severity) + void Add(int index, string prefix, int code, string resourceName, LocalizableResourceString title, DiagnosticSeverity severity, bool isNoEffect) { + // no-effect diagnostics should be warnings: + Debug.Assert(!isNoEffect || severity == DiagnosticSeverity.Warning); + if (index >= builder.Count) { builder.Count = index + 1; } + var id = GetDiagnosticId(prefix, code); + builder[index] = new DiagnosticDescriptor( - GetDiagnosticId(id), + id, title, messageFormat: new LocalizableResourceString(resourceName, FeaturesResources.ResourceManager, typeof(FeaturesResources)), DiagnosticCategory.EditAndContinue, severity, isEnabledByDefault: true, customTags: DiagnosticCustomTags.EditAndContinue); + + if (isNoEffect) + { + noEffectDiagnosticIds.Add(id); + } } - void AddRudeEdit(RudeEditKind kind, string resourceName, DiagnosticSeverity severity = DiagnosticSeverity.Error) - => add(GetDescriptorIndex(kind), (int)kind, resourceName, s_rudeEditLocString, severity); + void AddRudeEdit(RudeEditKind kind, string resourceName, DiagnosticSeverity severity = DiagnosticSeverity.Error, bool noEffect = false) + => Add(GetDescriptorIndex(kind), RudeEditDiagnosticIdPrefix, (int)kind, resourceName, s_rudeEditLocString, severity, noEffect); - void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, DiagnosticSeverity severity = DiagnosticSeverity.Error) - => add(GetDescriptorIndex(code), GeneralDiagnosticBaseId + (int)code, resourceName, s_encLocString, severity); + void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, DiagnosticSeverity severity = DiagnosticSeverity.Error, bool noEffect = false) + => Add(GetDescriptorIndex(code), GeneralDiagnosticIdPrefix, (int)code, resourceName, s_encLocString, severity, noEffect); // // rude edits @@ -148,7 +165,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.NotCapturingPrimaryConstructorParameter, nameof(FeaturesResources.Ceasing_to_capture_primary_constructor_parameter_0_of_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingAttribute, nameof(FeaturesResources.Changing_attribute_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingNameOrSignatureOfActiveMember, nameof(FeaturesResources.Changing_name_or_signature_of_0_that_contains_an_active_statement_requires_restarting_the_application)); - AddRudeEdit(RudeEditKind.UpdateMightNotHaveAnyEffect, nameof(FeaturesResources.Changing_0_might_not_have_any_effect_until_the_application_is_restarted), DiagnosticSeverity.Warning); + AddRudeEdit(RudeEditKind.UpdateMightNotHaveAnyEffect, nameof(FeaturesResources.Changing_0_might_not_have_any_effect_until_the_application_is_restarted), DiagnosticSeverity.Warning, noEffect: true); AddRudeEdit(RudeEditKind.TypeUpdateAroundActiveStatement, nameof(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.InsertOrMoveComInterfaceMember, nameof(FeaturesResources.Adding_or_moving_0_of_a_COM_interface_requires_restarting_the_application)); @@ -162,7 +179,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di // other Roslyn reported diagnostics: // - s_diagnosticBaseIndex = builder.Count; + s_generalDiagnosticBaseIndex = builder.Count; AddGeneralDiagnostic(EditAndContinueErrorCode.ErrorReadingFile, nameof(FeaturesResources.ErrorReadingFile)); AddGeneralDiagnostic(EditAndContinueErrorCode.CannotApplyChangesUnexpectedError, nameof(FeaturesResources.CannotApplyChangesUnexpectedError)); @@ -170,9 +187,10 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddGeneralDiagnostic(EditAndContinueErrorCode.DocumentIsOutOfSyncWithDebuggee, nameof(FeaturesResources.DocumentIsOutOfSyncWithDebuggee), DiagnosticSeverity.Warning); AddGeneralDiagnostic(EditAndContinueErrorCode.UnableToReadSourceFileOrPdb, nameof(FeaturesResources.UnableToReadSourceFileOrPdb)); AddGeneralDiagnostic(EditAndContinueErrorCode.AddingTypeRuntimeCapabilityRequired, nameof(FeaturesResources.ChangesRequiredSynthesizedType)); - AddGeneralDiagnostic(EditAndContinueErrorCode.UpdatingDocumentInStaleProject, nameof(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit), DiagnosticSeverity.Warning); + AddGeneralDiagnostic(EditAndContinueErrorCode.UpdatingDocumentInStaleProject, nameof(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit), DiagnosticSeverity.Warning, noEffect: true); s_descriptors = builder.ToImmutable(); + s_noEffectDiagnosticIds = noEffectDiagnosticIds.ToImmutable(); } internal static ImmutableArray GetDescriptors() @@ -193,7 +211,7 @@ internal static DiagnosticDescriptor GetModuleDiagnosticDescriptor(ManagedHotRel if (!s_lazyModuleDiagnosticDescriptors.TryGetValue(status, out var descriptor)) { s_lazyModuleDiagnosticDescriptors.Add(status, descriptor = new DiagnosticDescriptor( - $"ENC{ModuleDiagnosticBaseId + (int)status:D4}", + GetDiagnosticId(status), s_encLocString, s_encDisallowedByProjectLocString, DiagnosticCategory.EditAndContinue, @@ -206,15 +224,48 @@ internal static DiagnosticDescriptor GetModuleDiagnosticDescriptor(ManagedHotRel } } + private static string GetDiagnosticId(string prefix, int code) + => $"{prefix}{code:D3}"; + + private static string GetDiagnosticId(EditAndContinueErrorCode code) + => GetDiagnosticId(GeneralDiagnosticIdPrefix, (int)code); + + private static string GetDiagnosticId(ManagedHotReloadAvailabilityStatus status) + => GetDiagnosticId(ModuleDiagnosticIdPrefix, (int)status); + private static int GetDescriptorIndex(RudeEditKind kind) => (int)kind; private static int GetDescriptorIndex(EditAndContinueErrorCode errorCode) - => s_diagnosticBaseIndex + (int)errorCode; + => s_generalDiagnosticBaseIndex + (int)errorCode; + + public static bool IsEncDiagnostic(string diagnosticId) + => diagnosticId.Length > EncDiagnosticIdPrefix.Length && diagnosticId.StartsWith(EncDiagnosticIdPrefix, StringComparison.Ordinal) && diagnosticId[EncDiagnosticIdPrefix.Length] is >= '0' and <= '9'; - private static string GetDiagnosticId(int id) - => $"ENC{id:D4}"; + public static bool IsRudeEdit(string diagnosticId) + => diagnosticId.StartsWith(RudeEditDiagnosticIdPrefix, StringComparison.Ordinal); public static RudeEditKind GetRudeEditKind(string diagnosticId) - => diagnosticId.StartsWith("ENC", StringComparison.Ordinal) && int.TryParse(diagnosticId[3..], out var id) ? (RudeEditKind)id : RudeEditKind.None; + => IsRudeEdit(diagnosticId) && int.TryParse(diagnosticId[RudeEditDiagnosticIdPrefix.Length..], out var id) ? (RudeEditKind)id : RudeEditKind.None; + + public static bool IsDocumentReadError(this Diagnostic diagnostic) + => diagnostic.Id == s_documentReadErrorId; + + public static bool IsNoEffectDiagnostic(this Diagnostic diagnostic) + => s_noEffectDiagnosticIds.Contains(diagnostic.Id); + + public static bool IsEncDiagnostic(this Diagnostic diagnostic) + => IsEncDiagnostic(diagnostic.Id); + + public static bool IsRudeEdit(this Diagnostic diagnostic) + => IsRudeEdit(diagnostic.Id); + + public static DiagnosticSeverity GetSeverity(this RudeEditKind kind) + => GetDescriptor(kind).DefaultSeverity; + + public static bool IsBlocking(this RudeEditKind kind) + => kind.GetSeverity() == DiagnosticSeverity.Error; + + public static bool HasBlockingRudeEdits(this ImmutableArray diagnostics) + => diagnostics.Any(static e => e.Kind.IsBlocking()); } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 383ffa69bd5ff..c503af3bb49f7 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -151,33 +151,26 @@ public TraceLog Log /// /// Errors to be reported when a project is updated but the corresponding module does not support EnC. /// - /// if the module is not loaded. - public async Task?> GetModuleDiagnosticsAsync(Guid mvid, Project oldProject, Project newProject, ImmutableArray documentAnalyses, CancellationToken cancellationToken) + /// Non-null diagnostic id if the module blocks EnC operation. + public async Task ReportModuleDiagnosticsAsync(Guid mvid, Project oldProject, Project newProject, ImmutableArray documentAnalyses, ArrayBuilder diagnostics, CancellationToken cancellationToken) { Contract.ThrowIfTrue(documentAnalyses.IsEmpty); var availability = await DebuggingSession.DebuggerService.GetAvailabilityAsync(mvid, cancellationToken).ConfigureAwait(false); - if (availability.Status == ManagedHotReloadAvailabilityStatus.ModuleNotLoaded) + if (availability.Status is ManagedHotReloadAvailabilityStatus.ModuleNotLoaded or ManagedHotReloadAvailabilityStatus.Available) { return null; } - if (availability.Status == ManagedHotReloadAvailabilityStatus.Available) - { - return ImmutableArray.Empty; - } - var descriptor = EditAndContinueDiagnosticDescriptors.GetModuleDiagnosticDescriptor(availability.Status); var messageArgs = new[] { newProject.Name, availability.LocalizedMessage }; - using var _ = ArrayBuilder.GetInstance(out var diagnostics); - await foreach (var location in CreateChangedLocationsAsync(oldProject, newProject, documentAnalyses, cancellationToken).ConfigureAwait(false)) { diagnostics.Add(Diagnostic.Create(descriptor, location, messageArgs)); } - return diagnostics.ToImmutableAndClear(); + return descriptor.Id; } private static async IAsyncEnumerable CreateChangedLocationsAsync(Project oldProject, Project newProject, ImmutableArray documentAnalyses, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -441,7 +434,7 @@ internal static async ValueTask HasDocumentDifferencesAsync(Project oldPro return false; } - internal static async Task GetDocumentDifferencesAsync(TraceLog log, Project oldProject, Project newProject, ProjectDocumentDifferences documentDifferences, ArrayBuilder diagnostics, CancellationToken cancellationToken) + internal static async Task GetDocumentDifferencesAsync(TraceLog log, Project oldProject, Project newProject, ProjectDocumentDifferences documentDifferences, ArrayBuilder diagnostics, CancellationToken cancellationToken) { documentDifferences.Clear(); @@ -490,23 +483,18 @@ internal static async Task GetDocumentDifferencesAsync(TraceLog log, Project old } } - private static async ValueTask> GetSourceGeneratedDocumentStatesAsync(TraceLog log, Project project, ArrayBuilder? diagnostics, CancellationToken cancellationToken) + private static async ValueTask> GetSourceGeneratedDocumentStatesAsync(TraceLog log, Project project, ArrayBuilder? diagnostics, CancellationToken cancellationToken) { var generatorDiagnostics = await project.Solution.CompilationState.GetSourceGeneratorDiagnosticsAsync(project.State, cancellationToken).ConfigureAwait(false); - if (generatorDiagnostics is not []) - { - diagnostics?.Add(new ProjectDiagnostics(project.Id, generatorDiagnostics)); - } + diagnostics?.AddRange(generatorDiagnostics); - foreach (var generatorDiagnostic in generatorDiagnostics) + if (diagnostics == null) { - log.Write($"Source generator failed: {generatorDiagnostic}", generatorDiagnostic.Severity switch + foreach (var generatorDiagnostic in generatorDiagnostics) { - DiagnosticSeverity.Warning => LogMessageSeverity.Warning, - DiagnosticSeverity.Error => LogMessageSeverity.Error, - _ => LogMessageSeverity.Info - }); + log.Write($"Source generator failed: {generatorDiagnostic}", LogMessageSeverity.Info); + } } return await project.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); @@ -557,14 +545,14 @@ internal static async IAsyncEnumerable GetChangedDocumentsAsync(Trac } } - private async Task<(ImmutableArray results, ImmutableArray diagnostics, bool hasOutOfSyncDocument)> AnalyzeDocumentsAsync( + private async Task<(ImmutableArray results, bool hasOutOfSyncDocument)> AnalyzeDocumentsAsync( Solution newSolution, ProjectDocumentDifferences documentDifferences, ActiveStatementSpanProvider newDocumentActiveStatementSpanProvider, + ArrayBuilder diagnostics, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(out var documentDiagnostics); - using var _2 = ArrayBuilder<(Document? oldDocument, Document? newDocument)>.GetInstance(out var documents); + using var _ = ArrayBuilder<(Document? oldDocument, Document? newDocument)>.GetInstance(out var documents); var hasOutOfSyncDocument = false; foreach (var newDocument in documentDifferences.ChangedOrAdded) @@ -577,7 +565,7 @@ internal static async IAsyncEnumerable GetChangedDocumentsAsync(Trac case CommittedSolution.DocumentState.Indeterminate: var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.UnableToReadSourceFileOrPdb); - documentDiagnostics.Add(Diagnostic.Create(descriptor, Location.Create(newDocument.FilePath!, textSpan: default, lineSpan: default), [newDocument.FilePath])); + diagnostics.Add(Diagnostic.Create(descriptor, Location.Create(newDocument.FilePath!, textSpan: default, lineSpan: default), [newDocument.FilePath])); break; case CommittedSolution.DocumentState.OutOfSync: @@ -609,7 +597,7 @@ internal static async IAsyncEnumerable GetChangedDocumentsAsync(Trac ? [] : await Analyses.GetDocumentAnalysesAsync(DebuggingSession.LastCommittedSolution, newSolution, documents, newDocumentActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); - return (analyses, documentDiagnostics.ToImmutable(), hasOutOfSyncDocument); + return (analyses, hasOutOfSyncDocument); } private static ProjectAnalysisSummary GetProjectAnalysisSummary(ImmutableArray documentAnalyses) @@ -845,8 +833,15 @@ internal static void MergePartialEdits( addedSymbols = [.. addedSymbolsBuilder]; } - public async ValueTask EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, UpdateId updateId, CancellationToken cancellationToken) + public async ValueTask EmitSolutionUpdateAsync( + Solution solution, + ActiveStatementSpanProvider solutionActiveStatementSpanProvider, + UpdateId updateId, + ImmutableDictionary runningProjects, + CancellationToken cancellationToken) { + var projectDiagnostics = ArrayBuilder.GetInstance(); + try { Log.Write($"Found {updateId.SessionId} potentially changed document(s) in project {updateId.Ordinal} '{solution.FilePath}'"); @@ -854,9 +849,8 @@ public async ValueTask EmitSolutionUpdateAsync(Solution solution using var _1 = ArrayBuilder.GetInstance(out var deltas); using var _2 = ArrayBuilder<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)>.GetInstance(out var nonRemappableRegions); using var _3 = ArrayBuilder.GetInstance(out var newProjectBaselines); - using var _4 = ArrayBuilder.GetInstance(out var diagnostics); - using var _5 = ArrayBuilder.GetInstance(out var documentsWithRudeEdits); - using var _6 = ArrayBuilder.GetInstance(out var projectsToStale); + using var _4 = ArrayBuilder.GetInstance(out var projectsToStale); + using var _5 = PooledDictionary>.GetInstance(out var diagnosticBuilders); using var documentDifferences = new ProjectDocumentDifferences(); // After all projects have been analyzed "true" value indicates changed document that is only included in stale projects. @@ -886,343 +880,330 @@ void UpdateChangedDocumentsStaleness(bool isStale) var oldSolution = DebuggingSession.LastCommittedSolution; - var blockUpdates = false; - var hasEmitErrors = false; - var hadDocumentReadError = false; + var hasPersistentErrors = false; foreach (var newProject in solution.Projects) { - if (!newProject.SupportsEditAndContinue(Log)) + try { - continue; - } - - var oldProject = oldSolution.GetProject(newProject.Id); - if (oldProject == null) - { - Log.Write($"EnC state of {newProject.GetLogDisplay()} queried: project not loaded"); - - // TODO (https://github.com/dotnet/roslyn/issues/1204): - // - // When debugging session is started some projects might not have been loaded to the workspace yet (may be explicitly unloaded by the user). - // We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied - // and will result in source mismatch when the user steps into them. - // - // We can allow project to be added by including all its documents here. - // When we analyze these documents later on we'll check if they match the PDB. - // If so we can add them to the committed solution and detect further changes. - // It might be more efficient though to track added projects separately. - - continue; - } + if (!newProject.SupportsEditAndContinue(Log)) + { + continue; + } - await GetDocumentDifferencesAsync(Log, oldProject, newProject, documentDifferences, diagnostics, cancellationToken).ConfigureAwait(false); - if (documentDifferences.IsEmpty) - { - continue; - } + var oldProject = oldSolution.GetProject(newProject.Id); + if (oldProject == null) + { + Log.Write($"EnC state of {newProject.GetLogDisplay()} queried: project not loaded"); + + // TODO (https://github.com/dotnet/roslyn/issues/1204): + // + // When debugging session is started some projects might not have been loaded to the workspace yet (may be explicitly unloaded by the user). + // We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied + // and will result in source mismatch when the user steps into them. + // + // We can allow project to be added by including all its documents here. + // When we analyze these documents later on we'll check if they match the PDB. + // If so we can add them to the committed solution and detect further changes. + // It might be more efficient though to track added projects separately. + + continue; + } - Log.Write($"Found {documentDifferences.ChangedOrAdded.Count} potentially changed and {documentDifferences.Deleted.Count} deleted document(s) in project {newProject.GetLogDisplay()}"); + projectDiagnostics = ArrayBuilder.GetInstance(); - var isStaleProject = oldSolution.IsStaleProject(newProject.Id); + await GetDocumentDifferencesAsync(Log, oldProject, newProject, documentDifferences, projectDiagnostics, cancellationToken).ConfigureAwait(false); + if (documentDifferences.IsEmpty) + { + continue; + } - // We don't consider document changes in stale projects until they are rebuilt (removed from stale set). - if (isStaleProject) - { - Log.Write($"EnC state of {newProject.GetLogDisplay()} queried: project is stale"); - UpdateChangedDocumentsStaleness(isStale: true); - continue; - } + Log.Write($"Found {documentDifferences.ChangedOrAdded.Count} potentially changed and {documentDifferences.Deleted.Count} deleted document(s) in project {newProject.GetLogDisplay()}"); - var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(newProject, cancellationToken).ConfigureAwait(false); - if (mvidReadError != null) - { - // The error hasn't been reported by GetDocumentDiagnosticsAsync since it might have been intermittent. - // The MVID is required for emit so we consider the error permanent and report it here. - // Bail before analyzing documents as the analysis needs to read the PDB which will likely fail if we can't even read the MVID. - diagnostics.Add(new(newProject.Id, [mvidReadError])); + var isStaleProject = oldSolution.IsStaleProject(newProject.Id); - Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.ValidChanges, newProject.State.ProjectInfo.Attributes.TelemetryId, ImmutableArray.Create(mvidReadError.Descriptor.Id)); - blockUpdates = true; - continue; - } + // We don't consider document changes in stale projects until they are rebuilt (removed from stale set). + if (isStaleProject) + { + Log.Write($"EnC state of {newProject.GetLogDisplay()} queried: project is stale"); + UpdateChangedDocumentsStaleness(isStale: true); + continue; + } - if (mvid == Guid.Empty) - { - Log.Write($"Changes not applied to {newProject.Name} '{newProject.FilePath}': project not built"); - UpdateChangedDocumentsStaleness(isStale: true); - continue; - } + var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(newProject, cancellationToken).ConfigureAwait(false); + if (mvidReadError != null) + { + // The error hasn't been reported by GetDocumentDiagnosticsAsync since it might have been intermittent. + // The MVID is required for emit so we consider the error permanent and report it here. + // Bail before analyzing documents as the analysis needs to read the PDB which will likely fail if we can't even read the MVID. + projectDiagnostics.Add(mvidReadError); - // Ensure that all changed documents are in-sync. Once a document is in-sync it can't get out-of-sync. - // Therefore, results of further computations based on base snapshots of changed documents can't be invalidated by - // incoming events updating the content of out-of-sync documents. - // - // If in past we concluded that a document is out-of-sync, attempt to check one more time before we block apply. - // The source file content might have been updated since the last time we checked. - // - // TODO (investigate): https://github.com/dotnet/roslyn/issues/38866 - // It is possible that the result of Rude Edit semantic analysis of an unchanged document will change if there - // another document is updated. If we encounter a significant case of this we should consider caching such a result per project, - // rather then per document. Also, we might be observing an older semantics if the document that is causing the change is out-of-sync -- - // e.g. the binary was built with an overload C.M(object), but a generator updated class C to also contain C.M(string), - // which change we have not observed yet. Then call-sites of C.M in a changed document observed by the analysis will be seen as C.M(object) - // instead of the true C.M(string). - - var (changedDocumentAnalyses, documentDiagnostics, hasOutOfSyncChangedDocument) = - await AnalyzeDocumentsAsync(solution, documentDifferences, solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); - - if (documentDiagnostics.Any()) - { - // The diagnostic hasn't been reported by GetDocumentDiagnosticsAsync since out-of-sync documents are likely to be synchronized - // before the changes are attempted to be applied. If we still have any out-of-sync documents we report warnings and ignore changes in them. - // If in future the file is updated so that its content matches the PDB checksum, the document transitions to a matching state, - // and we consider any further changes to it for application. - diagnostics.Add(new(newProject.Id, documentDiagnostics)); + Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.ValidChanges, newProject.State.ProjectInfo.Attributes.TelemetryId, projectDiagnostics); + continue; + } - if (documentDiagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)) + if (mvid == Guid.Empty) { - blockUpdates = hadDocumentReadError = true; + Log.Write($"Changes not applied to {newProject.Name} '{newProject.FilePath}': project not built"); + UpdateChangedDocumentsStaleness(isStale: true); + continue; } - } - if (hasOutOfSyncChangedDocument) - { - // The project is considered stale as long as it has at least one document that is out-of-sync. - // Treat the project the same as if it hasn't been built. We won't produce delta for it until it gets rebuilt. - Log.Write($"Changes not applied to {newProject.Name} '{newProject.FilePath}': binaries not up-to-date"); + // Ensure that all changed documents are in-sync. Once a document is in-sync it can't get out-of-sync. + // Therefore, results of further computations based on base snapshots of changed documents can't be invalidated by + // incoming events updating the content of out-of-sync documents. + // + // If in past we concluded that a document is out-of-sync, attempt to check one more time before we block apply. + // The source file content might have been updated since the last time we checked. + // + // TODO (investigate): https://github.com/dotnet/roslyn/issues/38866 + // It is possible that the result of Rude Edit semantic analysis of an unchanged document will change if there + // another document is updated. If we encounter a significant case of this we should consider caching such a result per project, + // rather then per document. Also, we might be observing an older semantics if the document that is causing the change is out-of-sync -- + // e.g. the binary was built with an overload C.M(object), but a generator updated class C to also contain C.M(string), + // which change we have not observed yet. Then call-sites of C.M in a changed document observed by the analysis will be seen as C.M(object) + // instead of the true C.M(string). + + var (changedDocumentAnalyses, hasOutOfSyncChangedDocument) = + await AnalyzeDocumentsAsync(solution, documentDifferences, solutionActiveStatementSpanProvider, projectDiagnostics, cancellationToken).ConfigureAwait(false); + + if (hasOutOfSyncChangedDocument) + { + // The project is considered stale as long as it has at least one document that is out-of-sync. + // Treat the project the same as if it hasn't been built. We won't produce delta for it until it gets rebuilt. + Log.Write($"Changes not applied to {newProject.Name} '{newProject.FilePath}': binaries not up-to-date"); - projectsToStale.Add(newProject.Id); - UpdateChangedDocumentsStaleness(isStale: true); + projectsToStale.Add(newProject.Id); + UpdateChangedDocumentsStaleness(isStale: true); - continue; - } + continue; + } - UpdateChangedDocumentsStaleness(isStale: false); + UpdateChangedDocumentsStaleness(isStale: false); - foreach (var changedDocumentAnalysis in changedDocumentAnalyses) - { - if (changedDocumentAnalysis.SyntaxError != null) + foreach (var changedDocumentAnalysis in changedDocumentAnalyses) { - // only remember the first syntax error we encounter: - syntaxError ??= changedDocumentAnalysis.SyntaxError; + if (changedDocumentAnalysis.SyntaxError != null) + { + // only remember the first syntax error we encounter: + syntaxError ??= changedDocumentAnalysis.SyntaxError; + hasPersistentErrors = true; - Log.Write($"Changed document '{changedDocumentAnalysis.FilePath}' has syntax error: {changedDocumentAnalysis.SyntaxError}"); - } - else if (changedDocumentAnalysis.HasChanges) - { - Log.Write($"Document changed, added, or deleted: '{changedDocumentAnalysis.FilePath}'"); - } + Log.Write($"Changed document '{changedDocumentAnalysis.FilePath}' has syntax error: {changedDocumentAnalysis.SyntaxError}"); + } + else if (changedDocumentAnalysis.HasChanges) + { + Log.Write($"Document changed, added, or deleted: '{changedDocumentAnalysis.FilePath}'"); + } - Telemetry.LogAnalysisTime(changedDocumentAnalysis.ElapsedTime); - } + Telemetry.LogAnalysisTime(changedDocumentAnalysis.ElapsedTime); + } - var projectSummary = GetProjectAnalysisSummary(changedDocumentAnalyses); - Log.Write($"Project summary for {newProject.Name} '{newProject.FilePath}': {projectSummary}"); + var projectSummary = GetProjectAnalysisSummary(changedDocumentAnalyses); + Log.Write($"Project summary for {newProject.Name} '{newProject.FilePath}': {projectSummary}"); - if (projectSummary is ProjectAnalysisSummary.NoChanges or ProjectAnalysisSummary.ValidInsignificantChanges) - { - continue; - } + if (projectSummary is ProjectAnalysisSummary.NoChanges or ProjectAnalysisSummary.ValidInsignificantChanges) + { + continue; + } - // The capability of a module to apply edits may change during edit session if the user attaches debugger to - // an additional process that doesn't support EnC (or detaches from such process). Before we apply edits - // we need to check with the debugger. - var (moduleDiagnostics, isModuleLoaded) = await GetModuleDiagnosticsAsync(mvid, oldProject, newProject, changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); + // The capability of a module to apply edits may change during edit session if the user attaches debugger to + // an additional process that doesn't support EnC (or detaches from such process). Before we apply edits + // we need to check with the debugger. + var moduleBlockingDiagnosticId = await ReportModuleDiagnosticsAsync(mvid, oldProject, newProject, changedDocumentAnalyses, projectDiagnostics, cancellationToken).ConfigureAwait(false); - var isModuleEncBlocked = isModuleLoaded && !moduleDiagnostics.IsEmpty; - if (isModuleEncBlocked) - { - diagnostics.Add(new(newProject.Id, moduleDiagnostics)); - blockUpdates = true; - } + // Report rude edit diagnostics - these can be blocking (errors) or non-blocking (warnings): + foreach (var analysis in changedDocumentAnalyses) + { + if (!analysis.RudeEdits.IsEmpty) + { + var document = await solution.GetDocumentAsync(analysis.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + var tree = (document != null) ? await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false) : null; - if (projectSummary is ProjectAnalysisSummary.SyntaxErrors or ProjectAnalysisSummary.RudeEdits) - { - blockUpdates = true; - } + foreach (var rudeEdit in analysis.RudeEdits) + { + projectDiagnostics.Add(rudeEdit.ToDiagnostic(tree)); + } - // Report rude edit diagnostics - these can be blocking (errors) or non-blocking (warnings): - foreach (var analysis in changedDocumentAnalyses) - { - if (!analysis.RudeEdits.IsEmpty) - { - documentsWithRudeEdits.Add(new(analysis.DocumentId, analysis.RudeEdits)); - Telemetry.LogRudeEditDiagnostics(analysis.RudeEdits, newProject.State.Attributes.TelemetryId); + Telemetry.LogRudeEditDiagnostics(analysis.RudeEdits, newProject.State.Attributes.TelemetryId); + } } - } - if (isModuleEncBlocked || projectSummary != ProjectAnalysisSummary.ValidChanges) - { - Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, moduleDiagnostics.NullToEmpty().SelectAsArray(d => d.Descriptor.Id)); + if (moduleBlockingDiagnosticId != null || projectSummary != ProjectAnalysisSummary.ValidChanges) + { + Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, projectDiagnostics); - await LogDocumentChangesAsync(generation: null, cancellationToken).ConfigureAwait(false); - continue; - } + await LogDocumentChangesAsync(generation: null, cancellationToken).ConfigureAwait(false); + continue; + } - var oldCompilation = await oldProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(oldCompilation); + var oldCompilation = await oldProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(oldCompilation); - var projectBaselines = DebuggingSession.GetOrCreateEmitBaselines(mvid, oldProject, oldCompilation, out var createBaselineErrors, out var baselineAccessLock); - if (!createBaselineErrors.IsEmpty) - { - // Report diagnosics even when the module is never going to be loaded (e.g. in multi-targeting scenario, where only one framework being debugged). - // This is consistent with reporting compilation errors - the IDE reports them for all TFMs regardless of what framework the app is running on. - diagnostics.Add(new(newProject.Id, createBaselineErrors)); - Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, createBaselineErrors); - - blockUpdates = true; - await LogDocumentChangesAsync(generation: null, cancellationToken).ConfigureAwait(false); - continue; - } + var projectBaselines = DebuggingSession.GetOrCreateEmitBaselines(mvid, oldProject, oldCompilation, projectDiagnostics, out var baselineAccessLock); + if (projectBaselines.IsEmpty) + { + // Report diagnosics even when the module is never going to be loaded (e.g. in multi-targeting scenario, where only one framework being debugged). + // This is consistent with reporting compilation errors - the IDE reports them for all TFMs regardless of what framework the app is running on. + Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, projectDiagnostics); - Contract.ThrowIfTrue(projectBaselines.IsEmpty); + await LogDocumentChangesAsync(generation: null, cancellationToken).ConfigureAwait(false); + continue; + } - Log.Write($"Emitting update of {newProject.Name} '{newProject.FilePath}': project not built"); + Log.Write($"Emitting update of {newProject.Name} '{newProject.FilePath}': project not built"); - var newCompilation = await newProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var newCompilation = await newProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - // project must support compilations since it supports EnC - Contract.ThrowIfNull(newCompilation); + // project must support compilations since it supports EnC + Contract.ThrowIfNull(newCompilation); - var oldActiveStatementsMap = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); - var projectChanges = await GetProjectChangesAsync(oldActiveStatementsMap, oldCompilation, newCompilation, oldProject, newProject, changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); + var oldActiveStatementsMap = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); + var projectChanges = await GetProjectChangesAsync(oldActiveStatementsMap, oldCompilation, newCompilation, oldProject, newProject, changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); - // The compiler only uses this predicate to determine if CS7101: "Member 'X' added during the current debug session - // can only be accessed from within its declaring assembly 'Lib'" should be reported. - // Prior to .NET 8 Preview 4 the runtime failed to apply such edits. - // This was fixed in Preview 4 along with support for generics. If we see a generic capability we can disable reporting - // this compiler error. Otherwise, we leave the check as is in order to detect at least some runtime failures on .NET Framework. - // Note that the analysis in the compiler detecting the circumstances under which the runtime fails - // to apply the change has both false positives (flagged generic updates that shouldn't be flagged) and negatives - // (didn't flag cases like https://github.com/dotnet/roslyn/issues/68293). - var capabilities = await Capabilities.GetValueAsync(cancellationToken).ConfigureAwait(false); - var requiredCapabilities = projectChanges.RequiredCapabilities.ToStringArray(); + // The compiler only uses this predicate to determine if CS7101: "Member 'X' added during the current debug session + // can only be accessed from within its declaring assembly 'Lib'" should be reported. + // Prior to .NET 8 Preview 4 the runtime failed to apply such edits. + // This was fixed in Preview 4 along with support for generics. If we see a generic capability we can disable reporting + // this compiler error. Otherwise, we leave the check as is in order to detect at least some runtime failures on .NET Framework. + // Note that the analysis in the compiler detecting the circumstances under which the runtime fails + // to apply the change has both false positives (flagged generic updates that shouldn't be flagged) and negatives + // (didn't flag cases like https://github.com/dotnet/roslyn/issues/68293). + var capabilities = await Capabilities.GetValueAsync(cancellationToken).ConfigureAwait(false); + var requiredCapabilities = projectChanges.RequiredCapabilities.ToStringArray(); - var isAddedSymbolPredicate = capabilities.HasFlag(EditAndContinueCapabilities.GenericAddMethodToExistingType) ? - static _ => false : (Func)projectChanges.AddedSymbols.Contains; + var isAddedSymbolPredicate = capabilities.HasFlag(EditAndContinueCapabilities.GenericAddMethodToExistingType) ? + static _ => false : (Func)projectChanges.AddedSymbols.Contains; - var emitDiagnostics = ImmutableArray.Empty; + foreach (var projectBaseline in projectBaselines) + { + await LogDocumentChangesAsync(projectBaseline.Generation + 1, cancellationToken).ConfigureAwait(false); - foreach (var projectBaseline in projectBaselines) - { - await LogDocumentChangesAsync(projectBaseline.Generation + 1, cancellationToken).ConfigureAwait(false); + using var pdbStream = SerializableBytes.CreateWritableStream(); + using var metadataStream = SerializableBytes.CreateWritableStream(); + using var ilStream = SerializableBytes.CreateWritableStream(); - using var pdbStream = SerializableBytes.CreateWritableStream(); - using var metadataStream = SerializableBytes.CreateWritableStream(); - using var ilStream = SerializableBytes.CreateWritableStream(); + EmitDifferenceResult emitResult; - EmitDifferenceResult emitResult; + // The lock protects underlying baseline readers from being disposed while emitting delta. + // If the lock is disposed at this point the session has been incorrectly disposed while operations on it are in progress. + using (baselineAccessLock.DisposableRead()) + { + DebuggingSession.ThrowIfDisposed(); - // The lock protects underlying baseline readers from being disposed while emitting delta. - // If the lock is disposed at this point the session has been incorrectly disposed while operations on it are in progress. - using (baselineAccessLock.DisposableRead()) - { - DebuggingSession.ThrowIfDisposed(); + var emitDifferenceTimer = SharedStopwatch.StartNew(); - var emitDifferenceTimer = SharedStopwatch.StartNew(); + emitResult = newCompilation.EmitDifference( + projectBaseline.EmitBaseline, + projectChanges.SemanticEdits, + isAddedSymbolPredicate, + metadataStream, + ilStream, + pdbStream, + new EmitDifferenceOptions() { EmitFieldRva = capabilities.HasFlag(EditAndContinueCapabilities.AddFieldRva) }, + cancellationToken); - emitResult = newCompilation.EmitDifference( - projectBaseline.EmitBaseline, - projectChanges.SemanticEdits, - isAddedSymbolPredicate, - metadataStream, - ilStream, - pdbStream, - new EmitDifferenceOptions() { EmitFieldRva = capabilities.HasFlag(EditAndContinueCapabilities.AddFieldRva) }, - cancellationToken); + Telemetry.LogEmitDifferenceTime(emitDifferenceTimer.Elapsed); + } - Telemetry.LogEmitDifferenceTime(emitDifferenceTimer.Elapsed); - } + // TODO: https://github.com/dotnet/roslyn/issues/36061 + // We should only report diagnostics from emit phase. + // Syntax and semantic diagnostics are already reported by the diagnostic analyzer. + // Currently we do not have means to distinguish between diagnostics reported from compilation and emit phases. + // Querying diagnostics of the entire compilation or just the updated files migth be slow. + // In fact, it is desirable to allow emitting deltas for symbols affected by the change while allowing untouched + // method bodies to have errors. + if (!emitResult.Diagnostics.IsEmpty) + { + projectDiagnostics.AddRange(emitResult.Diagnostics); + } - // TODO: https://github.com/dotnet/roslyn/issues/36061 - // We should only report diagnostics from emit phase. - // Syntax and semantic diagnostics are already reported by the diagnostic analyzer. - // Currently we do not have means to distinguish between diagnostics reported from compilation and emit phases. - // Querying diagnostics of the entire compilation or just the updated files migth be slow. - // In fact, it is desirable to allow emitting deltas for symbols affected by the change while allowing untouched - // method bodies to have errors. - if (!emitResult.Diagnostics.IsEmpty) - { - diagnostics.Add(new(newProject.Id, emitResult.Diagnostics)); - } + if (!emitResult.Success) + { + hasPersistentErrors = true; - if (!emitResult.Success) - { - // error - blockUpdates = hasEmitErrors = true; - emitDiagnostics = emitResult.Diagnostics; - break; - } + // Stop emitting deltas, we will discard the updates emitted so far. + // Persistent errors need to be fixed before we attempt rebuilding the projects. + // The baseline solution snapshot will not be moved forward and next call to + // EmitSolutionUpdatesAsync will calculate changes for all updated projects again. + break; + } - Contract.ThrowIfNull(emitResult.Baseline); + Contract.ThrowIfNull(emitResult.Baseline); - var unsupportedChangesDiagnostic = await GetUnsupportedChangesDiagnosticAsync(emitResult, cancellationToken).ConfigureAwait(false); - if (unsupportedChangesDiagnostic is not null) - { - emitDiagnostics = [unsupportedChangesDiagnostic]; - diagnostics.Add(new(newProject.Id, emitDiagnostics)); - blockUpdates = true; - break; - } + var unsupportedChangesDiagnostic = await GetUnsupportedChangesDiagnosticAsync(emitResult, cancellationToken).ConfigureAwait(false); + if (unsupportedChangesDiagnostic is not null) + { + projectDiagnostics.Add(unsupportedChangesDiagnostic); + continue; + } - var updatedMethodTokens = emitResult.UpdatedMethods.SelectAsArray(h => MetadataTokens.GetToken(h)); - var changedTypeTokens = emitResult.ChangedTypes.SelectAsArray(h => MetadataTokens.GetToken(h)); - - // Determine all active statements whose span changed and exception region span deltas. - GetActiveStatementAndExceptionRegionSpans( - projectBaseline.ModuleId, - oldActiveStatementsMap, - updatedMethodTokens, - NonRemappableRegions, - projectChanges.ActiveStatementChanges, - out var activeStatementsInUpdatedMethods, - out var moduleNonRemappableRegions, - out var exceptionRegionUpdates); - - var delta = new ManagedHotReloadUpdate( - projectBaseline.ModuleId, - newCompilation.AssemblyName ?? newProject.Name, // used for display in debugger diagnostics - newProject.Id, - ilStream.ToImmutableArray(), - metadataStream.ToImmutableArray(), - pdbStream.ToImmutableArray(), - changedTypeTokens, - requiredCapabilities, - updatedMethodTokens, - projectChanges.LineChanges, - activeStatementsInUpdatedMethods, - exceptionRegionUpdates); - - deltas.Add(delta); - - nonRemappableRegions.Add((mvid, moduleNonRemappableRegions)); - newProjectBaselines.Add(new ProjectBaseline(mvid, projectBaseline.ProjectId, emitResult.Baseline, projectBaseline.Generation + 1)); - - var fileLog = Log.FileLog; - if (fileLog != null) - { - await LogDeltaFilesAsync(fileLog, delta, projectBaseline.Generation, oldProject, newProject, cancellationToken).ConfigureAwait(false); + var updatedMethodTokens = emitResult.UpdatedMethods.SelectAsArray(h => MetadataTokens.GetToken(h)); + var changedTypeTokens = emitResult.ChangedTypes.SelectAsArray(h => MetadataTokens.GetToken(h)); + + // Determine all active statements whose span changed and exception region span deltas. + GetActiveStatementAndExceptionRegionSpans( + projectBaseline.ModuleId, + oldActiveStatementsMap, + updatedMethodTokens, + NonRemappableRegions, + projectChanges.ActiveStatementChanges, + out var activeStatementsInUpdatedMethods, + out var moduleNonRemappableRegions, + out var exceptionRegionUpdates); + + var delta = new ManagedHotReloadUpdate( + projectBaseline.ModuleId, + newCompilation.AssemblyName ?? newProject.Name, // used for display in debugger diagnostics + newProject.Id, + ilStream.ToImmutableArray(), + metadataStream.ToImmutableArray(), + pdbStream.ToImmutableArray(), + changedTypeTokens, + requiredCapabilities, + updatedMethodTokens, + projectChanges.LineChanges, + activeStatementsInUpdatedMethods, + exceptionRegionUpdates); + + deltas.Add(delta); + + nonRemappableRegions.Add((mvid, moduleNonRemappableRegions)); + newProjectBaselines.Add(new ProjectBaseline(mvid, projectBaseline.ProjectId, emitResult.Baseline, projectBaseline.Generation + 1)); + + var fileLog = Log.FileLog; + if (fileLog != null) + { + await LogDeltaFilesAsync(fileLog, delta, projectBaseline.Generation, oldProject, newProject, cancellationToken).ConfigureAwait(false); + } } - } - Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, emitDiagnostics); + Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, projectDiagnostics); - async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cancellationToken) - { - var fileLog = Log.FileLog; - if (fileLog != null) + async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cancellationToken) { - foreach (var changedDocumentAnalysis in changedDocumentAnalyses) + var fileLog = Log.FileLog; + if (fileLog != null) { - if (changedDocumentAnalysis.HasChanges) + foreach (var changedDocumentAnalysis in changedDocumentAnalyses) { - var oldDocument = await oldProject.GetDocumentAsync(changedDocumentAnalysis.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - var newDocument = await newProject.GetDocumentAsync(changedDocumentAnalysis.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await fileLog.WriteDocumentChangeAsync(oldDocument, newDocument, updateId, generation, cancellationToken).ConfigureAwait(false); + if (changedDocumentAnalysis.HasChanges) + { + var oldDocument = await oldProject.GetDocumentAsync(changedDocumentAnalysis.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + var newDocument = await newProject.GetDocumentAsync(changedDocumentAnalysis.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + await fileLog.WriteDocumentChangeAsync(oldDocument, newDocument, updateId, generation, cancellationToken).ConfigureAwait(false); + } } } } } + finally + { + if (!projectDiagnostics.IsEmpty) + { + diagnosticBuilders.Add(newProject.Id, projectDiagnostics); + projectDiagnostics = ArrayBuilder.GetInstance(); + } + } } // Report stale document updates. @@ -1236,40 +1217,50 @@ async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cance { var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.UpdatingDocumentInStaleProject); var diagnostic = Diagnostic.Create(descriptor, Location.Create(documentPath, textSpan: default, lineSpan: default), [documentPath]); - diagnostics.Add(new ProjectDiagnostics(documentId.ProjectId, [diagnostic])); + diagnosticBuilders.MultiAdd(documentId.ProjectId, diagnostic); } } } - // log capabilities for edit sessions with changes or reported errors: - if (blockUpdates || deltas.Count > 0) + var diagnostics = diagnosticBuilders.SelectAsArray(entry => new ProjectDiagnostics(entry.Key, entry.Value.ToImmutableAndFree())); + + Telemetry.LogRuntimeCapabilities(await Capabilities.GetValueAsync(cancellationToken).ConfigureAwait(false)); + + if (hasPersistentErrors) { - Telemetry.LogRuntimeCapabilities(await Capabilities.GetValueAsync(cancellationToken).ConfigureAwait(false)); + return SolutionUpdate.Empty(diagnostics, syntaxError, ModuleUpdateStatus.Blocked); } - var update = blockUpdates - ? SolutionUpdate.Empty( - diagnostics.ToImmutable(), - documentsWithRudeEdits.ToImmutable(), - syntaxError, - syntaxError != null || hasEmitErrors || hadDocumentReadError ? ModuleUpdateStatus.Blocked : ModuleUpdateStatus.RestartRequired) - : new SolutionUpdate( - new ModuleUpdates( - (deltas.Count > 0) ? ModuleUpdateStatus.Ready : ModuleUpdateStatus.None, - deltas.ToImmutable()), - projectsToStale.ToImmutable(), - nonRemappableRegions.ToImmutable(), - newProjectBaselines.ToImmutable(), - diagnostics.ToImmutable(), - documentsWithRudeEdits.ToImmutable(), - syntaxError); - - return update; + var updates = deltas.ToImmutable(); + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + updates, + diagnostics, + runningProjects, + out var projectsToRestart, + out var projectsToRebuild); + + var moduleUpdates = new ModuleUpdates(deltas.IsEmpty && projectsToRebuild.IsEmpty ? ModuleUpdateStatus.None : ModuleUpdateStatus.Ready, updates); + + return new SolutionUpdate( + moduleUpdates, + projectsToStale.ToImmutable(), + nonRemappableRegions.ToImmutable(), + newProjectBaselines.ToImmutable(), + diagnostics, + syntaxError, + projectsToRestart, + projectsToRebuild); } catch (Exception e) when (LogException(e) && FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable(); } + finally + { + projectDiagnostics.Free(); + } bool LogException(Exception e) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditSessionTelemetry.cs b/src/Features/Core/Portable/EditAndContinue/EditSessionTelemetry.cs index d2ead3388ee92..33aad51f75d98 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSessionTelemetry.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSessionTelemetry.cs @@ -84,11 +84,19 @@ public void LogEmitDifferenceTime(TimeSpan span) public void LogAnalysisTime(TimeSpan span) => _analysisTime += span; - public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray errorsIds) + public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, IEnumerable diagnostics) { lock (_guard) { - _emitErrorIds.AddRange(errorsIds); + var hasError = false; + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Severity == DiagnosticSeverity.Error && !diagnostic.IsRudeEdit()) + { + _emitErrorIds.Add(diagnostic.Id); + hasError = true; + } + } switch (summary) { @@ -106,7 +114,7 @@ public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid proje case ProjectAnalysisSummary.ValidChanges: _hadValidChanges = true; - if (errorsIds.IsEmpty && _projectsWithValidDelta.Count < MaxReportedProjectIds) + if (!hasError && _projectsWithValidDelta.Count < MaxReportedProjectIds) { _projectsWithValidDelta.Add(projectTelemetryId); } @@ -123,9 +131,6 @@ public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid proje } } - public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray emitDiagnostics) - => LogProjectAnalysisSummary(summary, projectTelemetryId, emitDiagnostics.SelectAsArray(d => d.Severity == DiagnosticSeverity.Error, d => d.Id)); - public void LogRudeEditDiagnostics(ImmutableArray diagnostics, Guid projectTelemetryId) { lock (_guard) diff --git a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs index 5478af5286b7b..9ddbacc71d766 100644 --- a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs @@ -26,9 +26,6 @@ internal readonly struct Data [DataMember] public required ImmutableArray Diagnostics { get; init; } - [DataMember] - public required ImmutableArray RudeEdits { get; init; } - [DataMember] public required DiagnosticData? SyntaxError { get; init; } @@ -42,14 +39,20 @@ internal ImmutableArray GetAllDiagnostics() { using var _ = ArrayBuilder.GetInstance(out var builder); - // Add semantic and lowering diagnostics reported during delta emit: - foreach (var diagnostic in Diagnostics) { - builder.Add(diagnostic.ToHotReloadDiagnostic(ModuleUpdates.Status, isRudeEdit: false)); - } + var severity = diagnostic.Severity switch + { + DiagnosticSeverity.Error => EditAndContinueDiagnosticDescriptors.IsEncDiagnostic(diagnostic.Id) ? ManagedHotReloadDiagnosticSeverity.RestartRequired : ManagedHotReloadDiagnosticSeverity.Error, + DiagnosticSeverity.Warning => ManagedHotReloadDiagnosticSeverity.Warning, + _ => default + }; - // Add syntax error: + if (severity != default) + { + builder.Add(diagnostic.ToHotReloadDiagnostic(severity)); + } + } if (SyntaxError != null) { @@ -66,14 +69,28 @@ internal ImmutableArray GetAllDiagnostics() fileSpan.Span.ToSourceSpan())); } - // Report all rude edits. + return builder.ToImmutableAndClear(); + } - foreach (var data in RudeEdits) - { - builder.Add(data.ToHotReloadDiagnostic(ModuleUpdates.Status, isRudeEdit: true)); - } + public static Data CreateFromInternalError(Solution solution, string errorMessage, ImmutableDictionary runningProjects) + { + ImmutableArray diagnostics = []; + var firstProject = solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(); + var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.CannotApplyChangesUnexpectedError); - return builder.ToImmutableAndClear(); + var diagnostic = Diagnostic.Create( + descriptor, + Location.None, + string.Format(descriptor.MessageFormat.ToString(), "", errorMessage)); + + return new() + { + ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []), + Diagnostics = [DiagnosticData.Create(diagnostic, firstProject)], + SyntaxError = null, + ProjectsToRebuild = [.. runningProjects.Keys], + ProjectsToRestart = runningProjects.Keys.ToImmutableDictionary(keySelector: static p => p, elementSelector: static p => ImmutableArray.Create(p)) + }; } } @@ -82,7 +99,6 @@ internal ImmutableArray GetAllDiagnostics() Solution = null, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.None, []), Diagnostics = [], - RudeEdits = [], SyntaxError = null, ProjectsToRestart = ImmutableDictionary>.Empty, ProjectsToRebuild = [], @@ -100,12 +116,11 @@ internal ImmutableArray GetAllDiagnostics() public required ModuleUpdates ModuleUpdates { get; init; } /// - /// Reported diagnostics, other than rude edits, per project. - /// May contain multiple entries for the same project. + /// Reported diagnostics per project. + /// At most one set of diagnostics per project. /// public required ImmutableArray Diagnostics { get; init; } - public required ImmutableArray RudeEdits { get; init; } public required Diagnostic? SyntaxError { get; init; } /// @@ -126,7 +141,6 @@ public Data Dehydrate() { ModuleUpdates = ModuleUpdates, Diagnostics = [], - RudeEdits = [], SyntaxError = null, ProjectsToRestart = ImmutableDictionary>.Empty, ProjectsToRebuild = [], @@ -135,7 +149,6 @@ public Data Dehydrate() { ModuleUpdates = ModuleUpdates, Diagnostics = Diagnostics.ToDiagnosticData(Solution), - RudeEdits = RudeEdits.ToDiagnosticData(Solution), SyntaxError = GetSyntaxErrorData(), ProjectsToRestart = ProjectsToRestart, ProjectsToRebuild = ProjectsToRebuild, @@ -167,20 +180,20 @@ public Data Dehydrate() /// internal static void GetProjectsToRebuildAndRestart( Solution solution, - ModuleUpdates moduleUpdates, - ArrayBuilder rudeEdits, + ImmutableArray moduleUpdates, + ImmutableArray diagnostics, ImmutableDictionary runningProjects, out ImmutableDictionary> projectsToRestart, out ImmutableArray projectsToRebuild) { - Debug.Assert(!rudeEdits.HasDuplicates(d => d.ProjectId)); - Debug.Assert(rudeEdits.Select(re => re.ProjectId).IsSorted()); + Debug.Assert(!diagnostics.HasDuplicates(d => d.ProjectId)); + Debug.Assert(diagnostics.Select(re => re.ProjectId).IsSorted()); - // Projects with blocking rude edits should not have updates: - Debug.Assert(rudeEdits - .Where(r => r.Diagnostics.HasBlockingRudeEdits()) + // Projects with errors (including blocking rude edits) should not have updates: + Debug.Assert(diagnostics + .Where(r => r.Diagnostics.Any(static d => d.Severity == DiagnosticSeverity.Error)) .Select(r => r.ProjectId) - .Intersect(moduleUpdates.Updates.Select(u => u.ProjectId)) + .Intersect(moduleUpdates.Select(u => u.ProjectId)) .IsEmpty()); var graph = solution.GetProjectDependencyGraph(); @@ -207,12 +220,9 @@ internal static void GetProjectsToRebuildAndRestart( using var _4 = ArrayBuilder<(ProjectId projectWithRudeEdits, ImmutableArray impactedRunningProjects)>.GetInstance(out var impactedRunningProjectMap); using var _5 = ArrayBuilder.GetInstance(out var impactedRunningProjects); - for (var i = 0; i < rudeEdits.Count; i++) + foreach (var (projectId, projectDiagnostics) in diagnostics) { - var (projectId, projectDiagnostics) = rudeEdits[i]; - - var hasBlocking = projectDiagnostics.HasBlockingRudeEdits(); - var hasNoEffect = projectDiagnostics.HasNoEffectRudeEdits(); + ClassifyRudeEdits(projectDiagnostics, out var hasBlocking, out var hasNoEffect); if (!hasBlocking && !hasNoEffect) { continue; @@ -244,7 +254,7 @@ internal static void GetProjectsToRebuildAndRestart( // Partial solution update not supported. if (projectsToRestartBuilder.Any()) { - foreach (var update in moduleUpdates.Updates) + foreach (var update in moduleUpdates) { AddImpactedRunningProjects(impactedRunningProjects, update.ProjectId, isBlocking: true); @@ -257,7 +267,7 @@ internal static void GetProjectsToRebuildAndRestart( } } } - else if (!moduleUpdates.Updates.IsEmpty && projectsToRestartBuilder.Count > 0) + else if (!moduleUpdates.IsEmpty && projectsToRestartBuilder.Count > 0) { // The set of updated projects is usually much smaller than the number of all projects in the solution. // We iterate over this set updating the reset set until no new project is added to the reset set. @@ -271,7 +281,7 @@ internal static void GetProjectsToRebuildAndRestart( using var _7 = ArrayBuilder.GetInstance(out var updatedProjectsToRemove); using var _8 = PooledHashSet.GetInstance(out var projectsThatCausedRestart); - updatedProjects.AddRange(moduleUpdates.Updates.Select(static u => u.ProjectId)); + updatedProjects.AddRange(moduleUpdates.Select(static u => u.ProjectId)); while (true) { @@ -356,47 +366,82 @@ void AddImpactedRunningProjects(ArrayBuilder impactedProjects, Projec } } + private static void ClassifyRudeEdits(ImmutableArray diagnostics, out bool blocking, out bool noEffect) + { + noEffect = false; + blocking = false; + + foreach (var diagnostic in diagnostics) + { + noEffect |= diagnostic.IsNoEffectDiagnostic(); + blocking |= diagnostic.IsEncDiagnostic() && diagnostic.Severity == DiagnosticSeverity.Error; + + if (noEffect && blocking) + { + return; + } + } + } + public ImmutableArray GetAllDiagnostics() { - using var _ = ArrayBuilder.GetInstance(out var diagnostics); + using var _ = ArrayBuilder.GetInstance(out var result); - // add semantic and lowering diagnostics reported during delta emit: foreach (var (_, projectEmitDiagnostics) in Diagnostics) { - diagnostics.AddRange(projectEmitDiagnostics); + result.AddRange(projectEmitDiagnostics); } - // add syntax error: if (SyntaxError != null) { - diagnostics.Add(SyntaxError); + result.Add(SyntaxError); } - // add rude edits: - foreach (var (_, projectEmitDiagnostics) in RudeEdits) + return result.ToImmutableAndClear(); + } + + /// + /// Returns all diagnostics that can be addressed by rebuilding/restarting the project. + /// + public ImmutableArray<(ProjectId projectId, ImmutableArray diagnostics)> GetTransientDiagnostics() + { + using var _ = ArrayBuilder<(ProjectId projectId, ImmutableArray diagnostics)>.GetInstance(out var result); + + foreach (var (projectId, diagnostics) in Diagnostics) { - diagnostics.AddRange(projectEmitDiagnostics); + var transientDiagnostics = diagnostics.WhereAsArray(static d => d.IsEncDiagnostic()); + if (transientDiagnostics.Length > 0) + { + result.Add((projectId, transientDiagnostics)); + } } - return diagnostics.ToImmutableAndClear(); + return result.ToImmutable(); } - public ImmutableArray GetAllCompilationDiagnostics() + /// + /// Returns all diagnostics that can't be addressed by rebuilding/restarting the project. + /// + public ImmutableArray GetPersistentDiagnostics() { - using var _ = ArrayBuilder.GetInstance(out var diagnostics); + using var _ = ArrayBuilder.GetInstance(out var result); - // add semantic and lowering diagnostics reported during delta emit: - foreach (var (_, projectEmitDiagnostics) in Diagnostics) + foreach (var (_, diagnostics) in Diagnostics) { - diagnostics.AddRange(projectEmitDiagnostics); + foreach (var diagnostic in diagnostics) + { + if (!diagnostic.IsEncDiagnostic()) + { + result.AddRange(diagnostics); + } + } } - // add syntax error: if (SyntaxError != null) { - diagnostics.Add(SyntaxError); + result.Add(SyntaxError); } - return diagnostics.ToImmutableAndClear(); + return result.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs index 25086909a4bd9..922e0095c446a 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs @@ -19,9 +19,9 @@ internal interface IEditAndContinueSessionTracker /// /// Diagnostics reported by the last call. - /// Includes emit errors and issues reported by the debugger when applying changes. + /// Includes emit errors, project level errors, project level rude edits and issues reported by the debugger when applying changes. /// Does not include rude edits reported for added or changed documents, which are reported by , - /// Incldues rude edits reported for deleted documents. + /// Includes rude edits reported for deleted documents. /// ImmutableArray ApplyChangesDiagnostics { get; } } diff --git a/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs b/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs index 99e34c691aa77..9eb9f048428cd 100644 --- a/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs +++ b/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs @@ -15,18 +15,13 @@ internal enum ModuleUpdateStatus None = 0, /// - /// All changes are valid, can be applied. + /// Changes can be applied (project might need rebuild in presence of transient errors). /// Ready = 1, - /// - /// Changes require restarting the application in order to be applied. - /// - RestartRequired = 2, - /// /// Some changes are errors that block rebuild of the module. /// This means that the code is in a broken state that cannot be resolved by restarting the application. /// - Blocked = 3 + Blocked = 2 } diff --git a/src/Features/Core/Portable/EditAndContinue/PendingSolutionUpdate.cs b/src/Features/Core/Portable/EditAndContinue/PendingSolutionUpdate.cs index 5dc31b8945d50..8b0a26226e62d 100644 --- a/src/Features/Core/Portable/EditAndContinue/PendingSolutionUpdate.cs +++ b/src/Features/Core/Portable/EditAndContinue/PendingSolutionUpdate.cs @@ -19,11 +19,13 @@ internal abstract class PendingUpdate( internal sealed class PendingSolutionUpdate( Solution solution, ImmutableArray projectsToStale, + ImmutableArray projectsToRebuild, ImmutableArray projectBaselines, ImmutableArray deltas, ImmutableArray<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)> nonRemappableRegions) : PendingUpdate(projectBaselines, deltas) { public readonly Solution Solution = solution; public readonly ImmutableArray ProjectsToStale = projectsToStale; + public readonly ImmutableArray ProjectsToRebuild = projectsToRebuild; public readonly ImmutableArray<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)> Regions)> NonRemappableRegions = nonRemappableRegions; } diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs index 6ec4de320e5e9..2fa9e13776ec6 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs @@ -74,40 +74,13 @@ await client.TryInvokeAsync( callbackTarget: new ActiveStatementSpanProviderCallback(activeStatementSpanProvider), cancellationToken).ConfigureAwait(false); - return result.HasValue ? result.Value : new EmitSolutionUpdateResults.Data() - { - ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.RestartRequired, []), - Diagnostics = [], - RudeEdits = [], - SyntaxError = null, - ProjectsToRebuild = [], - ProjectsToRestart = ImmutableDictionary>.Empty, - }; + return result.HasValue + ? result.Value + : EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, errorMessage: "Unexpected RPC failure", runningProjects); // user friendly error already reported by OOP infra } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { - return new EmitSolutionUpdateResults.Data() - { - ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.RestartRequired, []), - Diagnostics = GetInternalErrorDiagnosticData(e.Message), - RudeEdits = [], - SyntaxError = null, - ProjectsToRebuild = [], - ProjectsToRestart = ImmutableDictionary>.Empty, - }; - } - - ImmutableArray GetInternalErrorDiagnosticData(string message) - { - var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(RudeEditKind.InternalError); - - var firstProject = solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(); - var diagnostic = Diagnostic.Create( - descriptor, - Location.None, - string.Format(descriptor.MessageFormat.ToString(), "", message)); - - return [DiagnosticData.Create(diagnostic, firstProject)]; + return EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjects); } } diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs index d422e1f99f7a7..34e38bee9de28 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs @@ -47,27 +47,3 @@ internal Diagnostic ToDiagnostic(SyntaxTree? tree) return Diagnostic.Create(descriptor, tree?.GetLocation(Span) ?? Location.None, Arguments); } } - -internal static class RudeEditExtensions -{ - internal static DiagnosticSeverity GetSeverity(this RudeEditKind kind) - => EditAndContinueDiagnosticDescriptors.GetDescriptor(kind).DefaultSeverity; - - internal static bool IsBlocking(this RudeEditKind kind) - => kind.GetSeverity() == DiagnosticSeverity.Error; - - internal static bool IsBlockingRudeEdit(this Diagnostic diagnostic) - => diagnostic.Descriptor.DefaultSeverity == DiagnosticSeverity.Error; - - internal static bool IsNoEffectRudeEdit(this Diagnostic diagnostic) - => EditAndContinueDiagnosticDescriptors.GetRudeEditKind(diagnostic.Id) == RudeEditKind.UpdateMightNotHaveAnyEffect; - - public static bool HasBlockingRudeEdits(this ImmutableArray diagnostics) - => diagnostics.Any(IsBlockingRudeEdit); - - public static bool HasNoEffectRudeEdits(this ImmutableArray diagnostics) - => diagnostics.Any(IsNoEffectRudeEdit); - - public static bool HasBlockingRudeEdits(this ImmutableArray diagnostics) - => diagnostics.Any(static e => e.Kind.IsBlocking()); -} diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index a8d9fcbeda3b2..f7dcd29eb4d39 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -7,6 +7,9 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; // TELEMETRY: DO NOT MODIFY ANY ENUM VALUES OF THIS ENUM. // IT WILL BREAK OUR SQM VARIABLE MAPPINGS. +/// +/// Diagnostics reported by . +/// internal enum RudeEditKind : ushort { None = 0, diff --git a/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs b/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs index 331e59b4682b7..c587ee664d340 100644 --- a/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs +++ b/src/Features/Core/Portable/EditAndContinue/SolutionUpdate.cs @@ -15,20 +15,23 @@ internal readonly struct SolutionUpdate( ImmutableArray<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)> nonRemappableRegions, ImmutableArray projectBaselines, ImmutableArray diagnostics, - ImmutableArray documentsWithRudeEdits, - Diagnostic? syntaxError) + Diagnostic? syntaxError, + ImmutableDictionary> projectsToRestart, + ImmutableArray projectsToRebuild) { public readonly ModuleUpdates ModuleUpdates = moduleUpdates; public readonly ImmutableArray ProjectsToStale = projectsToStale; public readonly ImmutableArray<(Guid ModuleId, ImmutableArray<(ManagedModuleMethodId Method, NonRemappableRegion Region)>)> NonRemappableRegions = nonRemappableRegions; public readonly ImmutableArray ProjectBaselines = projectBaselines; + + // Diagnostics for projects, unique entries per project. public readonly ImmutableArray Diagnostics = diagnostics; - public readonly ImmutableArray DocumentsWithRudeEdits = documentsWithRudeEdits; public readonly Diagnostic? SyntaxError = syntaxError; + public readonly ImmutableDictionary> ProjectsToRestart = projectsToRestart; + public readonly ImmutableArray ProjectsToRebuild = projectsToRebuild; public static SolutionUpdate Empty( ImmutableArray diagnostics, - ImmutableArray documentsWithRudeEdits, Diagnostic? syntaxError, ModuleUpdateStatus status) => new( @@ -37,8 +40,9 @@ public static SolutionUpdate Empty( nonRemappableRegions: [], projectBaselines: [], diagnostics, - documentsWithRudeEdits, - syntaxError); + syntaxError, + projectsToRestart: ImmutableDictionary>.Empty, + projectsToRebuild: []); internal void Log(TraceLog log, UpdateId updateId) { @@ -56,18 +60,12 @@ internal void Log(TraceLog log, UpdateId updateId) { foreach (var diagnostic in projectDiagnostics.Diagnostics) { - if (diagnostic.Severity == DiagnosticSeverity.Error) + log.Write($"[{projectDiagnostics.ProjectId.DebugName}]: {diagnostic}", diagnostic.Severity switch { - log.Write($"Project {projectDiagnostics.ProjectId.DebugName} update error: {diagnostic}", LogMessageSeverity.Error); - } - } - } - - foreach (var documentWithRudeEdits in DocumentsWithRudeEdits) - { - foreach (var rudeEdit in documentWithRudeEdits.RudeEdits) - { - log.Write($"Document {documentWithRudeEdits.Id.DebugName} rude edit: {rudeEdit.Kind} {rudeEdit.SyntaxKind}", LogMessageSeverity.Error); + DiagnosticSeverity.Warning => LogMessageSeverity.Warning, + DiagnosticSeverity.Error => LogMessageSeverity.Error, + _ => LogMessageSeverity.Info + }); } } } diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index 2efe0c1e1def4..5df8e5934181a 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -126,25 +126,14 @@ private static bool IsRazorDesignTimeOnlyDocument(string filePath) => filePath.EndsWith(".razor.g.cs", StringComparison.OrdinalIgnoreCase) || filePath.EndsWith(".cshtml.g.cs", StringComparison.OrdinalIgnoreCase); - public static ManagedHotReloadDiagnostic ToHotReloadDiagnostic(this DiagnosticData data, ModuleUpdateStatus updateStatus, bool isRudeEdit) + public static ManagedHotReloadDiagnostic ToHotReloadDiagnostic(this DiagnosticData data, ManagedHotReloadDiagnosticSeverity severity) { var fileSpan = data.DataLocation.MappedFileSpan; return new( data.Id, data.Message ?? FeaturesResources.Unknown_error_occurred, - isRudeEdit - ? data.DefaultSeverity switch - { - DiagnosticSeverity.Error => ManagedHotReloadDiagnosticSeverity.RestartRequired, - DiagnosticSeverity.Warning => ManagedHotReloadDiagnosticSeverity.Warning, - _ => throw ExceptionUtilities.UnexpectedValue(data.DefaultSeverity) - } - : updateStatus == ModuleUpdateStatus.RestartRequired - ? ManagedHotReloadDiagnosticSeverity.RestartRequired - : (data.Severity == DiagnosticSeverity.Error) - ? ManagedHotReloadDiagnosticSeverity.Error - : ManagedHotReloadDiagnosticSeverity.Warning, + severity, fileSpan.Path ?? "", fileSpan.Span.ToSourceSpan()); } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index f8b8e4b97aa2b..d246d2b462606 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -98,7 +98,7 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca .EmitSolutionUpdateAsync(sessionId, solution, runningProjects: ImmutableDictionary.Empty, s_solutionActiveStatementSpanProvider, cancellationToken) .ConfigureAwait(false); - if (results.ModuleUpdates.Status == ModuleUpdateStatus.Ready) + if (!results.ModuleUpdates.Updates.IsEmpty) { if (commitUpdates) { diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptClassificationService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptClassificationService.cs new file mode 100644 index 0000000000000..ed76545a29beb --- /dev/null +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptClassificationService.cs @@ -0,0 +1,17 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; + +internal interface IVSTypeScriptClassificationService +{ + Task AddSemanticClassificationsAsync(Document document, ImmutableArray textSpans, List result, CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptClassificationService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptClassificationService.cs new file mode 100644 index 0000000000000..41399a5c6c11e --- /dev/null +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptClassificationService.cs @@ -0,0 +1,61 @@ +// 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.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; + +[ExportLanguageService(typeof(IClassificationService), InternalLanguageNames.TypeScript), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VSTypeScriptClassificationService( + [Import(AllowDefault = true)] IVSTypeScriptClassificationService? classificationService) : IClassificationService +{ + public void AddLexicalClassifications(SourceText text, TextSpan textSpan, SegmentedList result, CancellationToken cancellationToken) + { + } + + public void AddSyntacticClassifications(SolutionServices services, SyntaxNode? root, ImmutableArray textSpans, SegmentedList result, CancellationToken cancellationToken) + { + } + + public Task AddSyntacticClassificationsAsync(Document document, ImmutableArray textSpans, SegmentedList result, CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task AddEmbeddedLanguageClassificationsAsync(Document document, ImmutableArray textSpans, ClassificationOptions options, SegmentedList result, CancellationToken cancellationToken) + => Task.CompletedTask; + + public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan) + => classifiedSpan; + + public ValueTask ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + => default; + + public TextChangeRange? ComputeSyntacticChangeRange(SolutionServices workspace, SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + => null; + + public async Task AddSemanticClassificationsAsync(Document document, ImmutableArray textSpans, ClassificationOptions options, SegmentedList result, CancellationToken cancellationToken) + { + if (classificationService is null) + return; + + using var _ = SharedPools.BigDefault>().GetPooledObject(out var list); + + await classificationService.AddSemanticClassificationsAsync(document, textSpans, list, cancellationToken).ConfigureAwait(false); + + foreach (var classifiedSpan in list) + result.Add(classifiedSpan); + } +} diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs index 1d2f818cef885..7b4675dde2770 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptCommentSelectionService.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CommentSelection; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 330331bf71a6b..c356ee7dbf091 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -69,53 +69,6 @@ public readonly struct RunningProjectInfo public required bool RestartWhenChangesHaveNoEffect { get; init; } } - [Obsolete("Use Updates2")] - public readonly struct Updates( - ModuleUpdateStatus status, - ImmutableArray diagnostics, - ImmutableArray projectUpdates, - IReadOnlySet projectsToRestart, - IReadOnlySet projectsToRebuild) - { - /// - /// Status of the updates. - /// - public readonly ModuleUpdateStatus Status { get; } = status; - - /// - /// Hot Reload specific diagnostics to be reported (includes rude edits and emit errors). - /// - public ImmutableArray Diagnostics { get; } = diagnostics; - - /// - /// Updates to be applied to modules. Empty if there are blocking rude edits. - /// Only updates to projects that are not included in are listed. - /// - public ImmutableArray ProjectUpdates { get; } = projectUpdates; - - /// - /// Running projects that need to be restarted due to rude edits in order to apply changes. - /// - [Obsolete("Use ProjectIdsToRestart")] - public IReadOnlySet ProjectsToRestart { get; } = projectsToRestart; - - /// - /// Projects with changes that need to be rebuilt in order to apply changes. - /// - [Obsolete("Use ProjectIdsToRebuild")] - public IReadOnlySet ProjectsToRebuild { get; } = projectsToRebuild; - - /// - /// Running projects that need to be restarted due to rude edits in order to apply changes. - /// - public ImmutableArray ProjectIdsToRestart { get; } = projectsToRestart.SelectAsArray(p => p.Id); - - /// - /// Projects with changes that need to be rebuilt in order to apply changes. - /// - public ImmutableArray ProjectIdsToRebuild { get; } = projectsToRebuild.SelectAsArray(p => p.Id); - } - public enum Status { /// @@ -143,6 +96,7 @@ public readonly struct Updates2 public readonly Status Status { get; init; } /// + /// Returns all diagnostics that can't be addressed by rebuilding/restarting the project. /// Syntactic, semantic and emit diagnostics. /// /// @@ -151,7 +105,8 @@ public readonly struct Updates2 public required ImmutableArray CompilationDiagnostics { get; init; } /// - /// Rude edits per project. + /// Transient diagnostics (rude edits) per project. + /// All diagnostics that can be addressed by rebuilding/restarting the project. /// public required ImmutableArray<(ProjectId project, ImmutableArray diagnostics)> RudeEdits { get; init; } @@ -231,48 +186,8 @@ public void CapabilitiesChanged() public static string? GetTargetFramework(Project project) => project.State.NameAndFlavor.flavor; - [Obsolete] - public async Task GetUpdatesAsync(Solution solution, IImmutableSet runningProjects, CancellationToken cancellationToken) - { - var sessionId = GetDebuggingSession(); - - var runningProjectsImpl = runningProjects.ToImmutableDictionary(keySelector: p => p, elementSelector: _ => new EditAndContinue.RunningProjectInfo() - { - RestartWhenChangesHaveNoEffect = false, - AllowPartialUpdate = false - }); - - var results = await _encService.EmitSolutionUpdateAsync(sessionId, solution, runningProjectsImpl, s_solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); - - // If the changes fail to apply dotnet-watch fails. - // We don't support discarding the changes and letting the user retry. - if (!results.ModuleUpdates.Updates.IsEmpty) - { - _encService.CommitSolutionUpdate(sessionId); - } - - var diagnostics = results.GetAllDiagnostics(); - - var projectUpdates = - from update in results.ModuleUpdates.Updates - let project = solution.GetRequiredProject(update.ProjectId) - where !results.ProjectsToRestart.ContainsKey(project.Id) - select new Update( - update.Module, - project.Id, - update.ILDelta, - update.MetadataDelta, - update.PdbDelta, - update.UpdatedTypes, - update.RequiredCapabilities); - - return new Updates( - results.ModuleUpdates.Status, - diagnostics, - [.. projectUpdates], - results.ProjectsToRestart.Keys.Select(solution.GetRequiredProject).ToImmutableHashSet(), - results.ProjectsToRebuild.Select(solution.GetRequiredProject).ToImmutableHashSet()); - } + // TODO: remove, for backwards compat only + public static bool RequireCommit { get; set; } /// /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or @@ -301,7 +216,7 @@ public async Task GetUpdatesAsync(Solution solution, ImmutableDictiona // If the changes fail to apply dotnet-watch fails. // We don't support discarding the changes and letting the user retry. - if (!results.ModuleUpdates.Updates.IsEmpty) + if (!RequireCommit && results.ModuleUpdates.Status is ModuleUpdateStatus.Ready) { _encService.CommitSolutionUpdate(sessionId); } @@ -311,12 +226,12 @@ public async Task GetUpdatesAsync(Solution solution, ImmutableDictiona Status = results.ModuleUpdates.Status switch { ModuleUpdateStatus.None => Status.NoChangesToApply, - ModuleUpdateStatus.Ready or ModuleUpdateStatus.RestartRequired => Status.ReadyToApply, + ModuleUpdateStatus.Ready => Status.ReadyToApply, ModuleUpdateStatus.Blocked => Status.Blocked, _ => throw ExceptionUtilities.UnexpectedValue(results.ModuleUpdates.Status) }, - CompilationDiagnostics = results.GetAllCompilationDiagnostics(), - RudeEdits = results.RudeEdits.SelectAsArray(static re => (re.ProjectId, re.Diagnostics)), + CompilationDiagnostics = results.GetPersistentDiagnostics(), + RudeEdits = results.GetTransientDiagnostics(), ProjectUpdates = results.ModuleUpdates.Updates.SelectAsArray(static update => new Update( update.Module, update.ProjectId, @@ -330,6 +245,18 @@ public async Task GetUpdatesAsync(Solution solution, ImmutableDictiona }; } + public void CommitUpdate() + { + var sessionId = GetDebuggingSession(); + _encService.CommitSolutionUpdate(sessionId); + } + + public void DiscardUpdate() + { + var sessionId = GetDebuggingSession(); + _encService.DiscardSolutionUpdate(sessionId); + } + public void UpdateBaselines(Solution solution, ImmutableArray projectIds) { var sessionId = GetDebuggingSession(); diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 3bd6140ad31b4..4d6a7ef70fc7d 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3226,4 +3226,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Add '{0}' check + + Add negative value check + + + Add negative value or zero check + + + '{0}' cannot be negative + + + '{0}' cannot be negative or zero + \ No newline at end of file diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs index 287b621957335..5e1d3263967f6 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -206,7 +206,16 @@ private static async Task> FindSourceAndMetadataImplemen } else { - return []; + // If a symbol is partial definition, return its implementation part + var implementationPart = symbol switch + { + IMethodSymbol method => method.PartialImplementationPart, + IPropertySymbol property => property.PartialImplementationPart, + IEventSymbol ev => ev.PartialImplementationPart, + _ => symbol, + }; + + return implementationPart is null ? [] : [implementationPart]; } } } diff --git a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs index a472b4ef95d42..e6b2c5afa6f82 100644 --- a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs +++ b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs @@ -46,18 +46,7 @@ internal static class GoToDefinitionFeatureHelpers var definition = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - symbol = definition ?? symbol; - - // If symbol has a partial implementation part, prefer to go to it, since that is where the body is. - symbol = symbol switch - { - IMethodSymbol method => method.PartialImplementationPart, - IPropertySymbol property => property.PartialImplementationPart, - IEventSymbol ev => ev.PartialImplementationPart, - _ => symbol, - } ?? symbol; - - return symbol; + return definition ?? symbol; } public static async Task> GetDefinitionsAsync( diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index b00c93a4f9f7d..eae201a5192da 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -42,16 +42,22 @@ internal abstract class AbstractAddParameterCheckCodeRefactoringProvider< where TBinaryExpressionSyntax : TExpressionSyntax where TSimplifierOptions : SimplifierOptions { - private const string s_isPrefix = "Is"; - private const string s_throwIfPrefix = "ThrowIf"; + private const string IsPrefix = "Is"; + private const string ThrowIfPrefix = "ThrowIf"; - private const string s_nullSuffix = "Null"; - private const string s_nullOrEmptySuffix = "NullOrEmpty"; - private const string s_nullOrWhiteSpaceSuffix = "NullOrWhiteSpace"; + private const string NullSuffix = "Null"; + private const string NullOrEmptySuffix = "NullOrEmpty"; + private const string NullOrWhiteSpaceSuffix = "NullOrWhiteSpace"; - private const string s_throwIfNullName = s_throwIfPrefix + s_nullSuffix; - private const string s_throwIfNullOrEmptyName = s_throwIfPrefix + s_nullOrEmptySuffix; - private const string s_throwIfNullOrWhiteSpaceName = s_throwIfPrefix + s_nullOrWhiteSpaceSuffix; + private const string NegativeSuffix = "Negative"; + private const string NegativeOrZeroSuffix = "NegativeOrZero"; + + private const string ThrowIfNullName = ThrowIfPrefix + NullSuffix; + private const string ThrowIfNullOrEmptyName = ThrowIfPrefix + NullOrEmptySuffix; + private const string ThrowIfNullOrWhiteSpaceName = ThrowIfPrefix + NullOrWhiteSpaceSuffix; + + private const string ThrowIfNegativeName = ThrowIfPrefix + NegativeSuffix; + private const string ThrowIfNegativeOrZeroName = ThrowIfPrefix + NegativeOrZeroSuffix; protected abstract bool CanOffer(SyntaxNode body); protected abstract bool PrefersThrowExpression(TSimplifierOptions options); @@ -118,20 +124,39 @@ protected override async Task> GetRefactoringsForSing { result.Add(CodeAction.Create( string.Format(FeaturesResources.Add_0_check, "string.IsNullOrEmpty"), - cancellationToken => AddStringCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, s_nullOrEmptySuffix, simplifierOptions, cancellationToken), + cancellationToken => AddStringCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, NullOrEmptySuffix, simplifierOptions, cancellationToken), "Add_string_IsNullOrEmpty_check")); result.Add(CodeAction.Create( string.Format(FeaturesResources.Add_0_check, "string.IsNullOrWhiteSpace"), - cancellationToken => AddStringCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, s_nullOrWhiteSpaceSuffix, simplifierOptions, cancellationToken), + cancellationToken => AddStringCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, NullOrWhiteSpaceSuffix, simplifierOptions, cancellationToken), "Add_string_IsNullOrWhiteSpace_check")); } return result.ToImmutableAndClear(); } + var compilation = semanticModel.Compilation; + + if (ParameterValidForNumericCheck(parameter, blockStatement)) + { + var simplifierOptions = (TSimplifierOptions)await document.GetSimplifierOptionsAsync(cancellationToken).ConfigureAwait(false); + + var negativeCheckAction = CodeAction.Create( + FeaturesResources.Add_negative_value_check, + cancellationToken => AddNumericCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, includeZero: false, simplifierOptions, cancellationToken), + nameof(FeaturesResources.Add_negative_value_check)); + + var negativeOrZeroCheckAction = CodeAction.Create( + FeaturesResources.Add_negative_value_or_zero_check, + cancellationToken => AddNumericCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatement, includeZero: true, simplifierOptions, cancellationToken), + nameof(FeaturesResources.Add_negative_value_or_zero_check)); + + return [negativeCheckAction, negativeOrZeroCheckAction]; + } + // Provide 'Enum.IsDefined' check for suitable enum parameters - if (ParameterValidForEnumIsDefinedCheck(parameter, semanticModel.Compilation, blockStatement)) + if (ParameterValidForEnumIsDefinedCheck(parameter, compilation, blockStatement)) { var action = CodeAction.Create( string.Format(FeaturesResources.Add_0_check, "Enum.IsDefined"), @@ -182,7 +207,7 @@ private async Task UpdateDocumentForRefactoringAsync( // commonly used in this regard according to telemetry and UX testing. if (parameter.Type.SpecialType == SpecialType.System_String) { - document = await AddStringCheckAsync(document, parameter, functionDeclaration, (IMethodSymbol)parameter.ContainingSymbol, blockStatement, s_nullOrEmptySuffix, lazySimplifierOptions, cancellationToken).ConfigureAwait(false); + document = await AddStringCheckAsync(document, parameter, functionDeclaration, (IMethodSymbol)parameter.ContainingSymbol, blockStatement, NullOrEmptySuffix, lazySimplifierOptions, cancellationToken).ConfigureAwait(false); continue; } @@ -264,6 +289,51 @@ private static bool IsIfNullCheck(IOperation statement, IParameterSymbol paramet return false; } + private static bool IsIfNumericCheck(IOperation statement, IParameterSymbol parameter) + { + if (statement is IConditionalOperation ifStatement) + { + var condition = ifStatement.Condition.UnwrapImplicitConversion(); + + // parameter < num + // parameter <= num + // parameter > num + // parameter >= num + // num < parameter + // num <= parameter + // num > parameter + // num >= parameter + if (condition is IBinaryOperation { OperatorKind: BinaryOperatorKind.LessThan or BinaryOperatorKind.LessThanOrEqual or BinaryOperatorKind.GreaterThan or BinaryOperatorKind.GreaterThanOrEqual } binaryOperator && + IsNumericCheckOperands(binaryOperator.LeftOperand, binaryOperator.RightOperand, parameter)) + { + return true; + } + // parameter is < num + // parameter is <= num + // parameter is > num + // parameter is >= num + else if (condition is IIsPatternOperation + { + Pattern: IRelationalPatternOperation + { + OperatorKind: BinaryOperatorKind.LessThan or BinaryOperatorKind.LessThanOrEqual or BinaryOperatorKind.GreaterThan or BinaryOperatorKind.GreaterThanOrEqual, + Value: ILiteralOperation value + } + } && value.Type.IsNumericType()) + { + return true; + } + } + + return false; + + static bool IsNumericCheckOperands(IOperation operand1, IOperation operand2, IParameterSymbol parameter) + { + return (IsParameterReference(operand1, parameter) && operand2.IsNumericLiteral()) || + (operand1.IsNumericLiteral() && IsParameterReference(operand2, parameter)); + } + } + private bool ParameterValidForNullCheck(Document document, IParameterSymbol parameter, SemanticModel semanticModel, IBlockOperation? blockStatement, CancellationToken cancellationToken) { @@ -317,6 +387,35 @@ private bool ParameterValidForNullCheck(Document document, IParameterSymbol para return true; } + private bool ParameterValidForNumericCheck(IParameterSymbol parameter, IBlockOperation? blockStatement) + { + if (parameter.RefKind == RefKind.Out) + return false; + + if (parameter.IsDiscard) + return false; + + if (!parameter.Type.IsSignedIntegralType()) + return false; + + if (blockStatement is not null) + { + if (!CanOffer(blockStatement.Syntax)) + return false; + + foreach (var statement in blockStatement.Operations) + { + if (IsIfNumericCheck(statement, parameter)) + return false; + + if (IsAnyThrowIfNumericCheckInvocation(statement, parameter)) + return false; + } + } + + return true; + } + private static (IMethodSymbol? GenericOverload, IMethodSymbol? NonGenericOverload) GetEnumIsDefinedMethods(Compilation compilation) { var enumType = compilation.GetSpecialType(SpecialType.System_Enum); @@ -388,7 +487,7 @@ private static bool IsEnumIsDefinedCheck(IOperation statement, IParameterSymbol return false; } - private static bool IsAnyThrowIfNullInvocation(IOperation statement, IParameterSymbol? parameter) + private static bool IsAnyThrowInvocation(IOperation statement, IParameterSymbol? parameter, ReadOnlySpan possibleTypeNames, Func methodNamePredicate) { if (statement is IExpressionStatementOperation { @@ -396,12 +495,14 @@ private static bool IsAnyThrowIfNullInvocation(IOperation statement, IParameterS { TargetMethod: { - ContainingType.Name: nameof(ArgumentNullException) or nameof(ArgumentException), - Name: s_throwIfNullName or s_throwIfNullOrEmptyName or s_throwIfNullOrWhiteSpaceName, + ContainingType.Name: var containingTypeName, + Name: var methodName, }, Arguments: [{ Value: var argumentValue }, ..] } - }) + } && + possibleTypeNames.Contains(containingTypeName) && + methodNamePredicate(methodName)) { if (argumentValue.UnwrapImplicitConversion() is IParameterReferenceOperation parameterReference) return parameter is null || parameter.Equals(parameterReference.Parameter); @@ -410,6 +511,16 @@ private static bool IsAnyThrowIfNullInvocation(IOperation statement, IParameterS return false; } + private static bool IsAnyThrowIfNullInvocation(IOperation statement, IParameterSymbol? parameter) + { + return IsAnyThrowInvocation(statement, parameter, [nameof(ArgumentNullException), nameof(ArgumentException)], m => m is ThrowIfNullName or ThrowIfNullOrEmptyName or ThrowIfNullOrWhiteSpaceName); + } + + private static bool IsAnyThrowIfNumericCheckInvocation(IOperation statement, IParameterSymbol? parameter) + { + return IsAnyThrowInvocation(statement, parameter, [nameof(ArgumentOutOfRangeException)], m => m.StartsWith(ThrowIfPrefix)); + } + private static bool IsStringCheck(IOperation condition, IParameterSymbol parameter) { if (condition is IInvocationOperation { Arguments: [{ Value: var argumentValue }] } invocation && @@ -443,7 +554,7 @@ private async Task AddNullCheckAsync( return modifiedDocument; // If we can't, then just offer to add an "if (s == null)" statement. - return await AddNullCheckStatementAsync( + return await AddCheckStatementAsync( document, parameter, functionDeclaration, method, blockStatement, (s, g) => CreateNullCheckStatement(s, g, parameter, options), cancellationToken).ConfigureAwait(false); @@ -459,41 +570,57 @@ private async Task AddStringCheckAsync( TSimplifierOptions options, CancellationToken cancellationToken) { - return await AddNullCheckStatementAsync( + return await AddCheckStatementAsync( document, parameter, functionDeclaration, method, blockStatement, (s, g) => CreateStringCheckStatement(s.Compilation, g, parameter, methodNameSuffix, options), cancellationToken).ConfigureAwait(false); } - private static async Task AddNullCheckStatementAsync( + private async Task AddNumericCheckAsync( Document document, IParameterSymbol parameter, SyntaxNode functionDeclaration, IMethodSymbol method, IBlockOperation? blockStatement, - Func generateNullCheck, + bool includeZero, + TSimplifierOptions options, + CancellationToken cancellationToken) + { + return await AddCheckStatementAsync( + document, parameter, functionDeclaration, method, blockStatement, + (s, g) => CreateNumericCheckStatement(s.Compilation, g, parameter, includeZero, options), + cancellationToken).ConfigureAwait(false); + } + + private static async Task AddCheckStatementAsync( + Document document, + IParameterSymbol parameter, + SyntaxNode functionDeclaration, + IMethodSymbol method, + IBlockOperation? blockStatement, + Func generateCheck, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(root, document.Project.Solution.Services); - var nullCheckStatement = generateNullCheck(semanticModel, editor.Generator); + var checkStatement = generateCheck(semanticModel, editor.Generator); // We may be inserting a statement into a single-line container. In that case, // we don't want the formatting engine to think this construct should stay single-line // so add a newline after the check to help dissuade it from thinking we should stay // on a single line. - nullCheckStatement = nullCheckStatement.WithAppendedTrailingTrivia( + checkStatement = checkStatement.WithAppendedTrailingTrivia( editor.Generator.ElasticCarriageReturnLineFeed); - // Find a good location to add the null check. In general, we want the order of checks + // Find a good location to add the check. In general, we want the order of checks // and assignments in the constructor to match the order of parameters in the method // signature. var statementToAddAfter = GetStatementToAddCheckAfter(semanticModel, parameter, blockStatement, cancellationToken); var initializeParameterService = document.GetRequiredLanguageService(); - initializeParameterService.InsertStatement(editor, functionDeclaration, method.ReturnsVoid, statementToAddAfter, nullCheckStatement); + initializeParameterService.InsertStatement(editor, functionDeclaration, method.ReturnsVoid, statementToAddAfter, checkStatement); var newRoot = editor.GetChangedRoot(); return document.WithSyntaxRoot(newRoot); @@ -566,15 +693,14 @@ private TStatementSyntax CreateNullCheckStatement(SemanticModel semanticModel, S if (parameter.Type.IsReferenceType && argumentNullExceptionType != null) { var throwIfNullMethod = argumentNullExceptionType - .GetMembers(s_throwIfNullName) - .OfType() - .FirstOrDefault(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Object }, ..]); + .GetMembers(ThrowIfNullName) + .FirstOrDefault(s => s is IMethodSymbol { Parameters: [{ Type.SpecialType: SpecialType.System_Object }, ..] }); if (throwIfNullMethod != null) { return (TStatementSyntax)generator.ExpressionStatement(generator.InvocationExpression( generator.MemberAccessExpression( generator.TypeExpression(argumentNullExceptionType), - s_throwIfNullName), + ThrowIfNullName), generator.IdentifierName(parameter.Name))); } } @@ -585,17 +711,57 @@ private TStatementSyntax CreateNullCheckStatement(SemanticModel semanticModel, S options); } + private TStatementSyntax CreateNumericCheckStatement(Compilation compilation, SyntaxGenerator generator, IParameterSymbol parameter, bool includeZero, TSimplifierOptions options) + { + var argumentOutOfRangeExceptionType = compilation.ArgumentOutOfRangeExceptionType(); + if (argumentOutOfRangeExceptionType is not null) + { + var throwMethodName = includeZero ? ThrowIfNegativeOrZeroName : ThrowIfNegativeName; + var throwMethod = argumentOutOfRangeExceptionType + .GetMembers(throwMethodName) + .FirstOrDefault(s => s is IMethodSymbol { IsStatic: true, Arity: 1, Parameters.Length: 2 }); + if (throwMethod is not null) + { + // We found 'ThrowIfX' method. Generate 'ArgumentOutOfRangeException.ThrowIfNegative[OrZero](parameter);' + return (TStatementSyntax)generator.ExpressionStatement(generator.InvocationExpression( + generator.MemberAccessExpression( + generator.TypeExpression(argumentOutOfRangeExceptionType), + throwMethodName), + generator.IdentifierName(parameter.Name))); + } + } + + // Generate 'manual check' like + // if (parameter <[=] 0) throw new ArgumentOutOfRangeException(nameof(parameter), parameter, "message"); + var parameterNameExpression = generator.IdentifierName(parameter.Name); + var zeroLiteralExpression = generator.LiteralExpression(0); + var condition = includeZero + ? generator.LessThanOrEqualExpression(parameterNameExpression, zeroLiteralExpression) + : generator.LessThanExpression(parameterNameExpression, zeroLiteralExpression); + + var parameterNameOfExpression = generator.NameOfExpression(parameterNameExpression); + var throwStatement = generator.ThrowStatement( + generator.ObjectCreationExpression( + GetTypeNode(compilation, generator, typeof(ArgumentOutOfRangeException)), + parameterNameOfExpression, + parameterNameExpression, + CreateExceptionMessageArgument(includeZero + ? FeaturesResources._0_cannot_be_negative_or_zero + : FeaturesResources._0_cannot_be_negative, generator, parameterNameOfExpression))); + + return CreateParameterCheckIfStatement((TExpressionSyntax)condition, (TStatementSyntax)throwStatement, options); + } + private TStatementSyntax CreateStringCheckStatement( Compilation compilation, SyntaxGenerator generator, IParameterSymbol parameter, string methodNameSuffix, TSimplifierOptions options) { var argumentExceptionType = compilation.ArgumentExceptionType(); if (argumentExceptionType != null) { - var throwMethodName = "ThrowIf" + methodNameSuffix; + var throwMethodName = ThrowIfPrefix + methodNameSuffix; var throwIfNullMethod = argumentExceptionType .GetMembers(throwMethodName) - .OfType() - .FirstOrDefault(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_String }, ..]); + .FirstOrDefault(s => s is IMethodSymbol { Parameters: [{ Type.SpecialType: SpecialType.System_String }, ..] }); if (throwIfNullMethod != null) { return (TStatementSyntax)generator.ExpressionStatement(generator.InvocationExpression( @@ -609,7 +775,7 @@ private TStatementSyntax CreateStringCheckStatement( var stringType = compilation.GetSpecialType(SpecialType.System_String); // generates: if (string.IsXXX(s)) throw new ArgumentException("message", nameof(s)) - var isMethodName = s_isPrefix + methodNameSuffix; + var isMethodName = IsPrefix + methodNameSuffix; var condition = (TExpressionSyntax)generator.InvocationExpression( generator.MemberAccessExpression( generator.TypeExpression(stringType), @@ -692,8 +858,12 @@ private TStatementSyntax CreateStringCheckStatement( continue; } - if (IsEnumIsDefinedCheck(statement, parameterSymbol, enumIsDefinedGenericMethod, enumIsDefinedNonGenericMethod)) + if (IsAnyThrowIfNumericCheckInvocation(statement, parameterSymbol) || + IsIfNumericCheck(statement, parameterSymbol) || + IsEnumIsDefinedCheck(statement, parameterSymbol, enumIsDefinedGenericMethod, enumIsDefinedNonGenericMethod)) + { return statement; + } if (statement is IConditionalOperation ifStatement) { @@ -790,40 +960,45 @@ private SyntaxNode CreateArgumentException( { var text = methodName switch { - nameof(string.IsNullOrEmpty) => new LocalizableResourceString(nameof(FeaturesResources._0_cannot_be_null_or_empty), FeaturesResources.ResourceManager, typeof(FeaturesResources)).ToString(), - nameof(string.IsNullOrWhiteSpace) => new LocalizableResourceString(nameof(FeaturesResources._0_cannot_be_null_or_whitespace), FeaturesResources.ResourceManager, typeof(FeaturesResources)).ToString(), + nameof(string.IsNullOrEmpty) => FeaturesResources._0_cannot_be_null_or_empty, + nameof(string.IsNullOrWhiteSpace) => FeaturesResources._0_cannot_be_null_or_whitespace, _ => throw ExceptionUtilities.Unreachable(), }; + var nameofExpression = generator.NameOfExpression(generator.IdentifierName(parameter.Name)); + + return generator.ObjectCreationExpression( + GetTypeNode(compilation, generator, typeof(ArgumentException)), + CreateExceptionMessageArgument(text, generator, nameofExpression), + nameofExpression); + } + + private SyntaxNode CreateExceptionMessageArgument(string messageTemplate, SyntaxGenerator generator, SyntaxNode parameterNameOfExpression) + { // The resource string is written to be shown in a UI and is not necessarily valid code, but we're // going to be putting it into a string literal so we need to escape quotes etc. to avoid syntax errors - var escapedText = EscapeResourceString(text); + var escapedText = EscapeResourceString(messageTemplate); using var _ = ArrayBuilder.GetInstance(out var content); - var nameofExpression = generator.NameOfExpression(generator.IdentifierName(parameter.Name)); - - var textParts = GetPreAndPostTextParts(text); + var textParts = GetPreAndPostTextParts(messageTemplate); var escapedTextParts = GetPreAndPostTextParts(escapedText); if (textParts.pre is null) { Debug.Fail("Should have found {0} in the resource string."); - content.Add(InterpolatedStringText(generator, escapedText, text)); + content.Add(InterpolatedStringText(generator, escapedText, messageTemplate)); } else { content.Add(InterpolatedStringText(generator, escapedTextParts.pre!, textParts.pre)); - content.Add(generator.Interpolation(nameofExpression)); + content.Add(generator.Interpolation(parameterNameOfExpression)); content.Add(InterpolatedStringText(generator, escapedTextParts.post!, textParts.post!)); } - return generator.ObjectCreationExpression( - GetTypeNode(compilation, generator, typeof(ArgumentException)), - generator.InterpolatedStringExpression( - generator.CreateInterpolatedStringStartToken(isVerbatim: false), - content, - generator.CreateInterpolatedStringEndToken()), - nameofExpression); + return generator.InterpolatedStringExpression( + generator.CreateInterpolatedStringStartToken(isVerbatim: false), + content, + generator.CreateInterpolatedStringEndToken()); } private static (string? pre, string? post) GetPreAndPostTextParts(string text) diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs index e2238705948fc..ee971b2a1fa05 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs @@ -127,6 +127,8 @@ public IMethodSymbol ReduceExtensionMethod(ITypeSymbol receiverType) public bool IsConditional => _symbol.IsConditional; + public bool IsIterator => _symbol.IsIterator; + public SignatureCallingConvention CallingConvention => _symbol.CallingConvention; public ImmutableArray UnmanagedCallingConventionTypes => _symbol.UnmanagedCallingConventionTypes; diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 7d818e8875dab..1fe725f9f372e 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -104,6 +104,7 @@ + diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 28a36f547a68c..8736055214edf 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -215,6 +215,8 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) return NavigateToItemKind.Property; case DeclaredSymbolInfoKind.Struct: return NavigateToItemKind.Structure; + case DeclaredSymbolInfoKind.Operator: + return NavigateToItemKind.OtherSymbol; default: throw ExceptionUtilities.UnexpectedValue(declaredSymbolInfo.Kind); } diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 325b107a6cf29..b55f11f73a38d 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -280,57 +280,7 @@ int GetCommonFolderCount() #region INavigableItem - Glyph INavigableItem.Glyph => GetGlyph(_item.DeclaredSymbolInfo.Kind, _item.DeclaredSymbolInfo.Accessibility); - - private static Glyph GetPublicGlyph(DeclaredSymbolInfoKind kind) - => kind switch - { - DeclaredSymbolInfoKind.Class => Glyph.ClassPublic, - DeclaredSymbolInfoKind.Constant => Glyph.ConstantPublic, - DeclaredSymbolInfoKind.Constructor => Glyph.MethodPublic, - DeclaredSymbolInfoKind.Delegate => Glyph.DelegatePublic, - DeclaredSymbolInfoKind.Enum => Glyph.EnumPublic, - DeclaredSymbolInfoKind.EnumMember => Glyph.EnumMemberPublic, - DeclaredSymbolInfoKind.Event => Glyph.EventPublic, - DeclaredSymbolInfoKind.ExtensionMethod => Glyph.ExtensionMethodPublic, - DeclaredSymbolInfoKind.Field => Glyph.FieldPublic, - DeclaredSymbolInfoKind.Indexer => Glyph.PropertyPublic, - DeclaredSymbolInfoKind.Interface => Glyph.InterfacePublic, - DeclaredSymbolInfoKind.Method => Glyph.MethodPublic, - DeclaredSymbolInfoKind.Module => Glyph.ModulePublic, - DeclaredSymbolInfoKind.Property => Glyph.PropertyPublic, - DeclaredSymbolInfoKind.Struct => Glyph.StructurePublic, - DeclaredSymbolInfoKind.RecordStruct => Glyph.StructurePublic, - _ => Glyph.ClassPublic, - }; - - private static Glyph GetGlyph(DeclaredSymbolInfoKind kind, Accessibility accessibility) - { - // Glyphs are stored in this order: - // ClassPublic, - // ClassProtected, - // ClassPrivate, - // ClassInternal, - - var rawGlyph = GetPublicGlyph(kind); - - switch (accessibility) - { - case Accessibility.Private: - rawGlyph += (Glyph.ClassPrivate - Glyph.ClassPublic); - break; - case Accessibility.Internal: - rawGlyph += (Glyph.ClassInternal - Glyph.ClassPublic); - break; - case Accessibility.Protected: - case Accessibility.ProtectedOrInternal: - case Accessibility.ProtectedAndInternal: - rawGlyph += (Glyph.ClassProtected - Glyph.ClassPublic); - break; - } - - return rawGlyph; - } + Glyph INavigableItem.Glyph => GlyphExtensions.GetGlyph(_item.DeclaredSymbolInfo.Kind, _item.DeclaredSymbolInfo.Accessibility); ImmutableArray INavigableItem.DisplayTaggedParts => [new TaggedText( diff --git a/src/Features/Core/Portable/SolutionExplorer/ISolutionExplorerSymbolTreeItemProvider.cs b/src/Features/Core/Portable/SolutionExplorer/ISolutionExplorerSymbolTreeItemProvider.cs new file mode 100644 index 0000000000000..954f8e3950331 --- /dev/null +++ b/src/Features/Core/Portable/SolutionExplorer/ISolutionExplorerSymbolTreeItemProvider.cs @@ -0,0 +1,134 @@ +// 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.Text; +using System.Threading; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.SolutionExplorer; + +internal interface ISolutionExplorerSymbolTreeItemProvider : ILanguageService +{ + ImmutableArray GetItems(DocumentId documentId, SyntaxNode declarationNode, CancellationToken cancellationToken); +} + +internal abstract class AbstractSolutionExplorerSymbolTreeItemProvider< + TCompilationUnitSyntax, + TMemberDeclarationSyntax, + TNamespaceDeclarationSyntax, + TEnumDeclarationSyntax, + TTypeDeclarationSyntax> + : ISolutionExplorerSymbolTreeItemProvider + where TCompilationUnitSyntax : SyntaxNode + where TMemberDeclarationSyntax : SyntaxNode + where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax + where TEnumDeclarationSyntax : TMemberDeclarationSyntax +{ + protected static void AppendCommaSeparatedList( + StringBuilder builder, + string openBrace, + string closeBrace, + TArgumentList? argumentList, + Func> getArguments, + Action append, + string separator = ", ") + where TArgumentList : SyntaxNode + where TArgument : SyntaxNode + { + if (argumentList is null) + return; + + AppendCommaSeparatedList(builder, openBrace, closeBrace, getArguments(argumentList), append, separator); + } + + protected static void AppendCommaSeparatedList( + StringBuilder builder, + string openBrace, + string closeBrace, + SeparatedSyntaxList arguments, + Action append, + string separator = ", ") + where TNode : SyntaxNode + { + builder.Append(openBrace); + builder.AppendJoinedValues(separator, arguments, append); + builder.Append(closeBrace); + } + + protected abstract SyntaxList GetMembers(TCompilationUnitSyntax root); + protected abstract SyntaxList GetMembers(TNamespaceDeclarationSyntax baseNamespace); + protected abstract SyntaxList GetMembers(TTypeDeclarationSyntax typeDeclaration); + + protected abstract bool TryAddType(DocumentId documentId, TMemberDeclarationSyntax member, ArrayBuilder items, StringBuilder nameBuilder); + protected abstract void AddMemberDeclaration(DocumentId documentId, TMemberDeclarationSyntax member, ArrayBuilder items, StringBuilder nameBuilder); + protected abstract void AddEnumDeclarationMembers(DocumentId documentId, TEnumDeclarationSyntax enumDeclaration, ArrayBuilder items, CancellationToken cancellationToken); + + public ImmutableArray GetItems(DocumentId documentId, SyntaxNode node, CancellationToken cancellationToken) + { + using var _1 = ArrayBuilder.GetInstance(out var items); + using var _2 = PooledStringBuilder.GetInstance(out var nameBuilder); + + switch (node) + { + case TCompilationUnitSyntax compilationUnit: + AddTopLevelTypes(documentId, compilationUnit, items, nameBuilder, cancellationToken); + break; + + case TEnumDeclarationSyntax enumDeclaration: + AddEnumDeclarationMembers(documentId, enumDeclaration, items, cancellationToken); + break; + + case TTypeDeclarationSyntax typeDeclaration: + AddTypeDeclarationMembers(typeDeclaration); + break; + } + + return items.ToImmutableAndClear(); + + void AddTypeDeclarationMembers(TTypeDeclarationSyntax typeDeclaration) + { + foreach (var member in GetMembers(typeDeclaration)) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (TryAddType(documentId, member, items, nameBuilder)) + continue; + + AddMemberDeclaration(documentId, member, items, nameBuilder); + } + } + } + + private void AddTopLevelTypes( + DocumentId documentId, + TCompilationUnitSyntax root, + ArrayBuilder items, + StringBuilder nameBuilder, + CancellationToken cancellationToken) + { + foreach (var member in GetMembers(root)) + RecurseIntoMemberDeclaration(member); + + return; + + void RecurseIntoMemberDeclaration(TMemberDeclarationSyntax member) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (member is TNamespaceDeclarationSyntax baseNamespace) + { + foreach (var childMember in GetMembers(baseNamespace)) + RecurseIntoMemberDeclaration(childMember); + } + else + { + TryAddType(documentId, member, items, nameBuilder); + } + } + } +} diff --git a/src/Features/Core/Portable/SolutionExplorer/SymbolTreeItemData.cs b/src/Features/Core/Portable/SolutionExplorer/SymbolTreeItemData.cs new file mode 100644 index 0000000000000..b77e51fcc6b1d --- /dev/null +++ b/src/Features/Core/Portable/SolutionExplorer/SymbolTreeItemData.cs @@ -0,0 +1,34 @@ +// 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. + +namespace Microsoft.CodeAnalysis.SolutionExplorer; + +internal readonly record struct SymbolTreeItemKey( + DocumentId DocumentId, + string Name, + Glyph Glyph, + bool HasItems); + +internal readonly record struct SymbolTreeItemSyntax( + SyntaxNode DeclarationNode, + SyntaxToken NavigationToken); + +internal readonly record struct SymbolTreeItemData( + SymbolTreeItemKey ItemKey, + SymbolTreeItemSyntax ItemSyntax) +{ + public SymbolTreeItemData( + DocumentId documentId, + string name, + Glyph glyph, + bool hasItems, + SyntaxNode declarationNode, + SyntaxToken navigationToken) + : this(new(documentId, name, glyph, hasItems), new(declarationNode, navigationToken)) + { + } + + public override string ToString() + => $"""Name="{ItemKey.Name}" Glyph={ItemKey.Glyph} HasItems={ItemKey.HasItems}"""; +} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 21bae01e7936f..fa5e9d4402d48 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -50,6 +50,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Přidat atribut DebuggerDisplay {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Přidat kontroly hodnot null u všech parametrů @@ -3170,6 +3180,16 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Základy {0} + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. Hodnota {0} nemůže být nulová a toto pole nemůže být prázdné. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 6703101cea0b5..0b5eace09efa2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -50,6 +50,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d DebuggerDisplay-Attribut hinzufügen {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters NULL-Überprüfungen für alle Parameter hinzufügen @@ -3170,6 +3180,16 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Basiswerte: “{0}” + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. "{0}" kann nicht NULL oder leer sein. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 2ec27811bf8f2..09e6c259b2320 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -50,6 +50,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Agregar atributo de "DebuggerDisplay" {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Agregar comprobaciones de valores NULL para todos los parámetros @@ -3170,6 +3180,16 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Bases de “{0}” + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' no puede ser nulo ni estar vacío. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 446554fb783eb..1a377151be2b4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -50,6 +50,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Ajouter l'attribut 'DebuggerDisplay' {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Ajouter des vérifications de valeur null pour tous les paramètres @@ -3170,6 +3180,16 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée '{0}' bases + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. « {0} » ne peut pas être vide ou avoir la valeur Null. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 3b5bd9d9a834c..880fa4389d189 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -50,6 +50,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Aggiungi l'attributo 'DebuggerDisplay' {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Aggiungi controlli Null per tutti i parametri @@ -3170,6 +3180,16 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Basi di '{0}' + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' non può essere null o vuoto. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 5a8d3a2262203..239f4544c29b1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -50,6 +50,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 'DebuggerDisplay' 属性の追加 {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters すべてのパラメーターに対して null チェックを追加する @@ -3170,6 +3180,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of '{0}' ベース + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' を NULL または空にすることはできません。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 0680c861b8bf0..bb34ab410c646 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -50,6 +50,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 'DebuggerDisplay' 특성을 추가합니다. {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters 모든 매개 변수에 대한 null 검사 추가 @@ -3170,6 +3180,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of '{0}' 기본 + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}'은(는) Null이거나 비워 둘 수 없습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 11f59f5c77cf1..bbf4ab1933685 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -50,6 +50,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Dodaj atrybut "DebuggerDisplay" {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Dodaj sprawdzenia wartości null dla wszystkich parametrów @@ -3170,6 +3180,16 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Wartości podstawowe „{0}” + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. Element „{0}” nie może mieć wartości null ani być pusty. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index da7b61b3adb0a..6b21337d1f1ed 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -50,6 +50,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Adicionar atributo 'DebuggerDisplay' {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Adicionar verificações nulas para todos os parâmetros @@ -3170,6 +3180,16 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Bases '{0}' + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' não pode ser nulo nem vazio. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 6cf3f47134875..0a1baaa9c8064 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -50,6 +50,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Добавить атрибут "DebuggerDisplay" {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Добавление проверки NULL для всех параметров @@ -3170,6 +3180,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Основания: “{0}” + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. "{0}" не может быть неопределенным или пустым. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 90fa99a150d46..54bc1368d36c9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -50,6 +50,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be 'DebuggerDisplay' özniteliği ekle {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters Tüm parametreler için null denetimi ekle @@ -3170,6 +3180,16 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri '{0}' tabanları + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' null veya boş olamaz. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index fd07af937e8d7..4122150e427ed 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -50,6 +50,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 添加 "DebuggerDisplay" 属性 {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters 为所有参数添加 null 检查 @@ -3170,6 +3180,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of “{0}”基数 + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. “{0}”不能为 null 或空。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 95b987e61e626..bc1483d57f47e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -50,6 +50,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 新增 'DebuggerDisplay' 屬性 {Locked="DebuggerDisplay"} "DebuggerDisplay" is a BCL class and should not be localized. + + Add negative value check + Add negative value check + + + + Add negative value or zero check + Add negative value or zero check + + Add null checks for all parameters 為所有參數新增 null 檢查 @@ -3170,6 +3180,16 @@ Zero-width positive lookbehind assertions are typically used at the beginning of '{0}' 個基底 + + '{0}' cannot be negative + '{0}' cannot be negative + + + + '{0}' cannot be negative or zero + '{0}' cannot be negative or zero + + '{0}' cannot be null or empty. '{0}' 不可為 Null 或空白。 diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs index c0f9333a4d827..35cc25a660ad9 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs @@ -28,7 +28,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics public abstract partial class AbstractUserDiagnosticTest_NoEditor { // TODO: IInlineRenameService requires WPF (https://github.com/dotnet/roslyn/issues/46153) - private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) .AddParts( typeof(MockDiagnosticUpdateSourceRegistrationService), diff --git a/src/Features/ExternalAccess/Copilot/Analyzer/CopilotAnalysisUtilities.cs b/src/Features/ExternalAccess/Copilot/Analyzer/CopilotAnalysisUtilities.cs new file mode 100644 index 0000000000000..9963fd48043be --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Analyzer/CopilotAnalysisUtilities.cs @@ -0,0 +1,23 @@ +// 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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot; + +internal static class CopilotAnalysisUtilities +{ + public static Task AnalyzeCopilotChangeAsync( + Document document, + bool accepted, + string featureId, + string proposalId, + IEnumerable textChanges, + CancellationToken cancellationToken) + => CopilotChangeAnalysisUtilities.AnalyzeCopilotChangeAsync(document, accepted, featureId, proposalId, textChanges, cancellationToken); +} diff --git a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index 5203c1fd27fa9..c51185e9ddd84 100644 --- a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -30,6 +30,7 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.TraitItem(Mic Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.TraitItem(string! name, string! value, int importance = 0) -> void Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Value.get -> string! Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Value.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotAnalysisUtilities Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper? other) -> bool Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentProposalWrapper @@ -125,6 +126,7 @@ static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem. static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.operator ==(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? right) -> bool static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.operator !=(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? right) -> bool static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.operator ==(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? right) -> bool +static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotAnalysisUtilities.AnalyzeCopilotChangeAsync(Microsoft.CodeAnalysis.Document! document, bool accepted, string! featureId, string! proposalId, System.Collections.Generic.IEnumerable! textChanges, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Create(System.Collections.Immutable.ImmutableArray values) -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.GetContainingMethodDeclarationAsync(Microsoft.CodeAnalysis.Document! document, int position, bool useFullSpan, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.GetCopilotSuggestionDiagnosticTag() -> string! diff --git a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 1d1bf2eaacfca..aee2f7829455a 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -518,20 +518,22 @@ public async Task ErrorReadingModuleFile(bool breakMode) var document2 = solution.GetDocument(document1.Id); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal( + [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], + InspectDiagnostics(results.Diagnostics)); // correct the error: EmitLibrary(projectId, source2); - var (updates2, emitDiagnostics2) = await EmitSolutionUpdateAsync(debuggingSession, solution); + var (updates2, diagnostics2) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, updates2.Status); - Assert.Empty(emitDiagnostics2); + Assert.Empty(diagnostics2); CommitSolutionUpdate(debuggingSession); @@ -596,14 +598,16 @@ public async Task ErrorReadingPdbFile() var document2 = solution.GetDocument(document1.Id); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal( + [$"proj: {document2.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], + InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); @@ -642,22 +646,24 @@ public async Task ErrorReadingSourceFile() using var fileLock = File.Open(sourceFile.Path, FileMode.Open, FileAccess.Read, FileShare.None); // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal( + [$"test: {document1.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], + InspectDiagnostics(results.Diagnostics)); fileLock.Dispose(); // try apply changes again: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.NotEmpty(updates.Updates); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.NotEmpty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); @@ -950,14 +956,16 @@ void M() // We do not report module diagnostics until emit. // This is to make the analysis deterministic (not dependent on the current state of the debuggee). - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - AssertEx.Empty(diagnostics1); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + AssertEx.Empty(docDiagnostics); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal([$"{document2.FilePath}: (5,0)-(5,32): Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal( + [$"proj: {document2.FilePath}: (5,0)-(5,32): Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}"], + InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); @@ -1048,15 +1056,16 @@ public async Task RudeEdits(bool breakMode) solution = solution.WithDocumentText(document1.Id, CreateText(source2)); var document2 = solution.GetDocument(document1.Id); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal(["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], - diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + AssertEx.Equal( + [$"{document2.FilePath}: (0,18)-(0,19): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); if (breakMode) { @@ -1072,7 +1081,7 @@ public async Task RudeEdits(bool breakMode) if (breakMode) { - AssertEx.Equal( + AssertEx.SequenceEqual( [ "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2", "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=", @@ -1081,7 +1090,7 @@ public async Task RudeEdits(bool breakMode) } else { - AssertEx.Equal( + AssertEx.SequenceEqual( [ "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0", "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=", @@ -1189,14 +1198,15 @@ class C { int Y => 2; } var generatedDocument = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single(); - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal(["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], - diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None); + AssertEx.Equal( + [$"{generatedDocument.FilePath}: (0,17)-(0,18): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -1240,13 +1250,13 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) var document2 = solution.GetDocument(documentId); // no Rude Edits, since the document is out-of-sync - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); // the document is out-of-sync, so no rude edits reported: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); // TODO: warning reported https://github.com/dotnet/roslyn/issues/78125 // AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); @@ -1255,30 +1265,28 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) // We don't check if the content on disk has been updated to match either. // Document state can only be reset via UpdateBaselines. sourceFile.WriteAllText(source0, Encoding.UTF8); - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); // rebuild triggers reload of out-of-sync file content: moduleId = EmitAndLoadLibraryToDebuggee(projectId, source0, sourceFilePath: sourceFile.Path); debuggingSession.UpdateBaselines(solution.WithDocumentText(documentId, CreateText(source0)), [projectId]); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); // now we see the rude edit: - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - [ - "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) - ], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{document2.FilePath}: (0,11)-(0,22): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); if (breakMode) { @@ -1341,16 +1349,16 @@ public async Task RudeEdits_DocumentWithoutSequencePoints() var document2 = solution.Projects.Single().Documents.Single(); // Rude Edits reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{document1.FilePath}: (0,45)-(0,69): Error ENC0023: {string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -1384,36 +1392,36 @@ public async Task RudeEdits_DelayLoadedModule() var document2 = solution.Projects.Single().Documents.Single(); // Rude Edits reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); // load library to the debuggee: LoadLibraryToDebuggee(moduleId); // Rude Edits still reported: - diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); + docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); EndDebuggingSession(debuggingSession); } [Theory] [CombinatorialData] - public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) + public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool allowPartialUpdates) { var source1 = "abstract class C { }"; var source2 = "abstract class C { void F() {} }"; @@ -1432,19 +1440,18 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) var debuggingSession = await StartDebuggingSessionAsync(service, solution); - EmitSolutionUpdateResults result; + EmitSolutionUpdateResults results; var readers = ImmutableArray.Empty; - var runningProjects = ImmutableDictionary.Empty.Add(projectId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = true }); // change the source (valid edit): if (validChangeBeforeRudeEdit) { solution = solution.WithDocumentText(documentId, CreateText(source2)); - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); - Assert.Equal(ModuleUpdateStatus.Ready, result.ModuleUpdates.Status); - Assert.Empty(result.ProjectsToRebuild); - Assert.Empty(result.ProjectsToRestart); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ProjectsToRebuild); + Assert.Empty(results.ProjectsToRestart); // baseline should be present: readers = debuggingSession.GetTestAccessor().GetBaselineModuleReaders(); @@ -1464,22 +1471,34 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) solution = solution.WithDocumentText(documentId, CreateText(source3)); // Rude Edits reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{document.FilePath}: (0,31)-(0,55): Error ENC0023: {string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); - Assert.Equal(ModuleUpdateStatus.RestartRequired, result.ModuleUpdates.Status); - AssertEx.Equal([projectId], result.ProjectsToRebuild); - AssertEx.Equal([projectId], result.ProjectsToRestart.Keys); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.GetAllDiagnostics())); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + AssertEx.Equal([projectId], results.ProjectsToRebuild); + AssertEx.Equal([projectId], results.ProjectsToRestart.Keys); + + if (allowPartialUpdates) + { + // assuming user approved restart and rebuild: + CommitSolutionUpdate(debuggingSession); + } - // restart and rebuild: + // rebuild and restart: _debuggerService.LoadedModules.Remove(moduleId); File.WriteAllText(sourceFilePath, source3, Encoding.UTF8); moduleId = EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentId)); - debuggingSession.UpdateBaselines(solution, result.ProjectsToRebuild); + + if (!allowPartialUpdates) + { + debuggingSession.UpdateBaselines(solution, results.ProjectsToRebuild); + } if (validChangeBeforeRudeEdit) { @@ -1500,8 +1519,8 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) Assert.Empty(await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None)); // apply valid change: - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); - Assert.Equal(ModuleUpdateStatus.Ready, result.ModuleUpdates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); EndDebuggingSession(debuggingSession); @@ -1766,7 +1785,7 @@ public async Task HasChanges_Documents(TextDocumentKind documentKind) AssertEx.Equal([generatedDocumentId], await EditSession.GetChangedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None)); - var diagnostics = new ArrayBuilder(); + var diagnostics = new ArrayBuilder(); await EditSession.GetDocumentDifferencesAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), documentDifferences, diagnostics, CancellationToken.None); Assert.Empty(diagnostics); Assert.Empty(documentDifferences.Deleted); @@ -1932,9 +1951,9 @@ public async Task HasChanges_SourceGeneratorFailure() // No changed source documents since the generator failed: AssertEx.Empty(await EditSession.GetChangedDocumentsAsync(log, oldProject, project, CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None)); - var diagnostics = new ArrayBuilder(); + var diagnostics = new ArrayBuilder(); await EditSession.GetDocumentDifferencesAsync(log, oldProject, project, documentDiffences, diagnostics, CancellationToken.None); - Assert.Contains("System.InvalidOperationException: Source generator failed", diagnostics.Single().Diagnostics.Single().GetMessage()); + Assert.Contains("System.InvalidOperationException: Source generator failed", diagnostics.Single().GetMessage()); AssertEx.Empty(documentDiffences.ChangedOrAdded); AssertEx.Equal(["generated.cs"], documentDiffences.Deleted.Select(d => d.Name)); @@ -2863,9 +2882,10 @@ partial class C { int Y = 2; } EndDebuggingSession(debuggingSession); } - [Fact] + [Theory] + [CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/78244")] - public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit() + public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit(bool allowPartialUpdate) { var sourceA1 = """ using System; @@ -2926,29 +2946,39 @@ void F() {} .WithDocumentText(documentBId, CreateText(sourceB2)); // Rude Edit reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentBId), s_noActiveSpans, CancellationToken.None); + var documentB = solution.GetRequiredDocument(documentBId); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + [$"{documentB.FilePath}: (4,4)-(4,12): Error ENC0023: {string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)}"], + InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate); + + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics)); + + if (allowPartialUpdate) + { + AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRebuild); + AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRestart.Keys); + + var delta = results.ModuleUpdates.Updates.Single(); + Assert.NotEmpty(delta.ILDelta); + Assert.NotEmpty(delta.MetadataDelta); + Assert.NotEmpty(delta.PdbDelta); + Assert.Equal(1, delta.UpdatedMethods.Length); - // TODO: https://github.com/dotnet/roslyn/issues/78244 - // Should emit delta for the valid change + debuggingSession.DiscardSolutionUpdate(); + } + else + { + AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRebuild); + AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRestart.Keys); - //// check emitted delta: - //var delta = updates.Updates.Single(); - //Assert.Empty(delta.ActiveStatements); - //Assert.NotEmpty(delta.ILDelta); - //Assert.NotEmpty(delta.MetadataDelta); - //Assert.NotEmpty(delta.PdbDelta); - //Assert.Equal(6, delta.UpdatedMethods.Length); // F, C.C(), D.D(), E.E(int), E.E(int, int), lambda - //AssertEx.SetEqual([0x02000002, 0x02000003, 0x02000004, 0x02000005], delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); + Assert.Empty(results.ModuleUpdates.Updates); + } - //debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -3015,10 +3045,11 @@ static B() .WithDocumentText(documentBId, CreateText(sourceB2)); // no-effect warning reported: - var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentBId), s_noActiveSpans, CancellationToken.None); + var documentB = solution.GetRequiredDocument(documentBId); + var diagnostics = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - ["ENC0118: Warning: " + string.Format(FeaturesResources.Changing_0_might_not_have_any_effect_until_the_application_is_restarted, FeaturesResources.static_constructor)], - diagnostics.Select(d => $"{d.Id}: {d.Severity}: {d.GetMessage()}")); + [$"{documentB.FilePath}: (2,4)-(2,14): Warning ENC0118: {string.Format(FeaturesResources.Changing_0_might_not_have_any_effect_until_the_application_is_restarted, FeaturesResources.static_constructor)}"], + InspectDiagnostics(diagnostics)); // TODO: Set RestartWhenChangesHaveNoEffect=true and AllowPartialUpdate=true // https://github.com/dotnet/roslyn/issues/78244 @@ -3032,7 +3063,7 @@ static B() AssertEx.SetEqual([], result.ProjectsToRestart.Select(p => p.Key.DebugName)); var updates = result.ModuleUpdates; - Assert.Empty(result.Diagnostics); + AssertEx.SequenceEqual(["ENC0118"], InspectDiagnosticIds(result.GetAllDiagnostics())); Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); // check emitted delta: @@ -3124,8 +3155,9 @@ class C { int Y => 2; } workspaceConfig.Options = new WorkspaceConfigurationOptions(executionPreference); (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator: generator); + var projectId = document1.Project.Id; - var moduleId = EmitLibrary(document1.Project.Id, sourceV1, generatorProject: document1.Project); + var moduleId = EmitLibrary(projectId, sourceV1, generatorProject: document1.Project); LoadLibraryToDebuggee(moduleId); // Trigger initial source generation before debugging session starts. @@ -3142,8 +3174,7 @@ class C { int Y => 2; } solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2)); // validate solution update status and emit: - var results = (await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None).ConfigureAwait(false)).Dehydrate(); - var diagnostics = results.GetAllDiagnostics(); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); var generatedFilePath = Path.Combine( TempRoot.Root, @@ -3152,13 +3183,8 @@ class C { int Y => 2; } "Generated_test1.cs"); AssertEx.Equal( - [ - $@"ENC0021: '{generatedFilePath}' (0,0)-(0,56): " + - string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.attribute), - ], diagnostics.Select(d => $"{d.Id}: '{d.FilePath}' {d.Span.GetDebuggerDisplay()}: {d.Message}")); - - Assert.Equal(ModuleUpdateStatus.RestartRequired, results.ModuleUpdates.Status); - Assert.Empty(results.ModuleUpdates.Updates); + [$"proj: {generatedFilePath}: (0,0)-(0,56): Error ENC0021: {string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.attribute)}"], + InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -3805,9 +3831,12 @@ public async Task ValidSignificantChange_BaselineCreationFailed_NoStream() // change the source (valid edit): solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], InspectDiagnostics(emitDiagnostics)); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + AssertEx.Equal( + [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], + InspectDiagnostics(results.Diagnostics)); + + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); } [Fact] @@ -3838,9 +3867,12 @@ public async Task ValidSignificantChange_BaselineCreationFailed_AssemblyReadErro var document1 = solution.Projects.Single().Documents.Single(); solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"{document.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], InspectDiagnostics(emitDiagnostics)); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + AssertEx.Equal( + [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], + InspectDiagnostics(results.Diagnostics)); + + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); EndDebuggingSession(debuggingSession); @@ -4371,13 +4403,14 @@ int F() solution = solution.WithDocumentText(document.Id, CreateText(source2)); document = solution.GetDocument(document.Id); - var diagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal(["ENC0063: " + string.Format(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application, CSharpFeaturesResources.catch_clause)], - diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None); + AssertEx.Equal( + [$"{document.FilePath}: (9,8)-(9,13): Error ENC0063: {string.Format(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application, CSharpFeaturesResources.catch_clause)}"], + InspectDiagnostics(docDiagnostics)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + AssertEx.SequenceEqual(["ENC0063"], InspectDiagnosticIds(results.Diagnostics)); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // undo the change solution = solution.WithDocumentText(document.Id, CreateText(source1)); @@ -4388,13 +4421,13 @@ int F() // change the source (now a valid edit since there is no active statement) solution = solution.WithDocumentText(document.Id, CreateText(source2)); - diagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None); - Assert.Empty(diagnostics); + docDiagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None); + Assert.Empty(docDiagnostics); // validate solution update status and emit (Hot Reload change): - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); diff --git a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs index db72d32fd5a77..bd010741dcf4e 100644 --- a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs +++ b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -46,10 +45,10 @@ private static ManagedHotReloadUpdate CreateMockUpdate(ProjectId projectId) activeStatements: [], exceptionRegions: []); - private static ModuleUpdates CreateValidUpdates(params IEnumerable projectIds) - => new(ModuleUpdateStatus.Blocked, [.. projectIds.Select(CreateMockUpdate)]); + private static ImmutableArray CreateValidUpdates(params IEnumerable projectIds) + => [.. projectIds.Select(CreateMockUpdate)]; - private static ArrayBuilder CreateProjectRudeEdits(IEnumerable blocking, IEnumerable noEffect) + private static ImmutableArray CreateProjectRudeEdits(IEnumerable blocking, IEnumerable noEffect) => [.. blocking.Select(id => (id, kind: RudeEditKind.InternalError)).Concat(noEffect.Select(id => (id, kind: RudeEditKind.UpdateMightNotHaveAnyEffect))) .GroupBy(e => e.id) .OrderBy(g => g.Key) @@ -139,8 +138,7 @@ public async Task GetHotReloadDiagnostics() var data = new EmitSolutionUpdateResults.Data() { - Diagnostics = diagnostics, - RudeEdits = rudeEdits, + Diagnostics = [.. diagnostics, .. rudeEdits], SyntaxError = syntaxError, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Blocked, Updates: []), ProjectsToRebuild = [], @@ -149,7 +147,7 @@ public async Task GetHotReloadDiagnostics() var actual = data.GetAllDiagnostics(); - AssertEx.Equal( + AssertEx.SetEqual( [ $@"Warning CS0001: {razorPath1} (10,10)-(10,15): warning", $@"Error CS0012: {razorPath2} (10,10)-(10,15): error", diff --git a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 8c84333be83a5..a2c51883bcb7d 100644 --- a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -213,7 +213,6 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution Solution = solution, ModuleUpdates = updates, Diagnostics = diagnostics, - RudeEdits = [], SyntaxError = syntaxError, ProjectsToRebuild = [project.Id], ProjectsToRestart = ImmutableDictionary>.Empty.Add(project.Id, []), diff --git a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs index 322572403d401..c9912a0979d53 100644 --- a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs +++ b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. #if NET -#nullable disable using System; using System.Collections.Immutable; @@ -28,11 +27,15 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests; [UseExportProvider] public sealed class WatchHotReloadServiceTests : EditAndContinueWorkspaceTestBase { - [Fact] - public async Task Test() + [Theory] + [CombinatorialData] + public async Task Test(bool requireCommit) { // See https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs#L125 + // Note that xUnit does not run test case of a theory in parallel, so we can set global state here: + WatchHotReloadService.RequireCommit = requireCommit; + var source1 = "class C { void M() { System.Console.WriteLine(1); } }"; var source2 = "class C { void M() { System.Console.WriteLine(2); /*2*/} }"; var source3 = "class C { void M() { System.Console.WriteLine(2); /*3*/} }"; @@ -69,7 +72,7 @@ public async Task Test() AssertEx.Equal( [ "(A, MatchesBuildOutput)" - ], matchingDocuments.Select(e => (solution.GetDocument(e.id).Name, e.state)).OrderBy(e => e.Name).Select(e => e.ToString())); + ], matchingDocuments.Select(e => (solution.GetRequiredDocument(e.id).Name, e.state)).OrderBy(e => e.Name).Select(e => e.ToString())); // Valid update: solution = solution.WithDocumentText(documentIdA, CreateText(source2)); @@ -79,6 +82,11 @@ public async Task Test() Assert.Equal(1, result.ProjectUpdates.Length); AssertEx.Equal([0x02000002], result.ProjectUpdates[0].UpdatedTypes); + if (requireCommit) + { + hotReload.CommitUpdate(); + } + // Insignificant change: solution = solution.WithDocumentText(documentIdA, CreateText(source3)); @@ -113,6 +121,13 @@ public async Task Test() AssertEx.SetEqual(["P"], result.ProjectsToRestart.Select(p => solution.GetRequiredProject(p.Key).Name)); AssertEx.SetEqual(["P"], result.ProjectsToRebuild.Select(p => solution.GetRequiredProject(p).Name)); + if (requireCommit) + { + // Emulate the user making choice to not restart. + // dotnet-watch then waits until Ctrl+R forces restart. + hotReload.DiscardUpdate(); + } + // Syntax error: solution = solution.WithDocumentText(documentIdA, CreateText(source5)); @@ -150,7 +165,7 @@ public async Task SourceGeneratorFailure() { generatorExecutionCount++; - var additionalText = context.AdditionalFiles.Single().GetText().ToString(); + var additionalText = context.AdditionalFiles.Single().GetText()!.ToString(); if (additionalText.Contains("updated")) { throw new InvalidOperationException("Source generator failed"); diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs index 8f62eed06aed2..993d3b16806f2 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs @@ -224,16 +224,67 @@ internal static void EndDebuggingSession(DebuggingSession session) Solution solution, ActiveStatementSpanProvider? activeStatementSpanProvider = null) { - var result = await session.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); + var runningProjects = solution.ProjectIds.ToImmutableDictionary( + keySelector: id => id, + elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = false, RestartWhenChangesHaveNoEffect = false }); + + var result = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); return (result.ModuleUpdates, result.Diagnostics.OrderBy(d => d.ProjectId.DebugName).ToImmutableArray().ToDiagnosticData(solution)); } + internal static async ValueTask EmitSolutionUpdateAsync( + DebuggingSession session, + Solution solution, + bool allowPartialUpdate, + ActiveStatementSpanProvider? activeStatementSpanProvider = null) + { + var runningProjects = solution.ProjectIds.ToImmutableDictionary( + keySelector: id => id, + elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = allowPartialUpdate, RestartWhenChangesHaveNoEffect = false }); + + var results = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); + + var hasTransientError = results.Diagnostics.SelectMany(pd => pd.Diagnostics).Any(d => d.IsEncDiagnostic() && d.Severity == DiagnosticSeverity.Error); + + Assert.Equal(hasTransientError, results.ProjectsToRestart.Any()); + Assert.Equal(hasTransientError, results.ProjectsToRebuild.Any()); + + if (!allowPartialUpdate) + { + // No updates should be produced if transient error is reported: + Assert.True(!hasTransientError || results.ModuleUpdates.Updates.IsEmpty); + } + + return results; + } + internal static IEnumerable InspectDiagnostics(ImmutableArray actual) => actual.Select(InspectDiagnostic); internal static string InspectDiagnostic(DiagnosticData diagnostic) => $"{(string.IsNullOrWhiteSpace(diagnostic.DataLocation.MappedFileSpan.Path) ? diagnostic.ProjectId.ToString() : diagnostic.DataLocation.MappedFileSpan.ToString())}: {diagnostic.Severity} {diagnostic.Id}: {diagnostic.Message}"; + internal static IEnumerable InspectDiagnostics(ImmutableArray actual) + => actual.SelectMany(pd => pd.Diagnostics.Select(d => $"{pd.ProjectId.DebugName}: {InspectDiagnostic(d)}")); + + internal static string InspectDiagnostic(Diagnostic actual) + => $"{Inspect(actual.Location)}: {actual.Severity} {actual.Id}: {actual.GetMessage()}"; + + internal static string Inspect(Location actual) + => actual.GetLineSpan() is { IsValid: true } span ? span.ToString() : ""; + + internal static IEnumerable InspectDiagnostics(ImmutableArray actual) + => actual.Select(InspectDiagnostic); + + internal static IEnumerable InspectDiagnosticIds(ImmutableArray actual) + => actual.Select(d => d.Id); + + internal static IEnumerable InspectDiagnosticIds(ImmutableArray actual) + => InspectDiagnosticIds(actual.SelectMany(pd => pd.Diagnostics)); + + internal static IEnumerable InspectDiagnosticIds(IEnumerable actual) + => actual.Select(d => d.Id); + internal static Guid ReadModuleVersionId(Stream stream) { using var peReader = new PEReader(stream); diff --git a/src/Features/TestUtilities/SolutionExplorer/AbstractSolutionExplorerSymbolTreeItemProviderTests.cs b/src/Features/TestUtilities/SolutionExplorer/AbstractSolutionExplorerSymbolTreeItemProviderTests.cs new file mode 100644 index 0000000000000..2cd0b675d4359 --- /dev/null +++ b/src/Features/TestUtilities/SolutionExplorer/AbstractSolutionExplorerSymbolTreeItemProviderTests.cs @@ -0,0 +1,46 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.SolutionExplorer; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Test.Utilities.SolutionExplorer; + +public abstract class AbstractSolutionExplorerSymbolTreeItemProviderTests +{ + protected abstract TestWorkspace CreateWorkspace(string code); + + protected async Task TestNode( + string code, string expected) where TNode : SyntaxNode + { + using var workspace = CreateWorkspace(code); + var testDocument = workspace.Documents.Single(); + var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var root = await document.GetRequiredSyntaxRootAsync(CancellationToken.None); + + var service = document.GetRequiredLanguageService(); + + var node = root.DescendantNodesAndSelf().OfType().First(); + var diagnostics = node.GetDiagnostics(); + Assert.Empty(diagnostics); + + var items = service.GetItems(document.Id, node, CancellationToken.None); + + var actual = string.Join("\r\n", items); + AssertEx.SequenceEqual( + expected.Trim().Split(["\r\n"], StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()), + items.Select(i => i.ToString())); + + AssertEx.SequenceEqual( + testDocument.SelectedSpans, + items.Select(i => i.ItemSyntax.NavigationToken.Span)); + } +} diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/SyncNamespace/VisualBasicChangeNamespaceService.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/SyncNamespace/VisualBasicChangeNamespaceService.vb index 41fd0cc2fa77b..a333205724464 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/SyncNamespace/VisualBasicChangeNamespaceService.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/SyncNamespace/VisualBasicChangeNamespaceService.vb @@ -8,19 +8,29 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.ChangeNamespace Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.LanguageService +Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeNamespace - Friend Class VisualBasicChangeNamespaceService - Inherits AbstractChangeNamespaceService(Of NamespaceStatementSyntax, CompilationUnitSyntax, StatementSyntax) + Friend NotInheritable Class VisualBasicChangeNamespaceService + Inherits AbstractChangeNamespaceService(Of + CompilationUnitSyntax, + StatementSyntax, + NamespaceStatementSyntax, + NameSyntax, + SimpleNameSyntax, + CrefReferenceSyntax) Public Sub New() End Sub + Public Overrides ReadOnly Property NameReducer As AbstractReducer = New VisualBasicNameReducer() + Public Overrides Function TryGetReplacementReferenceSyntax(reference As SyntaxNode, newNamespaceParts As ImmutableArray(Of String), syntaxFacts As ISyntaxFactsService, ByRef old As SyntaxNode, ByRef [new] As SyntaxNode) As Boolean Dim nameRef = TryCast(reference, SimpleNameSyntax) old = nameRef @@ -41,7 +51,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeNamespace [new] = [new].WithTriviaFrom(old) - ElseIf syntaxFacts.IsNameOfsimpleMemberAccessExpression(nameRef) Then + ElseIf syntaxFacts.IsNameOfSimpleMemberAccessExpression(nameRef) Then old = nameRef.Parent If IsGlobalNamespace(newNamespaceParts) Then [new] = SyntaxFactory.SimpleMemberAccessExpression(SyntaxFactory.GlobalName(), nameRef.WithoutTrivia()) diff --git a/src/Features/VisualBasic/Portable/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProvider.vb b/src/Features/VisualBasic/Portable/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProvider.vb new file mode 100644 index 0000000000000..6ed9b701d3f9d --- /dev/null +++ b/src/Features/VisualBasic/Portable/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProvider.vb @@ -0,0 +1,401 @@ +' 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. + +Imports System.Composition +Imports System.Text +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.FindSymbols +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.SolutionExplorer +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.SolutionExplorer + + Friend NotInheritable Class VisualBasicSolutionExplorerSymbolTreeItemProvider + Inherits AbstractSolutionExplorerSymbolTreeItemProvider(Of + CompilationUnitSyntax, + StatementSyntax, + NamespaceBlockSyntax, + EnumBlockSyntax, + TypeBlockSyntax) + + + + Public Sub New() + End Sub + + Protected Overrides Function GetMembers(root As CompilationUnitSyntax) As SyntaxList(Of StatementSyntax) + Return root.Members + End Function + + Protected Overrides Function GetMembers(baseNamespace As NamespaceBlockSyntax) As SyntaxList(Of StatementSyntax) + Return baseNamespace.Members + End Function + + Protected Overrides Function GetMembers(typeDeclaration As TypeBlockSyntax) As SyntaxList(Of StatementSyntax) + Return typeDeclaration.Members + End Function + + Protected Overrides Function TryAddType(documentId As DocumentId, member As StatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) As Boolean + Dim typeBlock = TryCast(member, TypeBlockSyntax) + If typeBlock IsNot Nothing Then + AddTypeBlock(documentId, typeBlock, items, nameBuilder) + Return True + End If + + Dim enumBlock = TryCast(member, EnumBlockSyntax) + If enumBlock IsNot Nothing Then + AddEnumBlock(documentId, enumBlock, items) + Return True + End If + + Dim delegateStatement = TryCast(member, DelegateStatementSyntax) + If delegateStatement IsNot Nothing Then + AddDelegateStatement(documentId, delegateStatement, items, nameBuilder) + Return True + End If + + Return False + End Function + + Private Shared Sub AddTypeBlock(documentId As DocumentId, typeBlock As TypeBlockSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + Dim blockStatement As TypeStatementSyntax = typeBlock.BlockStatement + + nameBuilder.Append(blockStatement.Identifier.ValueText) + AppendTypeParameterList(nameBuilder, blockStatement.TypeParameterList) + + Dim kind = GetDeclaredSymbolInfoKind(typeBlock) + Dim accessibility = GetAccessibility(typeBlock.Parent, blockStatement, blockStatement.Modifiers) + + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(kind, accessibility), + hasItems:=typeBlock.Members.Count > 0, + typeBlock, + blockStatement.Identifier)) + End Sub + + Private Shared Sub AddEnumBlock(documentId As DocumentId, enumBlock As EnumBlockSyntax, items As ArrayBuilder(Of SymbolTreeItemData)) + Dim accessibility = GetAccessibility(enumBlock.Parent, enumBlock.EnumStatement, enumBlock.EnumStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + enumBlock.EnumStatement.Identifier.ValueText, + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Enum, accessibility), + hasItems:=enumBlock.Members.Count > 0, + enumBlock, + enumBlock.EnumStatement.Identifier)) + End Sub + + Private Shared Sub AddDelegateStatement(documentId As DocumentId, delegateStatement As DelegateStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append(delegateStatement.Identifier.ValueText) + AppendTypeParameterList(nameBuilder, delegateStatement.TypeParameterList) + AppendParameterList(nameBuilder, delegateStatement.ParameterList) + AppendAsClause(nameBuilder, delegateStatement.AsClause) + + Dim accessibility = GetAccessibility(delegateStatement.Parent, delegateStatement, delegateStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Delegate, accessibility), + hasItems:=False, + delegateStatement, + delegateStatement.Identifier)) + End Sub + + Protected Overrides Sub AddEnumDeclarationMembers(documentId As DocumentId, enumDeclaration As EnumBlockSyntax, items As ArrayBuilder(Of SymbolTreeItemData), cancellationToken As CancellationToken) + For Each member In enumDeclaration.Members + Dim enumMember = TryCast(member, EnumMemberDeclarationSyntax) + items.Add(New SymbolTreeItemData( + documentId, + enumMember.Identifier.ValueText, + Glyph.EnumMemberPublic, + hasItems:=False, + enumMember, + enumMember.Identifier)) + Next + End Sub + + Protected Overrides Sub AddMemberDeclaration(documentId As DocumentId, member As StatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + Dim container = member.Parent + Dim methodStatement = If(TryCast(member, MethodStatementSyntax), TryCast(member, MethodBlockSyntax)?.SubOrFunctionStatement) + If methodStatement IsNot Nothing Then + AddMethodStatement(documentId, container, methodStatement, items, nameBuilder) + Return + End If + + Dim constructorStatement = If(TryCast(member, SubNewStatementSyntax), TryCast(member, ConstructorBlockSyntax)?.SubNewStatement) + If constructorStatement IsNot Nothing Then + AddConstructorStatement(documentId, container, constructorStatement, items, nameBuilder) + Return + End If + + Dim operatorStatement = If(TryCast(member, OperatorStatementSyntax), TryCast(member, OperatorBlockSyntax)?.OperatorStatement) + If operatorStatement IsNot Nothing Then + AddOperatorStatement(documentId, container, operatorStatement, items, nameBuilder) + Return + End If + + Dim propertystatement = If(TryCast(member, PropertyStatementSyntax), TryCast(member, PropertyBlockSyntax)?.PropertyStatement) + If propertystatement IsNot Nothing Then + AddPropertyStatement(documentId, container, propertystatement, items, nameBuilder) + Return + End If + + Dim eventStatement = If(TryCast(member, EventStatementSyntax), TryCast(member, EventBlockSyntax)?.EventStatement) + If eventStatement IsNot Nothing Then + AddEventStatement(documentId, container, eventStatement, items, nameBuilder) + Return + End If + + Dim fieldDeclaration = TryCast(member, FieldDeclarationSyntax) + If fieldDeclaration IsNot Nothing Then + AddFieldDeclaration(documentId, container, fieldDeclaration, items, nameBuilder) + Return + End If + End Sub + + Private Shared Sub AddMethodStatement(documentId As DocumentId, container As SyntaxNode, methodStatement As MethodStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append(methodStatement.Identifier.ValueText) + AppendTypeParameterList(nameBuilder, methodStatement.TypeParameterList) + AppendParameterList(nameBuilder, methodStatement.ParameterList) + AppendAsClause(nameBuilder, methodStatement.AsClause) + + Dim isExtension = container.IsKind(SyntaxKind.ModuleBlock) AndAlso methodStatement.AttributeLists.Any( + Function(list) list.Attributes.Any( + Function(attribute) + Dim name = attribute.Name.GetRightmostName().Identifier.ValueText + Return name = "Extension" OrElse name = "ExtensionAttribute" + End Function)) + + Dim accesibility = GetAccessibility(container, methodStatement, methodStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(If(isExtension, DeclaredSymbolInfoKind.ExtensionMethod, DeclaredSymbolInfoKind.Method), accesibility), + hasItems:=False, + methodStatement, + methodStatement.Identifier)) + End Sub + + Private Shared Sub AddConstructorStatement(documentId As DocumentId, container As SyntaxNode, constructorStatement As SubNewStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append("New") + AppendParameterList(nameBuilder, constructorStatement.ParameterList) + + Dim accesibility = GetAccessibility(container, constructorStatement, constructorStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Constructor, accesibility), + hasItems:=False, + constructorStatement, + constructorStatement.NewKeyword)) + End Sub + + Private Shared Sub AddOperatorStatement(documentId As DocumentId, container As SyntaxNode, operatorStatement As OperatorStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append("Operator ") + nameBuilder.Append(operatorStatement.OperatorToken.ToString()) + AppendParameterList(nameBuilder, operatorStatement.ParameterList) + AppendAsClause(nameBuilder, operatorStatement.AsClause, fallbackToObject:=True) + + Dim accesibility = GetAccessibility(container, operatorStatement, operatorStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Operator, accesibility), + hasItems:=False, + operatorStatement, + operatorStatement.OperatorToken)) + End Sub + + Private Shared Sub AddPropertyStatement(documentId As DocumentId, container As SyntaxNode, propertystatement As PropertyStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append(propertystatement.Identifier.ValueText) + AppendParameterList(nameBuilder, propertystatement.ParameterList) + AppendAsClause(nameBuilder, propertystatement.AsClause, fallbackToObject:=True) + + Dim accesibility = GetAccessibility(container, propertystatement, propertystatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Property, accesibility), + hasItems:=False, + propertystatement, + propertystatement.Identifier)) + End Sub + + Private Shared Sub AddEventStatement(documentId As DocumentId, container As SyntaxNode, eventStatement As EventStatementSyntax, items As ArrayBuilder(Of SymbolTreeItemData), nameBuilder As StringBuilder) + nameBuilder.Append(eventStatement.Identifier.ValueText) + AppendAsClause(nameBuilder, eventStatement.AsClause) + + Dim accesibility = GetAccessibility(container, eventStatement, eventStatement.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Event, accesibility), + hasItems:=False, + eventStatement, + eventStatement.Identifier)) + End Sub + + Private Shared Sub AddFieldDeclaration( + documentId As DocumentId, + container As SyntaxNode, + fieldDeclaration As FieldDeclarationSyntax, + items As ArrayBuilder(Of SymbolTreeItemData), + nameBuilder As StringBuilder) + For Each declarator In fieldDeclaration.Declarators + For Each name In declarator.Names + nameBuilder.Append(name.Identifier.ValueText) + AppendAsClause(nameBuilder, declarator.AsClause, fallbackToObject:=True) + + Dim accesibility = GetAccessibility(container, fieldDeclaration, fieldDeclaration.Modifiers) + items.Add(New SymbolTreeItemData( + documentId, + nameBuilder.ToStringAndClear(), + GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Field, accesibility), + hasItems:=False, + fieldDeclaration, + name.Identifier)) + Next + Next + End Sub + + Private Shared Sub AppendTypeParameterList( + builder As StringBuilder, + typeParameterList As TypeParameterListSyntax) + + AppendCommaSeparatedList( + builder, "(Of ", ")", typeParameterList, + Function(list) list.Parameters, + Sub(parameter, innherBuilder) innherBuilder.Append(parameter.Identifier.ValueText)) + End Sub + + Private Shared Sub AppendParameterList( + builder As StringBuilder, + parameterList As ParameterListSyntax) + + AppendCommaSeparatedList( + builder, "(", ")", parameterList, + Function(list) list.Parameters, + Sub(parameter, innerBuilder) AppendType(parameter?.AsClause?.Type, builder)) + End Sub + + Private Shared Sub AppendAsClause( + nameBuilder As StringBuilder, + asClause As AsClauseSyntax, + Optional fallbackToObject As Boolean = False) + If asClause IsNot Nothing Then + + Dim simpleAsClause = TryCast(asClause, SimpleAsClauseSyntax) + If simpleAsClause IsNot Nothing Then + nameBuilder.Append(" As ") + AppendType(simpleAsClause.Type, nameBuilder) + Return + End If + + Dim asNewClause = TryCast(asClause, AsNewClauseSyntax) + If asNewClause IsNot Nothing Then + Dim newObjectCreation = TryCast(asNewClause.NewExpression, ObjectCreationExpressionSyntax) + If newObjectCreation IsNot Nothing Then + nameBuilder.Append(" As ") + AppendType(newObjectCreation.Type, nameBuilder) + Return + End If + + Dim newArrayCreation = TryCast(asNewClause.NewExpression, ArrayCreationExpressionSyntax) + If newArrayCreation IsNot Nothing Then + nameBuilder.Append(" As ") + AppendType(newArrayCreation.Type, nameBuilder) + nameBuilder.Append("()") + Return + End If + End If + ElseIf fallbackToObject Then + nameBuilder.Append(" As Object") + End If + End Sub + + Private Shared Sub AppendType(typeSyntax As TypeSyntax, builder As StringBuilder) + If typeSyntax Is Nothing Then + Return + End If + + Dim tupleType = TryCast(typeSyntax, TupleTypeSyntax) + If tupleType IsNot Nothing Then + AppendCommaSeparatedList( + builder, "(", ")", tupleType.Elements, + Sub(element, innerBuilder) AppendTupleElement(element, innerBuilder)) + Return + End If + + Dim arrayType = TryCast(typeSyntax, ArrayTypeSyntax) + If arrayType IsNot Nothing Then + AppendType(arrayType.ElementType, builder) + For Each rankSpecifier In arrayType.RankSpecifiers + builder.Append("("c) + builder.Append(","c, rankSpecifier.CommaTokens.Count) + builder.Append(")"c) + Next + + Return + End If + + Dim nullableType = TryCast(typeSyntax, NullableTypeSyntax) + If nullableType IsNot Nothing Then + AppendType(nullableType.ElementType, builder) + builder.Append("?"c) + Return + End If + + Dim predefinedType = TryCast(typeSyntax, PredefinedTypeSyntax) + If predefinedType IsNot Nothing Then + builder.Append(predefinedType.ToString()) + Return + End If + + Dim identifierName = TryCast(typeSyntax, IdentifierNameSyntax) + If identifierName IsNot Nothing Then + builder.Append(identifierName.Identifier.ValueText) + Return + End If + + Dim genericName = TryCast(typeSyntax, GenericNameSyntax) + If genericName IsNot Nothing Then + builder.Append(genericName.Identifier.ValueText) + AppendCommaSeparatedList( + builder, "(Of ", ")", genericName.TypeArgumentList.Arguments, + Sub(typeArgument, innerBuilder) AppendType(typeArgument, innerBuilder)) + Return + End If + + Dim qualifiedName = TryCast(typeSyntax, QualifiedNameSyntax) + If qualifiedName IsNot Nothing Then + AppendType(qualifiedName.Right, builder) + Return + End If + + Debug.Fail("Unhandled type: " + typeSyntax.GetType().FullName) + End Sub + + Private Shared Sub AppendTupleElement(element As TupleElementSyntax, builder As StringBuilder) + Dim typedTupleElement = TryCast(element, TypedTupleElementSyntax) + If typedTupleElement IsNot Nothing Then + AppendType(typedTupleElement.Type, builder) + Return + End If + + Dim namedTupleElement = TryCast(element, NamedTupleElementSyntax) + If namedTupleElement IsNot Nothing Then + builder.Append(namedTupleElement.Identifier.ValueText) + AppendAsClause(builder, namedTupleElement.AsClause) + Return + End If + + Debug.Fail("Unhandled type: " + element.GetType().FullName) + + End Sub + End Class +End Namespace diff --git a/src/Features/VisualBasicTest/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProviderTests.vb b/src/Features/VisualBasicTest/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProviderTests.vb new file mode 100644 index 0000000000000..53da264c24f12 --- /dev/null +++ b/src/Features/VisualBasicTest/SolutionExplorer/VisualBasicSolutionExplorerSymbolTreeItemProviderTests.vb @@ -0,0 +1,253 @@ +' 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. + +Imports Microsoft.CodeAnalysis.Test.Utilities.SolutionExplorer +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SolutionExplorer + + Public NotInheritable Class VisualBasicSolutionExplorerSymbolTreeItemProviderTests + Inherits AbstractSolutionExplorerSymbolTreeItemProviderTests + + Protected Overrides Function CreateWorkspace(code As String) As TestWorkspace + Return TestWorkspace.CreateVisualBasic(code) + End Function + + Private Function TestCompilationUnit( + code As String, expected As String) As Task + + Return TestNode(Of CompilationUnitSyntax)(code, expected) + End Function + + + Public Async Function TestEmptyFile() As Task + Await TestCompilationUnit("", "") + End Function + + + Public Async Function TestTopLevelClass() As Task + Await TestCompilationUnit(" + class [|C|] + end class + ", " + Name=""C"" Glyph=ClassInternal HasItems=False + ") + End Function + + + Public Async Function TestTwoTopLevelTypes() As Task + Await TestCompilationUnit(" + class [|C|] + end class + + class [|D|] + end class + ", " + Name=""C"" Glyph=ClassInternal HasItems=False + Name=""D"" Glyph=ClassInternal HasItems=False + ") + End Function + + + Public Async Function TestDelegatesAndEnums() As Task + Await TestCompilationUnit(" + delegate function [|D|](x as Integer) as string + + enum [|E|] + end enum + ", " + Name=""D(Integer) As string"" Glyph=DelegateInternal HasItems=False + Name=""E"" Glyph=EnumInternal HasItems=False + ") + End Function + + + Public Async Function TestTypesInBlockNamespace() As Task + Await TestCompilationUnit(" + namespace N + class [|C|] + end class + + class [|D|] + end class + end namespace + ", " + Name=""C"" Glyph=ClassInternal HasItems=False + Name=""D"" Glyph=ClassInternal HasItems=False + ") + End Function + + + Public Async Function TestTypesAcrossNamespaces() As Task + Await TestCompilationUnit(" + class [|C|] + end class + + namespace N + class [|D|] + end class + end namespace + ", " + Name=""C"" Glyph=ClassInternal HasItems=False + Name=""D"" Glyph=ClassInternal HasItems=False + ") + End Function + + + Public Async Function TestTypePermutations( + + accessibility As String, + + type As String) As Task + Await TestCompilationUnit($" + {accessibility.ToLowerInvariant()} {type.ToLowerInvariant()} [|C|] + end {type.ToLowerInvariant()} + ", $" + Name=""C"" Glyph={type}{If(accessibility = "Friend", "Internal", accessibility)} HasItems=False + ") + End Function + + + Public Async Function TestTypeHasItems( + + type As String) As Task + Await TestCompilationUnit($" + {type.ToLowerInvariant()} [|C|] + readonly property P as string + end {type.ToLowerInvariant()} + ", $" + Name=""C"" Glyph={type}Internal HasItems=True + ") + End Function + + + Public Async Function TestEnumHasItems() As Task + Await TestCompilationUnit(" + enum [|E|] + A + B + C + end enum + ", " + Name=""E"" Glyph=EnumInternal HasItems=True + ") + End Function + + + + + + + + + + + + + Public Async Function TestTypes(parameterType As String, resultType As String) As Task + Await TestCompilationUnit($" + delegate sub [|D|](x as {parameterType}) + ", $" + Name=""D({resultType})"" Glyph=DelegateInternal HasItems=False + ") + End Function + + + Public Async Function TestGenericClass() As Task + Await TestCompilationUnit(" + class [|C|](of T) + end class + ", " + Name=""C(Of T)"" Glyph=ClassInternal HasItems=False + ") + End Function + + + Public Async Function TestGenericDelegate() As Task + Await TestCompilationUnit(" + delegate sub [|D|](Of T)() + ", " + Name=""D(Of T)()"" Glyph=DelegateInternal HasItems=False + ") + End Function + + + Public Async Function TestEnumMembers() As Task + Await TestNode(Of EnumBlockSyntax)(" + enum E + [|A|] + [|B|] + [|C|] + end enum + ", " + Name=""A"" Glyph=EnumMemberPublic HasItems=False + Name=""B"" Glyph=EnumMemberPublic HasItems=False + Name=""C"" Glyph=EnumMemberPublic HasItems=False + ") + End Function + + + Public Async Function TestClassMembers() As Task + Await TestNode(Of ClassBlockSyntax)(" + class C + private [|a|], [|b|] as Integer + public readonly property [|Prop|] as P + sub [|New|]() + end sub + + protected readonly property [|Item|](s as string) as R + get + end get + end property + + private custom event [|A|] as Action + end event + + sub [|M|](Of T)(a as Integer) + end sub + + public shared operator [|+|](c1 as C, a as Integer) + end operator + + friend shared widening operator [|CType|](c1 as C) as Integer + end operator + end class + ", " + Name=""a As Integer"" Glyph=FieldPrivate HasItems=False + Name=""b As Integer"" Glyph=FieldPrivate HasItems=False + Name=""Prop As P"" Glyph=PropertyPublic HasItems=False + Name=""New()"" Glyph=MethodPublic HasItems=False + Name=""Item(string) As R"" Glyph=PropertyProtected HasItems=False + Name=""A As Action"" Glyph=EventPrivate HasItems=False + Name=""M(Of T)(Integer)"" Glyph=MethodPublic HasItems=False + Name=""Operator +(C, Integer) As Object"" Glyph=OperatorPublic HasItems=False + Name=""Operator CType(C) As Integer"" Glyph=OperatorInternal HasItems=False + ") + End Function + + + Public Async Function TestExtension1() As Task + Await TestNode(Of ModuleBlockSyntax)(" + module C + + public sub [|M|](i as Integer) + end sub + end module + ", " + Name=""M(Integer)"" Glyph=ExtensionMethodPublic HasItems=False + ") + End Function + + + Public Async Function TestAsClauses() As Task + Await TestNode(Of ClassBlockSyntax)(" + class C + dim [|x|] as new Y() + end class + ", " + Name=""x As Y"" Glyph=FieldPrivate HasItems=False + ") + End Function + End Class +End Namespace diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs index 981095c2d0817..1d284207fb741 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs @@ -135,8 +135,10 @@ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool var content = await _projectXmlProvider.GetVirtualProjectContentAsync(documentPath, cancellationToken); if (content is not var (virtualProjectContent, diagnostics)) { - // 'GetVirtualProjectContentAsync' will log errors when it fails - return null; + // https://github.com/dotnet/roslyn/issues/78618: falling back to this until dotnet run-api is more widely available + _logger.LogInformation($"Failed to obtain virtual project for '{documentPath}' using dotnet run-api. Falling back to directly creating the virtual project."); + virtualProjectContent = VirtualProjectXmlProvider.MakeVirtualProjectContent_DirectFallback(documentPath); + diagnostics = []; } foreach (var diagnostic in diagnostics) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs index 96e9a08c32fb0..4bf41b8251798 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs @@ -106,4 +106,106 @@ internal static bool IsFileBasedProgram(string documentFilePath, SourceText text var isFileBasedProgram = root.GetLeadingTrivia().Any(SyntaxKind.IgnoredDirectiveTrivia) || root.ChildNodes().Any(node => node.IsKind(SyntaxKind.GlobalStatement)); return isFileBasedProgram; } + + #region Temporary copy of subset of dotnet run-api behavior for fallback: https://github.com/dotnet/roslyn/issues/78618 + // See https://github.com/dotnet/sdk/blob/b5dbc69cc28676ac6ea615654c8016a11b75e747/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs#L10 + private static class Sha256Hasher + { + public static string Hash(string text) + { + byte[] bytes = Encoding.UTF8.GetBytes(text); + byte[] hash = SHA256.HashData(bytes); +#if NET9_0_OR_GREATER + return Convert.ToHexStringLower(hash); +#else + return Convert.ToHexString(hash).ToLowerInvariant(); +#endif + } + + public static string HashWithNormalizedCasing(string text) + { + return Hash(text.ToUpperInvariant()); + } + } + + // See https://github.com/dotnet/sdk/blob/5a4292947487a9d34f4256c1d17fb3dc26859174/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs#L449 + internal static string GetArtifactsPath(string entryPointFileFullPath) + { + // We want a location where permissions are expected to be restricted to the current user. + string directory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.GetTempPath() + : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + // Include entry point file name so the directory name is not completely opaque. + string fileName = Path.GetFileNameWithoutExtension(entryPointFileFullPath); + string hash = Sha256Hasher.HashWithNormalizedCasing(entryPointFileFullPath); + string directoryName = $"{fileName}-{hash}"; + + return Path.Join(directory, "dotnet", "runfile", directoryName); + } + #endregion + + // https://github.com/dotnet/roslyn/issues/78618: falling back to this until dotnet run-api is more widely available + internal static string MakeVirtualProjectContent_DirectFallback(string documentFilePath) + { + Contract.ThrowIfFalse(PathUtilities.IsAbsolute(documentFilePath)); + var artifactsPath = GetArtifactsPath(documentFilePath); + + var targetFramework = Environment.GetEnvironmentVariable("DOTNET_RUN_FILE_TFM") ?? "net10.0"; + + var virtualProjectXml = $""" + + + false + {SecurityElement.Escape(artifactsPath)} + + + + + Exe + {SecurityElement.Escape(targetFramework)} + enable + enable + + + false + + + preview + + + $(Features);FileBasedProgram + + + + + + + + + + + + + + <_RestoreProjectPathItems Include="@(FilteredRestoreGraphProjectInputItems)" /> + + + + + + + """; + + return virtualProjectXml; + } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index d3d18c4abd815..0b1f6e306d66b 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -57,7 +57,7 @@ public async Task OpenSolutionAsync(string solutionFilePath) _logger.LogInformation(string.Format(LanguageServerResources.Loading_0, solutionFilePath)); ProjectFactory.SolutionPath = solutionFilePath; - var (_, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, CancellationToken.None); + var (_, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, DiagnosticReportingMode.Throw, CancellationToken.None); foreach (var (path, guid) in projects) { await BeginLoadingProjectAsync(path, guid); diff --git a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 1cfabad69bc18..debd210156970 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -253,7 +253,7 @@ public override async ValueTask OnReferencesFoundAsync(IAsyncEnumerable (uri1, uri2) switch + { + (null, null) => true, + (null, _) or (_, null) => false, + _ => uri1.Equals(uri2) + }; + + public static bool operator !=(DocumentUri? uri1, DocumentUri? uri2) + => !(uri1 == uri2); } diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs index 188f872aa0619..bcfbfa9906a68 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs @@ -9,6 +9,7 @@ using Roslyn.Text.Adornments; namespace Roslyn.LanguageServer.Protocol; + internal sealed class ImageElementConverter : JsonConverter { public static readonly ImageElementConverter Instance = new(); @@ -20,6 +21,8 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, ImageId? imageId = null; string? automationName = null; + Span scratchChars = stackalloc char[64]; + while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) @@ -30,7 +33,8 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, if (reader.TokenType == JsonTokenType.PropertyName) { - var propertyName = reader.GetString(); + var propertyName = reader.GetStringSpan(scratchChars); + reader.Read(); switch (propertyName) { @@ -41,7 +45,9 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, automationName = reader.GetString(); break; case ObjectContentConverter.TypeProperty: - if (reader.GetString() != nameof(ImageElement)) + var typePropertyValue = reader.GetStringSpan(scratchChars); + + if (!typePropertyValue.SequenceEqual(nameof(ImageElement).AsSpan())) throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageElement)}"); break; default: @@ -60,7 +66,10 @@ public override void Write(Utf8JsonWriter writer, ImageElement value, JsonSerial writer.WriteStartObject(); writer.WritePropertyName(nameof(ImageElement.ImageId)); ImageIdConverter.Instance.Write(writer, value.ImageId, options); - writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName); + + if (value.AutomationName != null) + writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName); + writer.WriteString(ObjectContentConverter.TypeProperty, nameof(ImageElement)); writer.WriteEndObject(); } diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs index b2d917c99b228..b4a0ec346a6c8 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs @@ -8,6 +8,7 @@ using Roslyn.Core.Imaging; namespace Roslyn.LanguageServer.Protocol; + internal sealed class ImageIdConverter : JsonConverter { public static readonly ImageIdConverter Instance = new(); @@ -16,21 +17,49 @@ public override ImageId Read(ref Utf8JsonReader reader, Type objectType, JsonSer { if (reader.TokenType == JsonTokenType.StartObject) { - using var document = JsonDocument.ParseValue(ref reader); - var root = document.RootElement; - if (root.TryGetProperty(ObjectContentConverter.TypeProperty, out var typeProperty) && typeProperty.GetString() != nameof(ImageId)) + Guid? guid = null; + int? id = null; + + Span scratchChars = stackalloc char[64]; + + while (reader.Read()) { - throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}"); - } + if (reader.TokenType == JsonTokenType.EndObject) + { + if (guid is null || id is null) + throw new JsonException("Expected properties Guid and Id to be present"); - var guid = root.GetProperty(nameof(ImageId.Guid)).GetString() ?? throw new JsonException(); - var id = root.GetProperty(nameof(ImageId.Id)).GetInt32(); - return new ImageId(new Guid(guid), id); - } - else - { - throw new JsonException("Expected start object or null tokens"); + return new ImageId(guid.Value, id.Value); + } + + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetStringSpan(scratchChars); + + reader.Read(); + switch (propertyName) + { + case nameof(ImageId.Guid): + guid = reader.GetGuid(); + break; + case nameof(ImageId.Id): + id = reader.GetInt32(); + break; + case ObjectContentConverter.TypeProperty: + var typePropertyValue = reader.GetStringSpan(scratchChars); + + if (!typePropertyValue.SequenceEqual(nameof(ImageId).AsSpan())) + throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}"); + break; + default: + reader.Skip(); + break; + } + } + } } + + throw new JsonException("Expected start object or null tokens"); } public override void Write(Utf8JsonWriter writer, ImageId value, JsonSerializerOptions options) diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Converters/VSInternalExtensionUtilities.cs b/src/LanguageServer/Protocol/Protocol/Internal/Converters/VSInternalExtensionUtilities.cs index 32ef117def30d..53cb0ffb79119 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Converters/VSInternalExtensionUtilities.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Converters/VSInternalExtensionUtilities.cs @@ -2,6 +2,7 @@ // 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.Text.Json; using System.Text.Json.Serialization; @@ -71,4 +72,26 @@ void AddOrReplaceConverter() converters.Add(new VSExtensionConverter()); } } + + /// + /// Returns a string read from the . If the string is small enough + /// to fit into the provided , no allocations will be needed. + /// + internal static ReadOnlySpan GetStringSpan(this ref readonly Utf8JsonReader reader, Span scratchChars) + { + var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length; + + if (valueLength <= scratchChars.Length) + { + // If the value fits into the scratch buffer, copy it there, and return a span from that. + var actualLength = reader.CopyString(scratchChars); + + return scratchChars.Slice(0, actualLength); + } + else + { + // Otherwise, ask the reader to allocate a string and return a span from that. + return reader.GetString().AsSpan(); + } + } } diff --git a/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs b/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs index 1ffac96a03262..7bda18323bc8c 100644 --- a/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs @@ -164,12 +164,12 @@ public async Task TestGotoDefinitionPartialMethods(bool mutatingLspWorkspace) public partial class C { - partial void {|caret:|}P(); + partial void {|caret:|}{|definition:P|}(); } public partial class C { - partial void {|definition:P|}() + partial void P() { Console.WriteLine("); } @@ -191,12 +191,12 @@ public async Task TestGotoDefinitionPartialProperties(bool mutatingLspWorkspace) public partial class C { - partial int {|caret:|}Prop { get; set; } + partial int {|caret:|}{|definition:Prop|} { get; set; } } public partial class C { - partial int {|definition:Prop|} { get => 1; set { } } + partial int Prop { get => 1; set { } } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); @@ -213,12 +213,12 @@ public async Task TestGotoDefinitionPartialEvents(bool mutatingLspWorkspace) public partial class C { - partial event Action {|caret:|}E; + partial event Action {|caret:|}{|definition:E|}; } public partial class C { - partial event Action {|definition:E|} { add { } remove { } } + partial event Action E { add { } remove { } } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); @@ -235,12 +235,12 @@ public async Task TestGotoDefinitionPartialConstructors(bool mutatingLspWorkspac public partial class C { - partial {|caret:|}C(); + partial {|caret:|}{|definition:C|}(); } public partial class C { - partial {|definition:C|}() { } + partial C() { } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions diff --git a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs index 4c1fc1b13264f..46891215ce8f5 100644 --- a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -300,6 +300,30 @@ class PREPROCESSING_SYMBOL AssertHighlightCount(results, expectedDefinitionCount: 0, expectedWrittenReferenceCount: 0, expectedReferenceCount: 3); } + [Theory, CombinatorialData] + public async Task TestFindReferencesAsync_UsingAlias(bool mutatingLspWorkspace) + { + var markup = + """ + using {|caret:MyType|} = System.{|reference:String|}; + + class SomeClassToExtract + { + void M() + { + {|reference:MyType|} p; + } + } + """; + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, CapabilitiesWithVSExtensions); + + var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); + Assert.Equal(3, results.Length); + Assert.True(results[0].Location.DocumentUri.ToString().EndsWith("String.cs")); + + AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Skip(1).Select(r => r.Location)); + } + private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret, IProgress progress) => new LSP.ReferenceParams() { diff --git a/src/LanguageServer/ProtocolUnitTests/UriTests.cs b/src/LanguageServer/ProtocolUnitTests/UriTests.cs index f9e5e3b0cf72d..b188ccf5bdd86 100644 --- a/src/LanguageServer/ProtocolUnitTests/UriTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/UriTests.cs @@ -352,6 +352,23 @@ public async Task TestOpenDocumentWithInvalidUri(bool mutatingLspWorkspace, stri Assert.Equal("hello", (await document!.GetTextAsync()).ToString()); } + [Theory] + [InlineData(true, null, null)] + [InlineData(false, "file://c:\\valid", null)] + [InlineData(false, null, "file://c:\\valid")] + [InlineData(true, "file://c:\\valid", "file://c:\\valid")] + [InlineData(true, "file://c:\\valid", "file:///c:/valid")] + [InlineData(true, "file://c:\\valid", "file://c:\\VALID")] + [InlineData(false, "file://c:\\valid", "file://c:\\valid2")] + public void TestUriEquality(bool areEqual, string? uriString1, string? uriString2) + { + var documentUri1 = uriString1 != null ? new DocumentUri(uriString1) : null; + var documentUri2 = uriString2 != null ? new DocumentUri(uriString2) : null; + + Assert.True(areEqual == (documentUri1 == documentUri2)); + Assert.True(areEqual != (documentUri1 != documentUri2)); + } + private sealed record class ResolvedDocumentInfo(string WorkspaceKind, string ProjectLanguage); private sealed record class CustomResolveParams([property: JsonPropertyName("textDocument")] LSP.TextDocumentIdentifier TextDocument); diff --git a/src/Tools/ExternalAccess/Razor/Features/Constants.cs b/src/Tools/ExternalAccess/Razor/Features/Constants.cs index 0a81f1b75b8e6..21809c4b7cdb7 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Constants.cs @@ -3,11 +3,13 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.LanguageServer; -using Roslyn.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; internal static class Constants { public const string RazorLanguageName = LanguageInfoProvider.RazorLanguageName; + + public const string CompleteComplexEditCommand = CompletionResultFactory.CompleteComplexEditCommand; } diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorCSharpFormattingInteractionService.cs b/src/Tools/ExternalAccess/Razor/Features/RazorCSharpFormattingInteractionService.cs index b593b48591143..a95cc3f5f7f86 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorCSharpFormattingInteractionService.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorCSharpFormattingInteractionService.cs @@ -8,13 +8,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { @@ -23,6 +23,24 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor /// internal static class RazorCSharpFormattingInteractionService { + [Obsolete("This overload is for binary compat only. Use GetFormattingChangesAsync with all parameters instead.")] + public static Task> GetFormattingChangesAsync( + Document document, + char typedChar, + int position, + RazorIndentationOptions indentationOptions, + RazorAutoFormattingOptions autoFormattingOptions, + FormattingOptions.IndentStyle indentStyle, + CancellationToken cancellationToken) + => GetFormattingChangesAsync( + document, + typedChar, + position, + indentationOptions, + autoFormattingOptions, + indentStyle, + csharpSyntaxFormattingOptionsOverride: null, + cancellationToken); /// /// Returns the text changes necessary to format the document after the user enters a @@ -36,6 +54,7 @@ public static async Task> GetFormattingChangesAsync( RazorIndentationOptions indentationOptions, RazorAutoFormattingOptions autoFormattingOptions, FormattingOptions.IndentStyle indentStyle, + RazorCSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptionsOverride, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document.Project.Language is LanguageNames.CSharp); @@ -44,10 +63,10 @@ public static async Task> GetFormattingChangesAsync( if (!formattingService.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) { - return ImmutableArray.Empty; + return []; } - var formattingOptions = GetFormattingOptions(document.Project.Solution.Services, indentationOptions); + var formattingOptions = GetFormattingOptions(document.Project.Solution.Services, indentationOptions, csharpSyntaxFormattingOptionsOverride); var roslynIndentationOptions = new IndentationOptions(formattingOptions) { AutoFormattingOptions = autoFormattingOptions.UnderlyingObject, @@ -57,33 +76,63 @@ public static async Task> GetFormattingChangesAsync( return formattingService.GetFormattingChangesOnTypedCharacter(documentSyntax, position, roslynIndentationOptions, cancellationToken); } + [Obsolete("This overload is for binary compat only. Use GetFormattingChangesAsync with all parameters instead.")] + public static IList GetFormattedTextChanges( + HostWorkspaceServices services, + SyntaxNode root, + TextSpan span, + RazorIndentationOptions indentationOptions, + CancellationToken cancellationToken) + => GetFormattedTextChanges( + services, + root, + span, + indentationOptions, + csharpSyntaxFormattingOptionsOverride: null, + cancellationToken); + public static IList GetFormattedTextChanges( HostWorkspaceServices services, SyntaxNode root, TextSpan span, RazorIndentationOptions indentationOptions, + RazorCSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptionsOverride, CancellationToken cancellationToken) { Contract.ThrowIfFalse(root.Language is LanguageNames.CSharp); - return Formatter.GetFormattedTextChanges(root, span, services.SolutionServices, GetFormattingOptions(services.SolutionServices, indentationOptions), cancellationToken); + return Formatter.GetFormattedTextChanges(root, span, services.SolutionServices, GetFormattingOptions(services.SolutionServices, indentationOptions, csharpSyntaxFormattingOptionsOverride), cancellationToken); } + [Obsolete("This overload is for binary compat only. Use GetFormattingChangesAsync with all parameters instead.")] + public static SyntaxNode Format( + HostWorkspaceServices services, + SyntaxNode root, + RazorIndentationOptions indentationOptions, + CancellationToken cancellationToken) + => Format( + services, + root, + indentationOptions, + csharpSyntaxFormattingOptionsOverride: null, + cancellationToken); + public static SyntaxNode Format( HostWorkspaceServices services, SyntaxNode root, RazorIndentationOptions indentationOptions, + RazorCSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptionsOverride, CancellationToken cancellationToken) { Contract.ThrowIfFalse(root.Language is LanguageNames.CSharp); - return Formatter.Format(root, services.SolutionServices, GetFormattingOptions(services.SolutionServices, indentationOptions), cancellationToken: cancellationToken); + return Formatter.Format(root, services.SolutionServices, GetFormattingOptions(services.SolutionServices, indentationOptions, csharpSyntaxFormattingOptionsOverride), cancellationToken: cancellationToken); } - private static SyntaxFormattingOptions GetFormattingOptions(SolutionServices services, RazorIndentationOptions indentationOptions) + private static SyntaxFormattingOptions GetFormattingOptions(SolutionServices services, RazorIndentationOptions indentationOptions, RazorCSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptionsOverride) { var legacyOptionsService = services.GetService(); - var formattingOptions = legacyOptionsService is null - ? new CSharpSyntaxFormattingOptions() - : legacyOptionsService.GetSyntaxFormattingOptions(services.GetLanguageServices(LanguageNames.CSharp)); + var formattingOptions = csharpSyntaxFormattingOptionsOverride?.ToCSharpSyntaxFormattingOptions() + ?? legacyOptionsService?.GetSyntaxFormattingOptions(services.GetLanguageServices(LanguageNames.CSharp)) + ?? CSharpSyntaxFormattingOptions.Default; return formattingOptions with { diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorCSharpSyntaxFormattingOptions.cs b/src/Tools/ExternalAccess/Razor/Features/RazorCSharpSyntaxFormattingOptions.cs new file mode 100644 index 0000000000000..5b02f3765f06c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Features/RazorCSharpSyntaxFormattingOptions.cs @@ -0,0 +1,148 @@ +// 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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Formatting; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features +{ + /// + /// Wrapper for CSharpSyntaxFormattingOptions for Razor external access. + /// + internal sealed record class RazorCSharpSyntaxFormattingOptions( + RazorSpacePlacement Spacing, + RazorBinaryOperatorSpacingOptions SpacingAroundBinaryOperator, + RazorNewLinePlacement NewLines, + RazorLabelPositionOptions LabelPositioning, + RazorIndentationPlacement Indentation, + bool WrappingKeepStatementsOnSingleLine, + bool WrappingPreserveSingleLine, + RazorNamespaceDeclarationPreference NamespaceDeclarations, + bool PreferTopLevelStatements, + int CollectionExpressionWrappingLength) + { + public static readonly RazorCSharpSyntaxFormattingOptions Default = new(); + + private RazorCSharpSyntaxFormattingOptions() + : this(CSharpSyntaxFormattingOptions.Default) + { + } + + public RazorCSharpSyntaxFormattingOptions(CSharpSyntaxFormattingOptions options) + : this( + (RazorSpacePlacement)options.Spacing, + (RazorBinaryOperatorSpacingOptions)options.SpacingAroundBinaryOperator, + (RazorNewLinePlacement)options.NewLines, + (RazorLabelPositionOptions)options.LabelPositioning, + (RazorIndentationPlacement)options.Indentation, + options.WrappingKeepStatementsOnSingleLine, + options.WrappingPreserveSingleLine, + (RazorNamespaceDeclarationPreference)options.NamespaceDeclarations.Value, + options.PreferTopLevelStatements.Value, + options.CollectionExpressionWrappingLength) + { + } + + public CSharpSyntaxFormattingOptions ToCSharpSyntaxFormattingOptions() + => new() + { + Spacing = (SpacePlacement)Spacing, + SpacingAroundBinaryOperator = (BinaryOperatorSpacingOptions)SpacingAroundBinaryOperator, + NewLines = (NewLinePlacement)NewLines, + LabelPositioning = (LabelPositionOptions)LabelPositioning, + Indentation = (IndentationPlacement)Indentation, + WrappingKeepStatementsOnSingleLine = WrappingKeepStatementsOnSingleLine, + WrappingPreserveSingleLine = WrappingPreserveSingleLine, + NamespaceDeclarations = new CodeStyleOption2( + (NamespaceDeclarationPreference)NamespaceDeclarations, + CSharpSyntaxFormattingOptions.Default.NamespaceDeclarations.Notification), + PreferTopLevelStatements = new CodeStyleOption2( + PreferTopLevelStatements, + CSharpSyntaxFormattingOptions.Default.PreferTopLevelStatements.Notification), + CollectionExpressionWrappingLength = CollectionExpressionWrappingLength + }; + } + + [Flags] + public enum RazorSpacePlacement + { + None = 0, + IgnoreAroundVariableDeclaration = SpacePlacement.IgnoreAroundVariableDeclaration, + AfterMethodDeclarationName = SpacePlacement.AfterMethodDeclarationName, + BetweenEmptyMethodDeclarationParentheses = SpacePlacement.BetweenEmptyMethodDeclarationParentheses, + WithinMethodDeclarationParenthesis = SpacePlacement.WithinMethodDeclarationParenthesis, + AfterMethodCallName = SpacePlacement.AfterMethodCallName, + BetweenEmptyMethodCallParentheses = SpacePlacement.BetweenEmptyMethodCallParentheses, + WithinMethodCallParentheses = SpacePlacement.WithinMethodCallParentheses, + AfterControlFlowStatementKeyword = SpacePlacement.AfterControlFlowStatementKeyword, + WithinExpressionParentheses = SpacePlacement.WithinExpressionParentheses, + WithinCastParentheses = SpacePlacement.WithinCastParentheses, + BeforeSemicolonsInForStatement = SpacePlacement.BeforeSemicolonsInForStatement, + AfterSemicolonsInForStatement = SpacePlacement.AfterSemicolonsInForStatement, + WithinOtherParentheses = SpacePlacement.WithinOtherParentheses, + AfterCast = SpacePlacement.AfterCast, + BeforeOpenSquareBracket = SpacePlacement.BeforeOpenSquareBracket, + BetweenEmptySquareBrackets = SpacePlacement.BetweenEmptySquareBrackets, + WithinSquareBrackets = SpacePlacement.WithinSquareBrackets, + AfterColonInBaseTypeDeclaration = SpacePlacement.AfterColonInBaseTypeDeclaration, + BeforeColonInBaseTypeDeclaration = SpacePlacement.BeforeColonInBaseTypeDeclaration, + AfterComma = SpacePlacement.AfterComma, + BeforeComma = SpacePlacement.BeforeComma, + AfterDot = SpacePlacement.AfterDot, + BeforeDot = SpacePlacement.BeforeDot, + } + + [Flags] + public enum RazorNewLinePlacement + { + None = 0, + BeforeMembersInObjectInitializers = NewLinePlacement.BeforeMembersInObjectInitializers, + BeforeMembersInAnonymousTypes = NewLinePlacement.BeforeMembersInAnonymousTypes, + BeforeElse = NewLinePlacement.BeforeElse, + BeforeCatch = NewLinePlacement.BeforeCatch, + BeforeFinally = NewLinePlacement.BeforeFinally, + BeforeOpenBraceInTypes = NewLinePlacement.BeforeOpenBraceInTypes, + BeforeOpenBraceInAnonymousTypes = NewLinePlacement.BeforeOpenBraceInAnonymousTypes, + BeforeOpenBraceInObjectCollectionArrayInitializers = NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers, + BeforeOpenBraceInProperties = NewLinePlacement.BeforeOpenBraceInProperties, + BeforeOpenBraceInMethods = NewLinePlacement.BeforeOpenBraceInMethods, + BeforeOpenBraceInAccessors = NewLinePlacement.BeforeOpenBraceInAccessors, + BeforeOpenBraceInAnonymousMethods = NewLinePlacement.BeforeOpenBraceInAnonymousMethods, + BeforeOpenBraceInLambdaExpressionBody = NewLinePlacement.BeforeOpenBraceInLambdaExpressionBody, + BeforeOpenBraceInControlBlocks = NewLinePlacement.BeforeOpenBraceInControlBlocks, + BetweenQueryExpressionClauses = NewLinePlacement.BetweenQueryExpressionClauses, + } + + [Flags] + public enum RazorIndentationPlacement + { + None = 0, + Braces = IndentationPlacement.Braces, + BlockContents = IndentationPlacement.BlockContents, + SwitchCaseContents = IndentationPlacement.SwitchCaseContents, + SwitchCaseContentsWhenBlock = IndentationPlacement.SwitchCaseContentsWhenBlock, + SwitchSection = IndentationPlacement.SwitchSection, + } + + public enum RazorBinaryOperatorSpacingOptions + { + Single = BinaryOperatorSpacingOptions.Single, + Ignore = BinaryOperatorSpacingOptions.Ignore, + Remove = BinaryOperatorSpacingOptions.Remove, + } + + public enum RazorLabelPositionOptions + { + LeftMost = LabelPositionOptions.LeftMost, + OneLess = LabelPositionOptions.OneLess, + NoIndent = LabelPositionOptions.NoIndent, + } + + public enum RazorNamespaceDeclarationPreference + { + BlockScoped = NamespaceDeclarationPreference.BlockScoped, + FileScoped = NamespaceDeclarationPreference.FileScoped, + } +} diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.CSharp.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.CSharp.txt index 53594c67d7a29..3eb8e1fb0791d 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.CSharp.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.CSharp.txt @@ -408,6 +408,7 @@ Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExpressionColon(Microsof Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExpressionElement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExpressionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionStatementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExtensionBlockDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax) +Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExtensionMemberCref(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExternAliasDirective(Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitFieldDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.FieldDeclarationSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax) @@ -704,6 +705,7 @@ Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExpressionColon(Microsoft Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExpressionElement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExpressionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionStatementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExtensionBlockDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax) +Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExtensionMemberCref(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExternAliasDirective(Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitFieldDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.FieldDeclarationSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax) @@ -954,6 +956,7 @@ Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExpressionColon(Microso Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExpressionElement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExpressionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionStatementSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExtensionBlockDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax) +Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExtensionMemberCref(Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitExternAliasDirective(Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitFieldDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.FieldDeclarationSyntax) Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax) @@ -2489,6 +2492,22 @@ Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax.get_OpenBra Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax.get_ParameterList Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax.get_SemicolonToken Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax.get_TypeParameterList +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.Accept``1(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor{``0}) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.AddParametersParameters(Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax[]) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.AddTypeArgumentListArguments(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax[]) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.CSharp.Syntax.TypeArgumentListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterListSyntax,Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.CSharp.Syntax.MemberCrefSyntax) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.WithDotToken(Microsoft.CodeAnalysis.SyntaxToken) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.WithExtensionKeyword(Microsoft.CodeAnalysis.SyntaxToken) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.WithMember(Microsoft.CodeAnalysis.CSharp.Syntax.MemberCrefSyntax) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.WithParameters(Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterListSyntax) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.WithTypeArgumentList(Microsoft.CodeAnalysis.CSharp.Syntax.TypeArgumentListSyntax) +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.get_DotToken +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.get_ExtensionKeyword +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.get_Member +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.get_Parameters +Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax.get_TypeArgumentList Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor) Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax.Accept``1(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor{``0}) @@ -5004,6 +5023,9 @@ Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExpressionStatement(Microsoft.CodeAn Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionBlockDeclaration Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionBlockDeclaration(Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax},Microsoft.CodeAnalysis.SyntaxTokenList,Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax,Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax},Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax}) Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionBlockDeclaration(Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax},Microsoft.CodeAnalysis.SyntaxTokenList,Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax,Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax},Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.SyntaxList{Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax},Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.SyntaxToken) +Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionMemberCref(Microsoft.CodeAnalysis.CSharp.Syntax.MemberCrefSyntax) +Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionMemberCref(Microsoft.CodeAnalysis.CSharp.Syntax.TypeArgumentListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.MemberCrefSyntax) +Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExtensionMemberCref(Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.CSharp.Syntax.TypeArgumentListSyntax,Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterListSyntax,Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.CSharp.Syntax.MemberCrefSyntax) Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExternAliasDirective(Microsoft.CodeAnalysis.SyntaxToken) Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExternAliasDirective(Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.SyntaxToken,Microsoft.CodeAnalysis.SyntaxToken) Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExternAliasDirective(System.String) @@ -5885,6 +5907,7 @@ Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExpressionElement Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExpressionStatement Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExtensionBlockDeclaration Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExtensionKeyword +Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExtensionMemberCref Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExternAliasDirective Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExternKeyword Microsoft.CodeAnalysis.CSharp.SyntaxKind.FalseKeyword diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt index 4b6ca5430ff24..6b1f60e975c86 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt @@ -1241,6 +1241,7 @@ Microsoft.CodeAnalysis.IMethodSymbol.get_IsConditional Microsoft.CodeAnalysis.IMethodSymbol.get_IsExtensionMethod Microsoft.CodeAnalysis.IMethodSymbol.get_IsGenericMethod Microsoft.CodeAnalysis.IMethodSymbol.get_IsInitOnly +Microsoft.CodeAnalysis.IMethodSymbol.get_IsIterator Microsoft.CodeAnalysis.IMethodSymbol.get_IsPartialDefinition Microsoft.CodeAnalysis.IMethodSymbol.get_IsReadOnly Microsoft.CodeAnalysis.IMethodSymbol.get_IsVararg diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/BoundNodeClassWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/BoundNodeClassWriter.cs index 084598463ccf0..676b548051144 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/BoundNodeClassWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/BoundNodeClassWriter.cs @@ -275,7 +275,7 @@ private void WriteKinds() private void WriteTypes() { - foreach (var node in _tree.Types.Where(n => n is not PredefinedNode)) + foreach (var node in _tree.Types.OfType()) { Blank(); WriteType(node); @@ -901,10 +901,8 @@ private void WriteAccept(string name) } } - private void WriteType(TreeType node) + private void WriteType(AbstractNode node) { - if (node is not AbstractNode) - return; WriteClassHeader(node); bool unsealed = !CanBeSealed(node); diff --git a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj index 151a0533819f1..255175874d81e 100644 --- a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj +++ b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj @@ -55,7 +55,7 @@ - + true VSPackage Designer diff --git a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs b/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs deleted file mode 100644 index 2a538e5454a8c..0000000000000 --- a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs +++ /dev/null @@ -1,128 +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 System; -using System.Collections.Generic; -using System.Composition; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -namespace Microsoft.VisualStudio.LanguageServices.CSharp.Progression; - -[ExportLanguageService(typeof(IProgressionLanguageService), LanguageNames.CSharp), Shared] -internal sealed partial class CSharpProgressionLanguageService : IProgressionLanguageService -{ - private static readonly SymbolDisplayFormat s_descriptionFormat = new( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - memberOptions: SymbolDisplayMemberOptions.IncludeParameters | - SymbolDisplayMemberOptions.IncludeContainingType, - parameterOptions: SymbolDisplayParameterOptions.IncludeType | - SymbolDisplayParameterOptions.IncludeParamsRefOut | - SymbolDisplayParameterOptions.IncludeOptionalBrackets, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - - private static readonly SymbolDisplayFormat s_labelFormat = new( - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - memberOptions: SymbolDisplayMemberOptions.IncludeParameters | - SymbolDisplayMemberOptions.IncludeExplicitInterface, - parameterOptions: SymbolDisplayParameterOptions.IncludeType | - SymbolDisplayParameterOptions.IncludeParamsRefOut | - SymbolDisplayParameterOptions.IncludeOptionalBrackets, - delegateStyle: SymbolDisplayDelegateStyle.NameAndParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpProgressionLanguageService() - { - } - - public IEnumerable GetTopLevelNodesFromDocument(SyntaxNode root, CancellationToken cancellationToken) - { - // We implement this method lazily so we are able to abort as soon as we need to. - if (!cancellationToken.IsCancellationRequested) - { - using var _ = ArrayBuilder.GetInstance(out var nodes); - nodes.Push(root); - - while (nodes.TryPop(out var node)) - { - if (!cancellationToken.IsCancellationRequested) - { - if (node.Kind() is SyntaxKind.ClassDeclaration or - SyntaxKind.RecordDeclaration or - SyntaxKind.RecordStructDeclaration or - SyntaxKind.DelegateDeclaration or - SyntaxKind.EnumDeclaration or - SyntaxKind.InterfaceDeclaration or - SyntaxKind.StructDeclaration or - SyntaxKind.VariableDeclarator or - SyntaxKind.MethodDeclaration or - SyntaxKind.PropertyDeclaration) - { - yield return node; - } - else - { - foreach (var child in node.ChildNodes()) - { - nodes.Push(child); - } - } - } - } - } - } - - public string GetDescriptionForSymbol(ISymbol symbol, bool includeContainingSymbol) - => GetSymbolText(symbol, includeContainingSymbol, s_descriptionFormat); - - public string GetLabelForSymbol(ISymbol symbol, bool includeContainingSymbol) - => GetSymbolText(symbol, includeContainingSymbol, s_labelFormat); - - private static string GetSymbolText(ISymbol symbol, bool includeContainingSymbol, SymbolDisplayFormat displayFormat) - { - var label = symbol.ToDisplayString(displayFormat); - - var typeToShow = GetType(symbol); - - if (typeToShow != null) - { - label += " : " + typeToShow.ToDisplayString(s_labelFormat); - } - - if (includeContainingSymbol && symbol.ContainingSymbol != null) - { - label += " (" + symbol.ContainingSymbol.ToDisplayString(s_labelFormat) + ")"; - } - - return label; - } - - private static ITypeSymbol GetType(ISymbol symbol) - { - switch (symbol) - { - case IEventSymbol f: return f.Type; - case IFieldSymbol f: return f.ContainingType.TypeKind == TypeKind.Enum ? null : f.Type; - case IMethodSymbol m: return IncludeReturnType(m) ? m.ReturnType : null; - case IPropertySymbol p: return p.Type; - case INamedTypeSymbol n: return n.IsDelegateType() ? n.DelegateInvokeMethod.ReturnType : null; - default: return null; - } - } - - private static bool IncludeReturnType(IMethodSymbol f) - => f.MethodKind is MethodKind.Ordinary or MethodKind.ExplicitInterfaceImplementation; -} diff --git a/src/VisualStudio/Core/Def/Commands.vsct b/src/VisualStudio/Core/Def/Commands.vsct index a0dce4077b083..252b01bc7f9ec 100644 --- a/src/VisualStudio/Core/Def/Commands.vsct +++ b/src/VisualStudio/Core/Def/Commands.vsct @@ -19,6 +19,10 @@ + + + + @@ -377,6 +381,33 @@ ResetVisualBasicInteractiveFromProject + + + + + + @@ -476,7 +507,7 @@ IconAndText Clear - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName ClearStackTraceExplorer ClearStackTraceExplorer @@ -557,7 +588,6 @@ Analyzer - @@ -622,6 +652,13 @@ + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + NotInTBList @@ -774,14 +811,10 @@ - - - - - - - - + + + + @@ -790,6 +823,12 @@ + + + + + + diff --git a/src/VisualStudio/Core/Def/Extensions/BulkObservableCollectionExtensions.cs b/src/VisualStudio/Core/Def/Extensions/BulkObservableCollectionExtensions.cs new file mode 100644 index 0000000000000..66f56c864436d --- /dev/null +++ b/src/VisualStudio/Core/Def/Extensions/BulkObservableCollectionExtensions.cs @@ -0,0 +1,24 @@ +// 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 Microsoft.VisualStudio.Language.Intellisense; + +namespace Microsoft.VisualStudio.LanguageServices.Extensions; + +internal static class BulkObservableCollectionExtensions +{ + public static BulkOperationDisposable GetBulkOperation(this BulkObservableCollection collection) + { + collection.BeginBulkOperation(); + return new BulkOperationDisposable(collection); + } + + public readonly struct BulkOperationDisposable(BulkObservableCollection collection) + : IDisposable + { + public void Dispose() + => collection.EndBulkOperation(); + } +} diff --git a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs index 39a4389e8f123..dda67b372c748 100644 --- a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs +++ b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs @@ -46,5 +46,10 @@ public static class RoslynCommands public const int DocumentOutlineSortByOrder = 0x314; public const int DocumentOutlineSortByType = 0x315; public const int DocumentOutlineToolbarGroup = 0x350; + + public const int SolutionExplorerSymbolItemContextMenu = 0x401; + public const int SolutionExplorerSymbolItemGoToBase = 0x402; + public const int SolutionExplorerSymbolItemGoToImplementation = 0x403; + public const int SolutionExplorerSymbolItemFindAllReferences = 0x404; } } diff --git a/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs b/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs index 5d94975eaf2ac..a9b702594db77 100644 --- a/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs +++ b/src/VisualStudio/Core/Def/Implementation/HierarchyItemToProjectIdMap.cs @@ -2,11 +2,14 @@ // 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.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.Shell; @@ -14,17 +17,23 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceService(typeof(IHierarchyItemToProjectIdMap), ServiceLayer.Host), Shared] -internal sealed class HierarchyItemToProjectIdMap : IHierarchyItemToProjectIdMap +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class HierarchyItemToProjectIdMap(VisualStudioWorkspaceImpl workspace) : IHierarchyItemToProjectIdMap { - private readonly VisualStudioWorkspaceImpl _workspace; + private readonly VisualStudioWorkspaceImpl _workspace = workspace; - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public HierarchyItemToProjectIdMap(VisualStudioWorkspaceImpl workspace) - => _workspace = workspace; + public bool TryGetProject(IVsHierarchyItem? hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out Project? project) + => TryGetProject(_workspace.CurrentSolution, hierarchyItem, targetFrameworkMoniker, out project); - public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out ProjectId? projectId) + private bool TryGetProject( + Solution solution, IVsHierarchyItem? hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out Project? project) { + project = null; + + if (hierarchyItem is null) + return false; + // A project node is represented in two different hierarchies: the solution's IVsHierarchy (where it is a leaf node) // and the project's own IVsHierarchy (where it is the root node). The IVsHierarchyItem joins them together for the // purpose of creating the tree displayed in Solution Explorer. The project's hierarchy is what is passed from the @@ -32,44 +41,45 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string? targetFramew // the "nested" hierarchy from the IVsHierarchyItem. var nestedHierarchy = hierarchyItem.HierarchyIdentity.NestedHierarchy; + using var candidateProjects = TemporaryArray.Empty; + // First filter the projects by matching up properties on the input hierarchy against properties on each // project's hierarchy. - var candidateProjects = _workspace.CurrentSolution.Projects - .Where(p => + foreach (var currentId in solution.ProjectIds) + { + var hierarchy = _workspace.GetHierarchy(currentId); + if (hierarchy == nestedHierarchy) { + var currentProject = solution.GetProject(currentId); + // We're about to access various properties of the IVsHierarchy associated with the project. // The properties supported and the interpretation of their values varies from one project system // to another. This code is designed with C# and VB in mind, so we need to filter out everything // else. - if (p.Language is not LanguageNames.CSharp - and not LanguageNames.VisualBasic) - { - return false; - } - - var hierarchy = _workspace.GetHierarchy(p.Id); - - return hierarchy == nestedHierarchy; - }) - .ToArray(); + if (currentProject?.Language is LanguageNames.CSharp or LanguageNames.VisualBasic) + candidateProjects.Add(currentProject); + } + } // If we only have one candidate then no further checks are required. - if (candidateProjects.Length == 1) + if (candidateProjects.Count == 1) { - projectId = candidateProjects[0].Id; - return true; + project = candidateProjects[0]; + return project != null; } // For CPS projects, we may have a string we extracted from a $TFM-prefixed capability; compare that to the string we're given // from CPS to see if this matches. if (targetFrameworkMoniker != null) { - var matchingProject = candidateProjects.FirstOrDefault(p => _workspace.TryGetDependencyNodeTargetIdentifier(p.Id) == targetFrameworkMoniker); + var matchingProject = candidateProjects.FirstOrDefault( + static (project, tuple) => tuple._workspace.TryGetDependencyNodeTargetIdentifier(project.Id) == tuple.targetFrameworkMoniker, + (_workspace, targetFrameworkMoniker)); if (matchingProject != null) { - projectId = matchingProject.Id; - return true; + project = matchingProject; + return project != null; } } @@ -81,12 +91,11 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string? targetFramew { if (!candidateProject.DocumentIds.Any(id => ContainedDocument.TryGetContainedDocument(id) != null)) { - projectId = candidateProject.Id; - return true; + project = candidateProject; + return project != null; } } - projectId = null; return false; } } diff --git a/src/VisualStudio/Core/Def/Implementation/IHierarchyItemToProjectIdMap.cs b/src/VisualStudio/Core/Def/Implementation/IHierarchyItemToProjectIdMap.cs index 0006128b2b7c9..c890ec608b87c 100644 --- a/src/VisualStudio/Core/Def/Implementation/IHierarchyItemToProjectIdMap.cs +++ b/src/VisualStudio/Core/Def/Implementation/IHierarchyItemToProjectIdMap.cs @@ -22,7 +22,24 @@ internal interface IHierarchyItemToProjectIdMap : IWorkspaceService /// An optional string representing a TargetFrameworkMoniker. /// This is only useful in multi-targeting scenarios where there may be multiple Roslyn projects /// (one per target framework) for a single project on disk. - /// The of the found project, if any. + /// The of the found project, if any. /// True if the desired project was found; false otherwise. - bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out ProjectId? projectId); + /// + /// Can be called on any thread. + /// + bool TryGetProject(IVsHierarchyItem hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out Project? project); +} + +internal static class IHierarchyItemToProjectIdMapExtensions +{ + /// "/> + public static bool TryGetProjectId(this IHierarchyItemToProjectIdMap idMap, IVsHierarchyItem hierarchyItem, string? targetFrameworkMoniker, [NotNullWhen(true)] out ProjectId? projectId) + { + projectId = null; + if (!idMap.TryGetProject(hierarchyItem, targetFrameworkMoniker, out var project)) + return false; + + projectId = project.Id; + return true; + } } diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs index 05764a55f1fec..1b39a2ae14a0c 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs @@ -5,7 +5,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 7a25a8b208664..8edff80c8b371 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -114,9 +114,6 @@ - - - diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index aaf0e6d907cd4..701193ed17739 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -1,5 +1,5 @@ [$RootKey$\Menus] -"{6cf2e545-6109-4730-8883-cf43d7aec3e1}"=", Menus.ctmenu, 21" +"{6cf2e545-6109-4730-8883-cf43d7aec3e1}"=", Menus.ctmenu, 22" // [ProvideUIContextRule( // Guids.EncCapableProjectExistsInWorkspaceUIContextString, diff --git a/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs b/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs deleted file mode 100644 index 76abcdc6c535e..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs +++ /dev/null @@ -1,855 +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.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.NavigateTo; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.CodeSchema; -using Microsoft.VisualStudio.GraphModel.Schemas; -using Microsoft.VisualStudio.Progression.CodeSchema; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed partial class GraphBuilder -{ - // Our usage of SemaphoreSlim is fine. We don't perform blocking waits for it on the UI thread. -#pragma warning disable RS0030 // Do not use banned APIs - private readonly SemaphoreSlim _gate = new(initialCount: 1); -#pragma warning restore RS0030 // Do not use banned APIs - - private readonly ISet _createdNodes = new HashSet(); - private readonly IList> _deferredPropertySets = []; - - private readonly Dictionary _nodeToContextProjectMap = []; - private readonly Dictionary _nodeToContextDocumentMap = []; - private readonly Dictionary _nodeToSymbolMap = []; - - /// - /// The input solution. Never null. - /// - private readonly Solution _solution; - - public GraphBuilder(Solution solution) - { - _solution = solution; - } - - public static async Task CreateForInputNodesAsync( - Solution solution, IEnumerable inputNodes, CancellationToken cancellationToken) - { - var builder = new GraphBuilder(solution); - - foreach (var inputNode in inputNodes) - { - if (inputNode.HasCategory(CodeNodeCategories.File)) - { - builder.PopulateMapsForFileInputNode(inputNode, cancellationToken); - } - else if (!inputNode.HasCategory(CodeNodeCategories.SourceLocation)) - { - await builder.PopulateMapsForSymbolInputNodeAsync(inputNode, cancellationToken).ConfigureAwait(false); - } - } - - return builder; - } - - private void PopulateMapsForFileInputNode(GraphNode inputNode, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - var projectPath = inputNode.Id.GetNestedValueByName(CodeGraphNodeIdName.Assembly); - var filePath = inputNode.Id.GetNestedValueByName(CodeGraphNodeIdName.File); - - if (projectPath == null || filePath == null) - { - return; - } - - var docIdsWithPath = _solution.GetDocumentIdsWithFilePath(filePath.OriginalString); - Document? document = null; - Project? project = null; - - foreach (var docIdWithPath in docIdsWithPath) - { - var projectState = _solution.GetProjectState(docIdWithPath.ProjectId); - if (projectState == null) - { - FatalError.ReportAndCatch(new Exception("GetDocumentIdsWithFilePath returned a document in a project that does not exist.")); - continue; - } - - if (string.Equals(projectState.FilePath, projectPath.OriginalString)) - { - project = _solution.GetRequiredProject(projectState.Id); - document = project.GetDocument(docIdWithPath); - break; - } - } - - if (document == null || project == null) - { - return; - } - - _nodeToContextProjectMap.Add(inputNode, project); - _nodeToContextDocumentMap.Add(inputNode, document); - } - } - - private async Task PopulateMapsForSymbolInputNodeAsync(GraphNode inputNode, CancellationToken cancellationToken) - { - using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - var projectId = (ProjectId)inputNode[RoslynGraphProperties.ContextProjectId]; - if (projectId == null) - { - return; - } - - var project = _solution.GetProject(projectId); - if (project == null) - { - return; - } - - _nodeToContextProjectMap.Add(inputNode, project); - - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var symbolId = (SymbolKey?)inputNode[RoslynGraphProperties.SymbolId]; - var symbol = symbolId.Value.Resolve(compilation, cancellationToken: cancellationToken).Symbol; - if (symbol != null) - { - _nodeToSymbolMap.Add(inputNode, symbol); - } - - var documentId = (DocumentId)inputNode[RoslynGraphProperties.ContextDocumentId]; - if (documentId != null) - { - var document = project.GetDocument(documentId); - if (document != null) - { - _nodeToContextDocumentMap.Add(inputNode, document); - } - } - } - } - - public Project GetContextProject(GraphNode node, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - _nodeToContextProjectMap.TryGetValue(node, out var project); - return project; - } - } - - public ProjectId GetContextProjectId(Project project, ISymbol symbol) - { - var thisProject = project.Solution.GetProject(symbol.ContainingAssembly) ?? project; - return thisProject.Id; - } - - public Document GetContextDocument(GraphNode node, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - _nodeToContextDocumentMap.TryGetValue(node, out var document); - return document; - } - } - - public ISymbol GetSymbol(GraphNode node, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - _nodeToSymbolMap.TryGetValue(node, out var symbol); - return symbol; - } - } - - public Task AddNodeAsync(ISymbol symbol, GraphNode relatedNode, CancellationToken cancellationToken) - { - // The lack of a lock here is acceptable, since each of the functions lock, and GetContextProject/GetContextDocument - // never change for the same input. - return AddNodeAsync( - symbol, - GetContextProject(relatedNode, cancellationToken), - GetContextDocument(relatedNode, cancellationToken), - cancellationToken); - } - - public async Task AddNodeAsync( - ISymbol symbol, Project contextProject, Document contextDocument, CancellationToken cancellationToken) - { - // Figure out what the location for this node should be. We'll arbitrarily pick the - // first one, unless we have a contextDocument to restrict it - var preferredLocation = symbol.Locations.FirstOrDefault(l => l.SourceTree != null); - - if (contextDocument != null) - { - var syntaxTree = await contextDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - - // If we have one in that tree, use it - preferredLocation = symbol.Locations.FirstOrDefault(l => l.SourceTree == syntaxTree) ?? preferredLocation; - } - - // We may need to look up source code within this solution - if (preferredLocation == null && symbol.Locations.Any(static loc => loc.IsInMetadata)) - { - var newSymbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, contextProject.Solution, cancellationToken).ConfigureAwait(false); - if (newSymbol != null) - preferredLocation = newSymbol.Locations.Where(loc => loc.IsInSource).FirstOrDefault(); - } - - using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - var node = await GetOrCreateNodeAsync(Graph, symbol, _solution, cancellationToken).ConfigureAwait(false); - - node[RoslynGraphProperties.SymbolId] = (SymbolKey?)symbol.GetSymbolKey(cancellationToken); - node[RoslynGraphProperties.ContextProjectId] = GetContextProjectId(contextProject, symbol); - node[RoslynGraphProperties.ExplicitInterfaceImplementations] = symbol.ExplicitInterfaceImplementations().Select(s => s.GetSymbolKey()).ToList(); - node[RoslynGraphProperties.DeclaredAccessibility] = symbol.DeclaredAccessibility; - node[RoslynGraphProperties.SymbolModifiers] = symbol.GetSymbolModifiers(); - node[RoslynGraphProperties.SymbolKind] = symbol.Kind; - - if (contextDocument != null) - node[RoslynGraphProperties.ContextDocumentId] = contextDocument.Id; - - if (preferredLocation?.SourceTree != null) - { - var lineSpan = preferredLocation.GetLineSpan(); - var sourceLocation = TryCreateSourceLocation( - preferredLocation.SourceTree.FilePath, - lineSpan.Span); - if (sourceLocation != null) - node[CodeNodeProperties.SourceLocation] = sourceLocation.Value; - } - - // Keep track of this as a node we have added. Note this is a HashSet, so if the node was already added - // we won't double-count. - _createdNodes.Add(node); - - _nodeToSymbolMap[node] = symbol; - _nodeToContextProjectMap[node] = contextProject; - - if (contextDocument != null) - _nodeToContextDocumentMap[node] = contextDocument; - - return node; - } - } - - internal static async Task GetOrCreateNodeAsync(Graph graph, ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - GraphNode node; - - switch (symbol.Kind) - { - case SymbolKind.Assembly: - node = await GetOrCreateNodeAssemblyAsync(graph, (IAssemblySymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Namespace: - node = await GetOrCreateNodeForNamespaceAsync(graph, (INamespaceSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.NamedType: - case SymbolKind.ErrorType: - node = await GetOrCreateNodeForNamedTypeAsync(graph, (INamedTypeSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Method: - node = await GetOrCreateNodeForMethodAsync(graph, (IMethodSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Field: - node = await GetOrCreateNodeForFieldAsync(graph, (IFieldSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Property: - node = await GetOrCreateNodeForPropertyAsync(graph, (IPropertySymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Event: - node = await GetOrCreateNodeForEventAsync(graph, (IEventSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Parameter: - node = await GetOrCreateNodeForParameterAsync(graph, (IParameterSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - case SymbolKind.Local: - case SymbolKind.RangeVariable: - node = await GetOrCreateNodeForLocalVariableAsync(graph, symbol, solution, cancellationToken).ConfigureAwait(false); - break; - - default: - throw new ArgumentException("symbol"); - } - - UpdatePropertiesForNode(symbol, node); - UpdateLabelsForNode(symbol, solution, node); - - return node; - } - - private static async Task GetOrCreateNodeForParameterAsync(Graph graph, IParameterSymbol parameterSymbol, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForParameterAsync(parameterSymbol, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - node.AddCategory(CodeNodeCategories.Parameter); - - node.SetValue(Properties.IsByReference, parameterSymbol.RefKind == RefKind.Ref); - node.SetValue(Properties.IsOut, parameterSymbol.RefKind == RefKind.Out); - node.SetValue(Properties.IsParameterArray, parameterSymbol.IsParams); - - return node; - } - - private static async Task GetOrCreateNodeForLocalVariableAsync(Graph graph, ISymbol localSymbol, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForLocalVariableAsync(localSymbol, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - node.AddCategory(NodeCategories.LocalExpression); - - return node; - } - - private static async Task GetOrCreateNodeAssemblyAsync(Graph graph, IAssemblySymbol assemblySymbol, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForAssemblyAsync(assemblySymbol, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - node.AddCategory(CodeNodeCategories.Assembly); - - return node; - } - - private static void UpdateLabelsForNode(ISymbol symbol, Solution solution, GraphNode node) - { - var progressionLanguageService = solution.Services.GetLanguageServices(symbol.Language).GetService(); - - // A call from unittest may not have a proper language service. - if (progressionLanguageService != null) - { - node[RoslynGraphProperties.Description] = progressionLanguageService.GetDescriptionForSymbol(symbol, includeContainingSymbol: false); - node[RoslynGraphProperties.DescriptionWithContainingSymbol] = progressionLanguageService.GetDescriptionForSymbol(symbol, includeContainingSymbol: true); - - node[RoslynGraphProperties.FormattedLabelWithoutContainingSymbol] = progressionLanguageService.GetLabelForSymbol(symbol, includeContainingSymbol: false); - node[RoslynGraphProperties.FormattedLabelWithContainingSymbol] = progressionLanguageService.GetLabelForSymbol(symbol, includeContainingSymbol: true); - } - - switch (symbol.Kind) - { - case SymbolKind.NamedType: - var typeSymbol = (INamedTypeSymbol)symbol; - if (typeSymbol.IsGenericType) - { - // Symbol.name does not contain type params for generic types, so we populate them here for some requiring cases like VS properties panel. - node.Label = (string)node[RoslynGraphProperties.FormattedLabelWithoutContainingSymbol]; - - // Some consumers like CodeMap want to show types in an unified way for both C# and VB. - // Therefore, populate a common label property using only name and its type parameters. - // For example, VB's "Goo(Of T)" or C#'s "Goo(): T" will be shown as "Goo". - // This property will be used for drag-and-drop case. - var commonLabel = new System.Text.StringBuilder(); - commonLabel.Append(typeSymbol.Name); - commonLabel.Append('<'); - commonLabel.Append(string.Join(", ", typeSymbol.TypeParameters.Select(t => t.Name))); - commonLabel.Append('>'); - node[Microsoft.VisualStudio.ArchitectureTools.ProgressiveReveal.ProgressiveRevealSchema.CommonLabel] = commonLabel.ToString(); - - return; - } - else - { - node.Label = symbol.Name; - } - - break; - - case SymbolKind.Method: - var methodSymbol = (IMethodSymbol)symbol; - if (methodSymbol.MethodKind == MethodKind.Constructor) - { - node.Label = CodeQualifiedIdentifierBuilder.SpecialNames.GetConstructorLabel(methodSymbol.ContainingSymbol.Name); - } - else if (methodSymbol.MethodKind == MethodKind.StaticConstructor) - { - node.Label = CodeQualifiedIdentifierBuilder.SpecialNames.GetStaticConstructorLabel(methodSymbol.ContainingSymbol.Name); - } - else if (methodSymbol.MethodKind == MethodKind.Destructor) - { - node.Label = CodeQualifiedIdentifierBuilder.SpecialNames.GetFinalizerLabel(methodSymbol.ContainingSymbol.Name); - } - else - { - node.Label = methodSymbol.Name; - } - - break; - - case SymbolKind.Property: - node.Label = symbol.MetadataName; - - var propertySymbol = (IPropertySymbol)symbol; - if (propertySymbol.IsIndexer && LanguageNames.CSharp == propertySymbol.Language) - { - // For C# indexer, we will strip off the "[]" - node.Label = symbol.Name.Replace("[]", string.Empty); - } - - break; - - case SymbolKind.Namespace: - // Use a name with its parents (e.g., A.B.C) - node.Label = symbol.ToDisplayString(); - break; - - default: - node.Label = symbol.Name; - break; - } - - // When a node is dragged and dropped from SE to CodeMap, its label could be reset during copying to clipboard. - // So, we try to keep its label that we computed above in a common label property, which CodeMap can access later. - node[Microsoft.VisualStudio.ArchitectureTools.ProgressiveReveal.ProgressiveRevealSchema.CommonLabel] = node.Label; - } - - private static void UpdatePropertiesForNode(ISymbol symbol, GraphNode node) - { - // Set accessibility properties - switch (symbol.DeclaredAccessibility) - { - case Accessibility.Public: - node[Properties.IsPublic] = true; - break; - - case Accessibility.Internal: - node[Properties.IsInternal] = true; - break; - - case Accessibility.Protected: - node[Properties.IsProtected] = true; - break; - - case Accessibility.Private: - node[Properties.IsPrivate] = true; - break; - - case Accessibility.ProtectedOrInternal: - node[Properties.IsProtectedOrInternal] = true; - break; - - case Accessibility.ProtectedAndInternal: - node[Properties.IsProtected] = true; - node[Properties.IsInternal] = true; - break; - - case Accessibility.NotApplicable: - break; - } - - // Set common properties - if (symbol.IsAbstract) - { - node[Properties.IsAbstract] = true; - } - - if (symbol.IsSealed) - { - // For VB module, do not set IsFinal since it's not inheritable. - if (!symbol.IsModuleType()) - { - node[Properties.IsFinal] = true; - } - } - - if (symbol.IsStatic) - { - node[Properties.IsStatic] = true; - } - - if (symbol.IsVirtual) - { - node[Properties.IsVirtual] = true; - } - - if (symbol.IsOverride) - { - // The property name is a misnomer, but this is what the previous providers do. - node[Microsoft.VisualStudio.Progression.DgmlProperties.IsOverloaded] = true; - } - - // Set type-specific properties - if (symbol is ITypeSymbol typeSymbol && typeSymbol.IsAnonymousType) - { - node[Properties.IsAnonymous] = true; - } - else if (symbol is IMethodSymbol methodSymbol) - { - UpdateMethodPropertiesForNode(methodSymbol, node); - } - } - - private static void UpdateMethodPropertiesForNode(IMethodSymbol symbol, GraphNode node) - { - if (symbol.HidesBaseMethodsByName) - { - node[Properties.IsHideBySignature] = true; - } - - if (symbol.IsExtensionMethod) - { - node[Properties.IsExtension] = true; - } - - switch (symbol.MethodKind) - { - case MethodKind.AnonymousFunction: - node[Properties.IsAnonymous] = true; - break; - - case MethodKind.BuiltinOperator: - case MethodKind.UserDefinedOperator: - node[Properties.IsOperator] = true; - break; - - case MethodKind.Constructor: - case MethodKind.StaticConstructor: - node[Properties.IsConstructor] = true; - break; - - case MethodKind.Conversion: - // Operator implicit/explicit - node[Properties.IsOperator] = true; - break; - - case MethodKind.Destructor: - node[Properties.IsFinalizer] = true; - break; - - case MethodKind.PropertyGet: - node[Properties.IsPropertyGet] = true; - break; - - case MethodKind.PropertySet: - node[Properties.IsPropertySet] = true; - break; - } - } - - private static async Task GetOrCreateNodeForNamespaceAsync(Graph graph, INamespaceSymbol symbol, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForNamespaceAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - node.AddCategory(CodeNodeCategories.Namespace); - - return node; - } - - private static async Task GetOrCreateNodeForNamedTypeAsync(Graph graph, INamedTypeSymbol namedType, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForTypeAsync(namedType, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - string iconGroupName; - - switch (namedType.TypeKind) - { - case TypeKind.Class: - node.AddCategory(CodeNodeCategories.Class); - iconGroupName = "Class"; - break; - - case TypeKind.Delegate: - node.AddCategory(CodeNodeCategories.Delegate); - iconGroupName = "Delegate"; - break; - - case TypeKind.Enum: - node.AddCategory(CodeNodeCategories.Enum); - iconGroupName = "Enum"; - break; - - case TypeKind.Interface: - node.AddCategory(CodeNodeCategories.Interface); - iconGroupName = "Interface"; - break; - - case TypeKind.Module: - node.AddCategory(CodeNodeCategories.Module); - iconGroupName = "Module"; - break; - - case TypeKind.Struct: - node.AddCategory(CodeNodeCategories.Struct); - iconGroupName = "Struct"; - break; - - case TypeKind.Error: - node.AddCategory(CodeNodeCategories.Type); - iconGroupName = "Error"; - break; - - default: - throw ExceptionUtilities.UnexpectedValue(namedType.TypeKind); - } - - node[DgmlNodeProperties.Icon] = IconHelper.GetIconName(iconGroupName, namedType.DeclaredAccessibility); - node[RoslynGraphProperties.TypeKind] = namedType.TypeKind; - - return node; - } - - private static async Task GetOrCreateNodeForMethodAsync(Graph graph, IMethodSymbol method, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForMemberAsync(method, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - - node.AddCategory(CodeNodeCategories.Method); - - var isOperator = method.MethodKind is MethodKind.UserDefinedOperator or MethodKind.Conversion; - node[DgmlNodeProperties.Icon] = isOperator - ? IconHelper.GetIconName("Operator", method.DeclaredAccessibility) - : IconHelper.GetIconName("Method", method.DeclaredAccessibility); - - node[RoslynGraphProperties.TypeKind] = method.ContainingType.TypeKind; - node[RoslynGraphProperties.MethodKind] = method.MethodKind; - - return node; - } - - private static async Task GetOrCreateNodeForFieldAsync(Graph graph, IFieldSymbol field, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForMemberAsync(field, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - - node.AddCategory(CodeNodeCategories.Field); - - if (field.ContainingType.TypeKind == TypeKind.Enum) - { - node[DgmlNodeProperties.Icon] = IconHelper.GetIconName("EnumMember", field.DeclaredAccessibility); - } - else - { - node[DgmlNodeProperties.Icon] = IconHelper.GetIconName("Field", field.DeclaredAccessibility); - } - - return node; - } - - private static async Task GetOrCreateNodeForPropertyAsync(Graph graph, IPropertySymbol property, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForMemberAsync(property, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - - node.AddCategory(CodeNodeCategories.Property); - - node[DgmlNodeProperties.Icon] = IconHelper.GetIconName("Property", property.DeclaredAccessibility); - node[RoslynGraphProperties.TypeKind] = property.ContainingType.TypeKind; - - return node; - } - - private static async Task GetOrCreateNodeForEventAsync(Graph graph, IEventSymbol eventSymbol, Solution solution, CancellationToken cancellationToken) - { - var id = await GraphNodeIdCreation.GetIdForMemberAsync(eventSymbol, solution, cancellationToken).ConfigureAwait(false); - var node = graph.Nodes.GetOrCreate(id); - - node.AddCategory(CodeNodeCategories.Event); - - node[DgmlNodeProperties.Icon] = IconHelper.GetIconName("Event", eventSymbol.DeclaredAccessibility); - node[RoslynGraphProperties.TypeKind] = eventSymbol.ContainingType.TypeKind; - - return node; - } - - public void AddLink(GraphNode from, GraphCategory category, GraphNode to, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - Graph.Links.GetOrCreate(from, to).AddCategory(category); - } - } - - public GraphNode? TryAddNodeForDocument(Document document, CancellationToken cancellationToken) - { - // Under the covers, progression will attempt to convert a label into a URI. Ensure that we - // can do this safely. before proceeding. - // - // The corresponding code on the progression side does: new Uri(text, UriKind.RelativeOrAbsolute) - // so we check that same kind here. - var fileName = Path.GetFileName(document.FilePath); - if (!Uri.TryCreate(fileName, UriKind.RelativeOrAbsolute, out _)) - return null; - - using (_gate.DisposableWait(cancellationToken)) - { - var id = GraphNodeIdCreation.GetIdForDocument(document); - - var node = Graph.Nodes.GetOrCreate(id, fileName, CodeNodeCategories.ProjectItem); - - _nodeToContextDocumentMap[node] = document; - _nodeToContextProjectMap[node] = document.Project; - - _createdNodes.Add(node); - - return node; - } - } - - public async Task CreateNodeAsync(Solution solution, INavigateToSearchResult result, CancellationToken cancellationToken) - { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); - var project = document.Project; - - // If it doesn't belong to a document or project we can navigate to, then ignore entirely. - if (document.FilePath == null || project.FilePath == null) - return null; - - var category = result.Kind switch - { - NavigateToItemKind.Class => CodeNodeCategories.Class, - NavigateToItemKind.Delegate => CodeNodeCategories.Delegate, - NavigateToItemKind.Enum => CodeNodeCategories.Enum, - NavigateToItemKind.Interface => CodeNodeCategories.Interface, - NavigateToItemKind.Module => CodeNodeCategories.Module, - NavigateToItemKind.Structure => CodeNodeCategories.Struct, - NavigateToItemKind.Method => CodeNodeCategories.Method, - NavigateToItemKind.Property => CodeNodeCategories.Property, - NavigateToItemKind.Event => CodeNodeCategories.Event, - NavigateToItemKind.Constant or - NavigateToItemKind.EnumItem or - NavigateToItemKind.Field => CodeNodeCategories.Field, - _ => null, - }; - - // If it's not a category that progression understands, then ignore. - if (category == null) - return null; - - // Get or make a node for this symbol's containing document that will act as the parent node in the UI. - var documentNode = this.TryAddNodeForDocument(document, cancellationToken); - if (documentNode == null) - return null; - - // For purposes of keying this node, just use the display text we will show. In practice, outside of error - // scenarios this will be unique and suitable as an ID (esp. as these names are joined with their parent - // document name to form the full ID). - var label = result.NavigableItem.DisplayTaggedParts.JoinText(); - var id = documentNode.Id.Add(GraphNodeId.GetLiteral(label)); - - // If we already have a node that matches this (say there are multiple identical sibling symbols in an error - // situation). We just ignore the second match. - var existing = Graph.Nodes.Get(id); - if (existing != null) - return null; - - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - var span = text.Lines.GetLinePositionSpan(NavigateToUtilities.GetBoundedSpan(result.NavigableItem, text)); - var sourceLocation = TryCreateSourceLocation(document.FilePath, span); - if (sourceLocation == null) - return null; - - var symbolNode = Graph.Nodes.GetOrCreate(id); - - symbolNode.Label = label; - symbolNode.AddCategory(category); - symbolNode[DgmlNodeProperties.Icon] = GetIconString(result.NavigableItem.Glyph); - symbolNode[RoslynGraphProperties.ContextDocumentId] = document.Id; - symbolNode[RoslynGraphProperties.ContextProjectId] = document.Project.Id; - - symbolNode[CodeNodeProperties.SourceLocation] = sourceLocation.Value; - - this.AddLink(documentNode, GraphCommonSchema.Contains, symbolNode, cancellationToken); - - return symbolNode; - } - - public static SourceLocation? TryCreateSourceLocation(string path, LinePositionSpan span) - { - // SourceLocation's constructor attempts to create an absolute uri. So if we can't do that - // bail out immediately. - if (!Uri.TryCreate(path, UriKind.Absolute, out var uri)) - return null; - - return new SourceLocation( - uri, - new Position(span.Start.Line, span.Start.Character), - new Position(span.End.Line, span.End.Character)); - } - - private static string? GetIconString(Glyph glyph) - { - var groupName = glyph switch - { - Glyph.ClassPublic or Glyph.ClassProtected or Glyph.ClassPrivate or Glyph.ClassInternal => "Class", - Glyph.ConstantPublic or Glyph.ConstantProtected or Glyph.ConstantPrivate or Glyph.ConstantInternal => "Field", - Glyph.DelegatePublic or Glyph.DelegateProtected or Glyph.DelegatePrivate or Glyph.DelegateInternal => "Delegate", - Glyph.EnumPublic or Glyph.EnumProtected or Glyph.EnumPrivate or Glyph.EnumInternal => "Enum", - Glyph.EnumMemberPublic or Glyph.EnumMemberProtected or Glyph.EnumMemberPrivate or Glyph.EnumMemberInternal => "EnumMember", - Glyph.ExtensionMethodPublic or Glyph.ExtensionMethodProtected or Glyph.ExtensionMethodPrivate or Glyph.ExtensionMethodInternal => "Method", - Glyph.EventPublic or Glyph.EventProtected or Glyph.EventPrivate or Glyph.EventInternal => "Event", - Glyph.FieldPublic or Glyph.FieldProtected or Glyph.FieldPrivate or Glyph.FieldInternal => "Field", - Glyph.InterfacePublic or Glyph.InterfaceProtected or Glyph.InterfacePrivate or Glyph.InterfaceInternal => "Interface", - Glyph.MethodPublic or Glyph.MethodProtected or Glyph.MethodPrivate or Glyph.MethodInternal => "Method", - Glyph.ModulePublic or Glyph.ModuleProtected or Glyph.ModulePrivate or Glyph.ModuleInternal => "Module", - Glyph.PropertyPublic or Glyph.PropertyProtected or Glyph.PropertyPrivate or Glyph.PropertyInternal => "Property", - Glyph.StructurePublic or Glyph.StructureProtected or Glyph.StructurePrivate or Glyph.StructureInternal => "Structure", - _ => null, - }; - - if (groupName == null) - return null; - - return IconHelper.GetIconName(groupName, GlyphExtensions.GetAccessibility(GlyphTags.GetTags(glyph))); - } - - public void ApplyToGraph(Graph graph, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - using var graphTransaction = new GraphTransactionScope(); - graph.Merge(this.Graph); - - foreach (var deferredProperty in _deferredPropertySets) - { - var nodeToSet = graph.Nodes.Get(deferredProperty.Item1.Id); - nodeToSet.SetValue(deferredProperty.Item2, deferredProperty.Item3); - } - - graphTransaction.Complete(); - } - } - - public void AddDeferredPropertySet(GraphNode node, GraphProperty property, object value, CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - _deferredPropertySets.Add(Tuple.Create(node, property, value)); - } - } - - public Graph Graph { get; } = new(); - - public ImmutableArray GetCreatedNodes(CancellationToken cancellationToken) - { - using (_gate.DisposableWait(cancellationToken)) - { - return [.. _createdNodes]; - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphFormattedLabelExtension.cs b/src/VisualStudio/Core/Def/Progression/GraphFormattedLabelExtension.cs deleted file mode 100644 index e1f517d8ca36c..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphFormattedLabelExtension.cs +++ /dev/null @@ -1,48 +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.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class GraphFormattedLabelExtension : IGraphFormattedLabel -{ - public string Description(GraphObject graphObject, string graphCommandDefinitionIdentifier) - { - return GetStringPropertyForGraphObject( - graphObject, - graphCommandDefinitionIdentifier, - RoslynGraphProperties.Description, - RoslynGraphProperties.DescriptionWithContainingSymbol); - } - - public string Label(GraphObject graphObject, string graphCommandDefinitionIdentifier) - { - return GetStringPropertyForGraphObject( - graphObject, - graphCommandDefinitionIdentifier, - RoslynGraphProperties.FormattedLabelWithoutContainingSymbol, - RoslynGraphProperties.FormattedLabelWithContainingSymbol); - } - - private static string GetStringPropertyForGraphObject(GraphObject graphObject, string graphCommandDefinitionIdentifier, GraphProperty propertyWithoutContainingSymbol, GraphProperty propertyWithContainingSymbol) - { - - if (graphObject is GraphNode graphNode) - { - if (graphCommandDefinitionIdentifier != GraphCommandDefinition.Contains.Id) - { - return graphNode.GetValue(propertyWithContainingSymbol); - } - else - { - return graphNode.GetValue(propertyWithoutContainingSymbol); - } - } - - return null; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs deleted file mode 100644 index 70cb966833785..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs +++ /dev/null @@ -1,116 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.GoToDefinition; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.CodeSchema; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -using Workspace = Microsoft.CodeAnalysis.Workspace; - -internal sealed class GraphNavigatorExtension( - IThreadingContext threadingContext, - Workspace workspace, - Lazy streamingPresenter) : IGraphNavigateToItem -{ - private readonly IThreadingContext _threadingContext = threadingContext; - private readonly Workspace _workspace = workspace; - private readonly Lazy _streamingPresenter = streamingPresenter; - - public void NavigateTo(GraphObject graphObject) - { - if (graphObject is not GraphNode graphNode) - return; - - _threadingContext.JoinableTaskFactory.Run(() => NavigateToAsync(graphNode, CancellationToken.None)); - } - - private async Task NavigateToAsync(GraphNode graphNode, CancellationToken cancellationToken) - { - var projectId = graphNode.GetValue(RoslynGraphProperties.ContextProjectId); - - if (projectId is null) - return; - - var solution = _workspace.CurrentSolution; - var project = solution.GetProject(projectId); - if (project is null) - return; - - // Go through the mainline symbol id path if we have it. That way we notify third parties, and we can navigate - // to metadata. - var symbolId = graphNode.GetValue(RoslynGraphProperties.SymbolId); - if (symbolId is not null) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - if (compilation is not null) - { - var symbol = symbolId.Value.Resolve(compilation, cancellationToken: cancellationToken).GetAnySymbol(); - if (symbol is not null) - { - await GoToDefinitionHelpers.TryNavigateToLocationAsync( - symbol, project.Solution, _threadingContext, _streamingPresenter.Value, cancellationToken).ConfigureAwait(false); - return; - } - } - } - - // If we didn't have a symbol id, attempt to navigate to the source location directly if the node includes one. - var sourceLocation = graphNode.GetValue(CodeNodeProperties.SourceLocation); - if (sourceLocation.FileName is null || !sourceLocation.IsValid) - return; - - var document = project.Documents.FirstOrDefault( - d => string.Equals( - d.FilePath, - sourceLocation.FileName.LocalPath, - StringComparison.OrdinalIgnoreCase)); - - if (document == null) - return; - - // We must find the right document in this project. This may not be the - // ContextDocumentId if you have a partial member that is shown under one - // document, but only exists in the other - - var editorWorkspace = document.Project.Solution.Workspace; - var navigationService = editorWorkspace.Services.GetRequiredService(); - - // TODO: Get the platform to use and pass us an operation context, or create one ourselves. - await navigationService.TryNavigateToLineAndOffsetAsync( - _threadingContext, - editorWorkspace, - document.Id, - sourceLocation.StartPosition.Line, - sourceLocation.StartPosition.Character, - NavigationOptions.Default, - cancellationToken).ConfigureAwait(false); - } - - public int GetRank(GraphObject graphObject) - { - if (graphObject is GraphNode graphNode) - { - var sourceLocation = graphNode.GetValue(CodeNodeProperties.SourceLocation); - var projectId = graphNode.GetValue(RoslynGraphProperties.ContextProjectId); - - if (sourceLocation.IsValid && projectId != null) - { - return GraphNavigateToItemRanks.OwnItem; - } - } - - return GraphNavigateToItemRanks.CanNavigateToItem; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphNodeCreation.cs b/src/VisualStudio/Core/Def/Progression/GraphNodeCreation.cs index 7cbae5050f676..60076ab449d24 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphNodeCreation.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphNodeCreation.cs @@ -7,73 +7,17 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.LanguageServices.Implementation.Progression; namespace Microsoft.VisualStudio.LanguageServices.Progression; -/// -/// A helper class that implements the creation of s. -/// +[Obsolete("This class is not implemented and should not be used.", error: true)] public static class GraphNodeCreation { - public static async Task CreateNodeIdAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - if (symbol == null) - { - throw new ArgumentNullException(nameof(symbol)); - } + [Obsolete("This method is not implemented and always returns an empty GraphNodeId.", error: true)] + public static Task CreateNodeIdAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) + => Task.FromResult(GraphNodeId.Empty); - if (solution == null) - { - throw new ArgumentNullException(nameof(solution)); - } - - switch (symbol.Kind) - { - case SymbolKind.Assembly: - return await GraphNodeIdCreation.GetIdForAssemblyAsync((IAssemblySymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - - case SymbolKind.Namespace: - return await GraphNodeIdCreation.GetIdForNamespaceAsync((INamespaceSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - - case SymbolKind.NamedType: - return await GraphNodeIdCreation.GetIdForTypeAsync((ITypeSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - - case SymbolKind.Method: - case SymbolKind.Field: - case SymbolKind.Property: - case SymbolKind.Event: - return await GraphNodeIdCreation.GetIdForMemberAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - - case SymbolKind.Parameter: - return await GraphNodeIdCreation.GetIdForParameterAsync((IParameterSymbol)symbol, solution, cancellationToken).ConfigureAwait(false); - - case SymbolKind.Local: - case SymbolKind.RangeVariable: - return await GraphNodeIdCreation.GetIdForLocalVariableAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - - default: - throw new ArgumentException(string.Format(ServicesVSResources.Can_t_create_a_node_id_for_this_symbol_kind_colon_0, symbol)); - } - } - - public static async Task CreateNodeAsync(this Graph graph, ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - if (graph == null) - { - throw new ArgumentNullException(nameof(graph)); - } - - if (symbol == null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - if (solution == null) - { - throw new ArgumentNullException(nameof(solution)); - } - - return await GraphBuilder.GetOrCreateNodeAsync(graph, symbol, solution, cancellationToken).ConfigureAwait(false); - } + [Obsolete("This method is not implemented and always returns an empty GraphNode.", error: true)] + public static Task CreateNodeAsync(this Graph graph, ISymbol symbol, Solution solution, CancellationToken cancellationToken) + => Task.FromResult(graph.Nodes.GetOrCreate(GraphNodeId.Empty)); } diff --git a/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs b/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs deleted file mode 100644 index 7038949464e5f..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs +++ /dev/null @@ -1,561 +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 System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.CodeSchema; -using Microsoft.VisualStudio.GraphModel.Schemas; -using Microsoft.VisualStudio.Progression.CodeSchema; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -/// -/// A helper class that implements the creation of GraphNodeIds that matches the .dgml creation -/// by the metadata progression provider. -/// -internal static class GraphNodeIdCreation -{ - public static GraphNodeId GetIdForDocument(Document document) - { - return - GraphNodeId.GetNested( - GraphNodeId.GetPartial(CodeGraphNodeIdName.Assembly, new Uri(document.Project.FilePath, UriKind.RelativeOrAbsolute)), - GraphNodeId.GetPartial(CodeGraphNodeIdName.File, new Uri(document.FilePath, UriKind.RelativeOrAbsolute))); - } - - internal static async Task GetIdForNamespaceAsync(INamespaceSymbol symbol, Solution solution, CancellationToken cancellationToken) - { - var builder = new CodeQualifiedIdentifierBuilder(); - - var assembly = await GetAssemblyFullPathAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - if (assembly != null) - { - builder.Assembly = assembly; - } - - builder.Namespace = symbol.ToDisplayString(); - - return builder.ToQualifiedIdentifier(); - } - - internal static async Task GetIdForTypeAsync(ITypeSymbol symbol, Solution solution, CancellationToken cancellationToken) - { - var nodes = await GetPartialsForNamespaceAndTypeAsync(symbol, true, solution, cancellationToken).ConfigureAwait(false); - var partials = nodes.ToArray(); - - if (partials.Length == 1) - { - return partials[0]; - } - else - { - return GraphNodeId.GetNested(partials); - } - } - - private static async Task> GetPartialsForNamespaceAndTypeAsync(ITypeSymbol symbol, bool includeNamespace, Solution solution, CancellationToken cancellationToken, bool isInGenericArguments = false) - { - var items = new List(); - - Uri assembly = null; - if (includeNamespace) - { - assembly = await GetAssemblyFullPathAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - } - - var underlyingType = ChaseToUnderlyingType(symbol); - - if (symbol.TypeKind == TypeKind.TypeParameter) - { - var typeParameter = (ITypeParameterSymbol)symbol; - - if (typeParameter.TypeParameterKind == TypeParameterKind.Type) - { - if (includeNamespace && !typeParameter.ContainingNamespace.IsGlobalNamespace) - { - if (assembly != null) - { - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Assembly, assembly)); - } - - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Namespace, typeParameter.ContainingNamespace.ToDisplayString())); - } - - items.Add(await GetPartialForTypeAsync(symbol.ContainingType, CodeGraphNodeIdName.Type, solution, cancellationToken).ConfigureAwait(false)); - } - - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Parameter, ((ITypeParameterSymbol)symbol).Ordinal.ToString())); - } - else - { - if (assembly != null) - { - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Assembly, assembly)); - } - - if (underlyingType.TypeKind == TypeKind.Dynamic) - { - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Namespace, "System")); - } - else if (underlyingType.ContainingNamespace != null && !underlyingType.ContainingNamespace.IsGlobalNamespace) - { - items.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Namespace, underlyingType.ContainingNamespace.ToDisplayString())); - } - - items.Add(await GetPartialForTypeAsync(symbol, CodeGraphNodeIdName.Type, solution, cancellationToken, isInGenericArguments).ConfigureAwait(false)); - } - - return items; - } - - private static async Task GetPartialForTypeAsync(ITypeSymbol symbol, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken, bool isInGenericArguments = false) - { - if (symbol is IArrayTypeSymbol arrayType) - { - return await GetPartialForArrayTypeAsync(arrayType, nodeName, solution, cancellationToken).ConfigureAwait(false); - } - else if (symbol is INamedTypeSymbol namedType) - { - return await GetPartialForNamedTypeAsync(namedType, nodeName, solution, cancellationToken, isInGenericArguments).ConfigureAwait(false); - } - else if (symbol is IPointerTypeSymbol pointerType) - { - return await GetPartialForPointerTypeAsync(pointerType, nodeName, solution, cancellationToken).ConfigureAwait(false); - } - else if (symbol is ITypeParameterSymbol typeParameter) - { - return await GetPartialForTypeParameterSymbolAsync(typeParameter, nodeName, solution, cancellationToken).ConfigureAwait(false); - } - else if (symbol is IDynamicTypeSymbol) - { - return GetPartialForDynamicType(nodeName); - } - - throw ExceptionUtilities.Unreachable(); - } - - private static GraphNodeId GetPartialForDynamicType(GraphNodeIdName nodeName) - { - // We always consider this to be the "Object" type since Progression takes a very metadata-ish view of the type - return GraphNodeId.GetPartial(nodeName, "Object"); - } - - private static async Task GetPartialForNamedTypeAsync(INamedTypeSymbol namedType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken, bool isInGenericArguments = false) - { - // If this is a simple type, then we don't have much to do - if (namedType.ContainingType == null && Equals(namedType.ConstructedFrom, namedType) && namedType.Arity == 0) - { - return GraphNodeId.GetPartial(nodeName, namedType.Name); - } - else - { - // For a generic type, we need to populate "type" property with the following form: - // - // Type = (Name =...GenericParameterCount = GenericArguments =...ParentType =...) - // - // where "Name" contains a symbol name - // and "GenericParameterCount" contains the number of type parameters, - // and "GenericArguments" contains its type parameters' node information. - // and "ParentType" contains its containing type's node information. - - var partials = new List - { - GraphNodeId.GetPartial(CodeQualifiedName.Name, namedType.Name) - }; - - if (namedType.Arity > 0) - { - partials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.GenericParameterCountIdentifier, namedType.Arity.ToString())); - } - - // For the property "GenericArguments", we only populate them - // when type parameters are constructed using instance types (i.e., namedType.ConstructedFrom != namedType). - // However, there is a case where we need to populate "GenericArguments" even though arguments are not marked as "constructed" - // because a symbol is not marked as "constructed" when a type is constructed using its own type parameters. - // To distinguish this case, we use "isInGenericArguments" flag which we pass either to populate arguments recursively or to populate "ParentType". - - var hasGenericArguments = (!Equals(namedType.ConstructedFrom, namedType) || isInGenericArguments) && namedType.TypeArguments != null && namedType.TypeArguments.Any(); - - if (hasGenericArguments) - { - var genericArguments = new List(); - foreach (var arg in namedType.TypeArguments) - { - var nodes = await GetPartialsForNamespaceAndTypeAsync(arg, includeNamespace: true, solution: solution, cancellationToken: cancellationToken, isInGenericArguments: true).ConfigureAwait(false); - genericArguments.Add(GraphNodeId.GetNested([.. nodes])); - } - - partials.Add(GraphNodeId.GetArray( - CodeGraphNodeIdName.GenericArgumentsIdentifier, - [.. genericArguments])); - } - - if (namedType.ContainingType != null) - { - partials.Add(await GetPartialForTypeAsync(namedType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken, hasGenericArguments).ConfigureAwait(false)); - } - - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); - } - } - - private static async Task GetPartialForPointerTypeAsync(IPointerTypeSymbol pointerType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) - { - var indirection = 1; - - while (pointerType.PointedAtType.TypeKind == TypeKind.Pointer) - { - indirection++; - pointerType = (IPointerTypeSymbol)pointerType.PointedAtType; - } - - var partials = new List - { - GraphNodeId.GetPartial(CodeQualifiedName.Name, pointerType.PointedAtType.Name), - GraphNodeId.GetPartial(CodeQualifiedName.Indirection, indirection.ToString()) - }; - - if (pointerType.PointedAtType.ContainingType != null) - { - partials.Add(await GetPartialForTypeAsync(pointerType.PointedAtType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); - } - - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); - } - - private static async Task GetPartialForArrayTypeAsync(IArrayTypeSymbol arrayType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) - { - var partials = new List(); - - var underlyingType = ChaseToUnderlyingType(arrayType); - - if (underlyingType.TypeKind == TypeKind.Dynamic) - { - partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.Name, "Object")); - } - else if (underlyingType.TypeKind != TypeKind.TypeParameter) - { - partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.Name, underlyingType.Name)); - } - - partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.ArrayRank, arrayType.Rank.ToString())); - partials.Add(await GetPartialForTypeAsync(arrayType.ElementType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); - - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); - } - - private static async Task GetPartialForTypeParameterSymbolAsync(ITypeParameterSymbol typeParameterSymbol, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) - { - if (typeParameterSymbol.TypeParameterKind == TypeParameterKind.Method) - { - return GraphNodeId.GetPartial(nodeName, - new GraphNodeIdCollection(false, - GraphNodeId.GetPartial(CodeGraphNodeIdName.Parameter, typeParameterSymbol.Ordinal.ToString()))); - } - else - { - var nodes = await GetPartialsForNamespaceAndTypeAsync(typeParameterSymbol, false, solution, cancellationToken).ConfigureAwait(false); - return GraphNodeId.GetPartial(nodeName, - new GraphNodeIdCollection(false, [.. nodes])); - } - } - - private static ITypeSymbol ChaseToUnderlyingType(ITypeSymbol symbol) - { - while (symbol.TypeKind == TypeKind.Array) - { - symbol = ((IArrayTypeSymbol)symbol).ElementType; - } - - while (symbol.TypeKind == TypeKind.Pointer) - { - symbol = ((IPointerTypeSymbol)symbol).PointedAtType; - } - - return symbol; - } - - public static async Task GetIdForMemberAsync(ISymbol member, Solution solution, CancellationToken cancellationToken) - { - var partials = new List(); - - partials.AddRange(await GetPartialsForNamespaceAndTypeAsync(member.ContainingType, true, solution, cancellationToken).ConfigureAwait(false)); - - var parameters = member.GetParameters(); - if (parameters.Any() || member.GetArity() > 0) - { - var memberPartials = new List - { - GraphNodeId.GetPartial(CodeQualifiedName.Name, member.MetadataName) - }; - - if (member.GetArity() > 0) - { - memberPartials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.GenericParameterCountIdentifier, member.GetArity().ToString())); - } - - if (parameters.Any()) - { - var parameterTypeIds = new List(); - foreach (var p in parameters) - { - var parameterIds = await GetPartialsForNamespaceAndTypeAsync(p.Type, true, solution, cancellationToken).ConfigureAwait(false); - var nodes = parameterIds.ToList(); - if (p.IsRefOrOut()) - { - nodes.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, ParamKind.Ref)); - } - - parameterTypeIds.Add(GraphNodeId.GetNested([.. nodes])); - } - - if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.Conversion) - { - // For explicit/implicit conversion operators, we need to include the return type in the method Id, - // because there can be several conversion operators with same parameters and only differ by return type. - // For example, - // - // public class Class1 - // { - // public static explicit (explicit) operator int(Class1 c) { ... } - // public static explicit (explicit) operator double(Class1 c) { ... } - // } - - var nodes = await GetPartialsForNamespaceAndTypeAsync(methodSymbol.ReturnType, true, solution, cancellationToken).ConfigureAwait(false); - var returnTypePartial = nodes.ToList(); - returnTypePartial.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, Microsoft.VisualStudio.GraphModel.CodeSchema.ParamKind.Return)); - - var returnCollection = GraphNodeId.GetNested([.. returnTypePartial]); - parameterTypeIds.Add(returnCollection); - } - - memberPartials.Add(GraphNodeId.GetArray( - CodeGraphNodeIdName.OverloadingParameters, - [.. parameterTypeIds])); - } - - partials.Add(GraphNodeId.GetPartial( - CodeGraphNodeIdName.Member, - MakeCollectionIfNecessary([.. memberPartials]))); - } - else - { - partials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Member, member.MetadataName)); - } - - return GraphNodeId.GetNested([.. partials]); - } - - private static object MakeCollectionIfNecessary(GraphNodeId[] array) - { - // Place the array of GraphNodeId's into the collection if necessary, so to make them appear in VS Properties Panel - if (array.Length > 1) - { - return new GraphNodeIdCollection(false, array); - } - - return GraphNodeId.GetNested(array); - } - - private static IAssemblySymbol GetContainingAssembly(ISymbol symbol) - { - if (symbol.ContainingAssembly != null) - { - return symbol.ContainingAssembly; - } - - if (symbol is not ITypeSymbol typeSymbol) - { - return null; - } - - var underlyingType = ChaseToUnderlyingType(typeSymbol); - if (Equals(typeSymbol, underlyingType)) - { - // when symbol is for dynamic type - return null; - } - - return GetContainingAssembly(underlyingType); - } - - private static async Task GetAssemblyFullPathAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - var containingAssembly = GetContainingAssembly(symbol); - return await GetAssemblyFullPathAsync(containingAssembly, solution, cancellationToken).ConfigureAwait(false); - } - - private static async Task GetAssemblyFullPathAsync(IAssemblySymbol containingAssembly, Solution solution, CancellationToken cancellationToken) - { - if (containingAssembly == null) - { - return null; - } - - var foundProject = solution.GetProject(containingAssembly, cancellationToken); - if (foundProject != null) - { - if (solution.Workspace is VisualStudioWorkspace) - { - // TODO: audit the OutputFilePath and whether this is bin or obj - if (!string.IsNullOrWhiteSpace(foundProject.OutputFilePath)) - { - return new Uri(foundProject.OutputFilePath, UriKind.RelativeOrAbsolute); - } - - return null; - } - } - else - { - // This symbol is not present in the source code, we need to resolve it from the references! - // If a MetadataReference returned by Compilation.GetMetadataReference(AssemblySymbol) has a path, we could use it. - foreach (var project in solution.Projects) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - if (compilation != null) - { - if (compilation.GetMetadataReference(containingAssembly) is PortableExecutableReference reference && !string.IsNullOrEmpty(reference.FilePath)) - { - return new Uri(reference.FilePath, UriKind.RelativeOrAbsolute); - } - } - } - } - - // If we are not in VS, return project.OutputFilePath as a reasonable fallback. - // For an example, it could be AdhocWorkspace for unit tests. - if (foundProject != null && !string.IsNullOrEmpty(foundProject.OutputFilePath)) - { - return new Uri(foundProject.OutputFilePath, UriKind.Absolute); - } - - return null; - } - - internal static async Task GetIdForAssemblyAsync(IAssemblySymbol assemblySymbol, Solution solution, CancellationToken cancellationToken) - { - var assembly = await GetAssemblyFullPathAsync(assemblySymbol, solution, cancellationToken).ConfigureAwait(false); - if (assembly != null) - { - var builder = new CodeQualifiedIdentifierBuilder(); - builder.Assembly = assembly; - return builder.ToQualifiedIdentifier(); - } - - return null; - } - - internal static async Task GetIdForParameterAsync(IParameterSymbol symbol, Solution solution, CancellationToken cancellationToken) - { - if (symbol.ContainingSymbol == null || - (symbol.ContainingSymbol.Kind != SymbolKind.Method && symbol.ContainingSymbol.Kind != SymbolKind.Property)) - { - // We are only support parameters inside methods or properties. - throw new ArgumentException("symbol"); - } - - var containingSymbol = symbol.ContainingSymbol; - if (containingSymbol is IMethodSymbol method && method.AssociatedSymbol != null && method.AssociatedSymbol.Kind == SymbolKind.Property) - { - var property = (IPropertySymbol)method.AssociatedSymbol; - if (property.Parameters.Any(static (p, symbol) => p.Name == symbol.Name, symbol)) - { - containingSymbol = property; - } - } - - var memberId = await GetIdForMemberAsync(containingSymbol, solution, cancellationToken).ConfigureAwait(false); - if (memberId != null) - { - return memberId + GraphNodeId.GetPartial(CodeGraphNodeIdName.Parameter, symbol.Name); - } - - return null; - } - - internal static async Task GetIdForLocalVariableAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - if (symbol.ContainingSymbol == null || - (symbol.ContainingSymbol.Kind != SymbolKind.Method && symbol.ContainingSymbol.Kind != SymbolKind.Property)) - { - // We are only support local variables inside methods or properties. - throw new ArgumentException("symbol"); - } - - var memberId = await GetIdForMemberAsync(symbol.ContainingSymbol, solution, cancellationToken).ConfigureAwait(false); - if (memberId != null) - { - var builder = new CodeQualifiedIdentifierBuilder(memberId); - builder.LocalVariable = symbol.Name; - builder.LocalVariableIndex = await GetLocalVariableIndexAsync(symbol, solution, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); - - return builder.ToQualifiedIdentifier(); - } - - return null; - } - - /// - /// Get the position of where a given local variable is defined considering there could be multiple variables with the same name in method body. - /// For example, in "int M() { { int goo = 0; ...} { int goo = 1; ...} }", - /// the return value for the first "goo" would be 0 while the value for the second one would be 1. - /// It will be used to create a node with LocalVariableIndex for a non-zero value. - /// In the above example, hence, a node id for the first "goo" would look like (... Member=M LocalVariable=bar) - /// but an id for the second "goo" would be (... Member=M LocalVariable=bar LocalVariableIndex=1) - /// - private static async Task GetLocalVariableIndexAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - var pos = 0; - - foreach (var reference in symbol.ContainingSymbol.DeclaringSyntaxReferences) - { - var currentNode = await reference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); - - // For VB, we have to ask its parent to get local variables within this method body - // since DeclaringSyntaxReferences return statement rather than enclosing block. - if (currentNode != null && symbol.Language == LanguageNames.VisualBasic) - { - currentNode = currentNode.Parent; - } - - if (currentNode != null) - { - var document = solution.GetDocument(currentNode.SyntaxTree); - if (document == null) - { - continue; - } - - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - foreach (var node in currentNode.DescendantNodes()) - { - var current = semanticModel.GetDeclaredSymbol(node, cancellationToken); - if (current is { Kind: SymbolKind.Local or SymbolKind.RangeVariable } && current.Name == symbol.Name) - { - if (!current.Equals(symbol)) - { - pos++; - } - else - { - return pos; - } - } - } - } - } - - throw ExceptionUtilities.Unreachable(); - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs b/src/VisualStudio/Core/Def/Progression/GraphProvider.cs deleted file mode 100644 index 1ab0c1d885dd7..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs +++ /dev/null @@ -1,387 +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.Generic; -using System.Collections.Immutable; -using System.ComponentModel.Composition; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.NavigateTo; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.CodeSchema; -using Microsoft.VisualStudio.GraphModel.Schemas; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Progression; -using Microsoft.VisualStudio.Shell; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -[GraphProvider(Name = nameof(RoslynGraphProvider), ProjectCapability = "(CSharp | VB)")] -internal sealed class RoslynGraphProvider : IGraphProvider -{ - private readonly IThreadingContext _threadingContext; - private readonly IGlyphService _glyphService; - private readonly IServiceProvider _serviceProvider; - private readonly IAsynchronousOperationListener _asyncListener; - private readonly Workspace _workspace; - private readonly Lazy _streamingPresenter; - private readonly GraphQueryManager _graphQueryManager; - - private bool _initialized = false; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RoslynGraphProvider( - IThreadingContext threadingContext, - IGlyphService glyphService, - SVsServiceProvider serviceProvider, - VisualStudioWorkspace workspace, - Lazy streamingPresenter, - IAsynchronousOperationListenerProvider listenerProvider) - { - _threadingContext = threadingContext; - _glyphService = glyphService; - _serviceProvider = serviceProvider; - _asyncListener = listenerProvider.GetListener(FeatureAttribute.GraphProvider); - _workspace = workspace; - _streamingPresenter = streamingPresenter; - _graphQueryManager = new GraphQueryManager(workspace, threadingContext, _asyncListener); - } - - private void EnsureInitialized() - { - if (_initialized) - { - return; - } - - var iconService = (IIconService)_serviceProvider.GetService(typeof(IIconService)); - IconHelper.Initialize(_glyphService, iconService); - _initialized = true; - } - - public static ImmutableArray GetGraphQueries(IGraphContext context) - { - using var _ = ArrayBuilder.GetInstance(out var graphQueries); - - if (context.Direction == GraphContextDirection.Self && context.RequestedProperties.Contains(DgmlNodeProperties.ContainsChildren)) - { - graphQueries.Add(new ContainsChildrenGraphQuery()); - } - - if (context.Direction == GraphContextDirection.Contains || - (context.Direction == GraphContextDirection.Target && context.LinkCategories.Contains(CodeLinkCategories.Contains))) - { - graphQueries.Add(new ContainsGraphQuery()); - } - - if (context.LinkCategories.Contains(CodeLinkCategories.InheritsFrom)) - { - if (context.Direction == GraphContextDirection.Target) - { - graphQueries.Add(new InheritsGraphQuery()); - } - else if (context.Direction == GraphContextDirection.Source) - { - graphQueries.Add(new InheritedByGraphQuery()); - } - } - - if (context.LinkCategories.Contains(CodeLinkCategories.SourceReferences)) - { - graphQueries.Add(new IsUsedByGraphQuery()); - } - - if (context.LinkCategories.Contains(CodeLinkCategories.Calls)) - { - if (context.Direction == GraphContextDirection.Target) - { - graphQueries.Add(new CallsGraphQuery()); - } - else if (context.Direction == GraphContextDirection.Source) - { - graphQueries.Add(new IsCalledByGraphQuery()); - } - } - - if (context.LinkCategories.Contains(CodeLinkCategories.Implements)) - { - if (context.Direction == GraphContextDirection.Target) - { - graphQueries.Add(new ImplementsGraphQuery()); - } - else if (context.Direction == GraphContextDirection.Source) - { - graphQueries.Add(new ImplementedByGraphQuery()); - } - } - - if (context.LinkCategories.Contains(RoslynGraphCategories.Overrides)) - { - if (context.Direction == GraphContextDirection.Source) - { - graphQueries.Add(new OverridesGraphQuery()); - } - else if (context.Direction == GraphContextDirection.Target) - { - graphQueries.Add(new OverriddenByGraphQuery()); - } - } - - if (context.Direction == GraphContextDirection.Custom) - { - var searchParameters = context.GetValue(typeof(ISolutionSearchParameters).GUID.ToString()); - - if (searchParameters != null) - { - // WARNING: searchParameters.SearchQuery returns an IVsSearchQuery object, which is a COM type. - // Therefore, it's probably best to grab the values we want now rather than get surprised by COM - // marshalling later. - // - // Create two queries. One to find results in normal docs, and one to find results in generated - // docs. That way if the generated docs take a long time we can still report the regular doc - // results immediately. - graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToDocumentSupport.RegularDocuments)); - graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToDocumentSupport.GeneratedDocuments)); - } - } - - return graphQueries.ToImmutableAndClear(); - } - - public void BeginGetGraphData(IGraphContext context) - { - EnsureInitialized(); - - var graphQueries = GetGraphQueries(context); - - // Perform the queries asynchronously in a fire-and-forget fashion. This helper will be responsible - // for always completing the context. AddQueriesAsync is `async`, so it always returns a task and will never - // bubble out an exception synchronously (so CompletesAsyncOperation is safe). - var asyncToken = _asyncListener.BeginAsyncOperation(nameof(BeginGetGraphData)); - _ = _graphQueryManager - .AddQueriesAsync(context, graphQueries, _threadingContext.DisposalToken) - .CompletesAsyncOperation(asyncToken); - } - - public IEnumerable GetCommands(IEnumerable nodes) - { - EnsureInitialized(); - - // Only nodes that explicitly state that they contain children (e.g., source files) and named types should - // be expandable. - if (nodes.Any(n => n.Properties.Any(p => p.Key == DgmlNodeProperties.ContainsChildren)) || - nodes.Any(n => IsAnySymbolKind(n, SymbolKind.NamedType))) - { - yield return new GraphCommand( - GraphCommandDefinition.Contains, - targetCategories: null, - linkCategories: [GraphCommonSchema.Contains], - trackChanges: true); - } - - // All graph commands below this point apply only to Roslyn-owned nodes. - if (!nodes.All(n => IsRoslynNode(n))) - { - yield break; - } - - // Only show 'Base Types' and 'Derived Types' on a class or interface. - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.NamedType) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Interface, TypeKind.Struct, TypeKind.Enum, TypeKind.Delegate))) - { - yield return new GraphCommand( - GraphCommandDefinition.BaseTypes, - targetCategories: null, - linkCategories: [CodeLinkCategories.InheritsFrom], - trackChanges: true); - - yield return new GraphCommand( - GraphCommandDefinition.DerivedTypes, - targetCategories: null, - linkCategories: [CodeLinkCategories.InheritsFrom], - trackChanges: true); - } - - // Only show 'Calls' on an applicable member in a class or struct - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property, SymbolKind.Field))) - { - yield return new GraphCommand( - GraphCommandDefinition.Calls, - targetCategories: null, - linkCategories: [CodeLinkCategories.Calls], - trackChanges: true); - } - - // Only show 'Is Called By' on an applicable member in a class or struct - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Struct))) - { - yield return new GraphCommand( - GraphCommandDefinition.IsCalledBy, - targetCategories: null, - linkCategories: [CodeLinkCategories.Calls], - trackChanges: true); - } - - // Show 'Is Used By' - yield return new GraphCommand( - GraphCommandDefinition.IsUsedBy, - targetCategories: [CodeNodeCategories.SourceLocation], - linkCategories: [CodeLinkCategories.SourceReferences], - trackChanges: true); - - // Show 'Implements' on a class or struct, or an applicable member in a class or struct. - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.NamedType) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Struct))) - { - yield return new GraphCommand( - s_implementsCommandDefinition, - targetCategories: null, - linkCategories: [CodeLinkCategories.Implements], - trackChanges: true); - } - - // Show 'Implements' on public, non-static members of a class or struct. Note: we should - // also show it on explicit interface impls in C#. - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Struct) && - !GetModifiers(n).IsStatic)) - { - if (nodes.Any(n => CheckAccessibility(n, Accessibility.Public) || - HasExplicitInterfaces(n))) - { - yield return new GraphCommand( - s_implementsCommandDefinition, - targetCategories: null, - linkCategories: [CodeLinkCategories.Implements], - trackChanges: true); - } - } - - // Show 'Implemented By' on an interface. - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.NamedType) && - IsAnyTypeKind(n, TypeKind.Interface))) - { - yield return new GraphCommand( - s_implementedByCommandDefinition, - targetCategories: null, - linkCategories: [CodeLinkCategories.Implements], - trackChanges: true); - } - - // Show 'Implemented By' on any member of an interface. - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property) && - IsAnyTypeKind(n, TypeKind.Interface))) - { - yield return new GraphCommand( - s_implementedByCommandDefinition, - targetCategories: null, - linkCategories: [CodeLinkCategories.Implements], - trackChanges: true); - } - - // Show 'Overrides' on any applicable member of a class or struct - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Struct) && - GetModifiers(n).IsOverride)) - { - yield return new GraphCommand( - s_overridesCommandDefinition, - targetCategories: null, - linkCategories: [RoslynGraphCategories.Overrides], - trackChanges: true); - } - - // Show 'Overridden By' on any applicable member of a class or struct - if (nodes.Any(n => IsAnySymbolKind(n, SymbolKind.Event, SymbolKind.Method, SymbolKind.Property) && - IsAnyTypeKind(n, TypeKind.Class, TypeKind.Struct) && - IsOverridable(n))) - { - yield return new GraphCommand( - s_overriddenByCommandDefinition, - targetCategories: null, - linkCategories: [RoslynGraphCategories.Overrides], - trackChanges: true); - } - } - - private static bool IsOverridable(GraphNode node) - { - var modifiers = GetModifiers(node); - return (modifiers.IsVirtual || modifiers.IsAbstract || modifiers.IsOverride) && - !modifiers.IsSealed; - } - - private static DeclarationModifiers GetModifiers(GraphNode node) - => (DeclarationModifiers)node[RoslynGraphProperties.SymbolModifiers]; - - private static bool CheckAccessibility(GraphNode node, Accessibility accessibility) - => node[RoslynGraphProperties.DeclaredAccessibility].Equals(accessibility); - - private static bool HasExplicitInterfaces(GraphNode node) - => ((IList)node[RoslynGraphProperties.ExplicitInterfaceImplementations]).Count > 0; - - private static bool IsRoslynNode(GraphNode node) - { - return node[RoslynGraphProperties.SymbolKind] != null - && node[RoslynGraphProperties.TypeKind] != null; - } - - private static bool IsAnySymbolKind(GraphNode node, params SymbolKind[] symbolKinds) - => symbolKinds.Any(k => k.Equals(node[RoslynGraphProperties.SymbolKind])); - - private static bool IsAnyTypeKind(GraphNode node, params TypeKind[] typeKinds) - => typeKinds.Any(k => node[RoslynGraphProperties.TypeKind].Equals(k)); - - private static readonly GraphCommandDefinition s_overridesCommandDefinition = - new("Overrides", EditorFeaturesResources.Overrides_, GraphContextDirection.Target, 700); - - private static readonly GraphCommandDefinition s_overriddenByCommandDefinition = - new("OverriddenBy", EditorFeaturesResources.Overridden_By, GraphContextDirection.Source, 700); - - private static readonly GraphCommandDefinition s_implementsCommandDefinition = - new("Implements", EditorFeaturesResources.Implements_, GraphContextDirection.Target, 600); - - private static readonly GraphCommandDefinition s_implementedByCommandDefinition = - new("ImplementedBy", EditorFeaturesResources.Implemented_By, GraphContextDirection.Source, 600); - - public T? GetExtension(GraphObject graphObject, T previous) where T : class - { - if (graphObject is GraphNode graphNode) - { - // If this is not a Roslyn node, bail out. - if (graphNode.GetValue(RoslynGraphProperties.ContextProjectId) == null) - return null; - - // Has to have at least a symbolid, or source location to navigate to. - if (graphNode.GetValue(RoslynGraphProperties.SymbolId) == null && - graphNode.GetValue(CodeNodeProperties.SourceLocation).FileName == null) - { - return null; - } - - if (typeof(T) == typeof(IGraphNavigateToItem)) - return new GraphNavigatorExtension(_threadingContext, _workspace, _streamingPresenter) as T; - - if (typeof(T) == typeof(IGraphFormattedLabel)) - return new GraphFormattedLabelExtension() as T; - } - - return null; - } - - public Graph? Schema - { - get { return null; } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs deleted file mode 100644 index b8c4e03ae4373..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs +++ /dev/null @@ -1,67 +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 System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class CallsGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_Calls, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol != null) - { - foreach (var newSymbol in await GetCalledMethodSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var newNode = await graphBuilder.AddNodeAsync(newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(node, CodeLinkCategories.Calls, newNode, cancellationToken); - } - } - } - - return graphBuilder; - } - - private static async Task> GetCalledMethodSymbolsAsync( - ISymbol symbol, Solution solution, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var symbols); - - foreach (var reference in symbol.DeclaringSyntaxReferences) - { - var semanticModel = await solution.GetDocument(reference.SyntaxTree).GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - foreach (var syntaxNode in (await reference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false)).DescendantNodes()) - { - cancellationToken.ThrowIfCancellationRequested(); - - var newSymbol = semanticModel.GetSymbolInfo(syntaxNode, cancellationToken).Symbol; - if (newSymbol != null && newSymbol is IMethodSymbol && - (newSymbol.CanBeReferencedByName || ((IMethodSymbol)newSymbol).MethodKind == MethodKind.Constructor)) - { - symbols.Add(newSymbol); - } - } - } - - return symbols.ToImmutableAndClear(); - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsChildrenGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsChildrenGraphQuery.cs deleted file mode 100644 index c116f6dcab525..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsChildrenGraphQuery.cs +++ /dev/null @@ -1,83 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class ContainsChildrenGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - if (!cancellationToken.IsCancellationRequested) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol != null) - { - var containsChildren = SymbolContainment.GetContainedSymbols(symbol).Any(); - graphBuilder.AddDeferredPropertySet( - node, DgmlNodeProperties.ContainsChildren, containsChildren, cancellationToken); - } - else if (node.HasCategory(CodeNodeCategories.File)) - { - var document = graphBuilder.GetContextDocument(node, cancellationToken); - if (document != null) - { - var childNodes = await SymbolContainment.GetContainedSyntaxNodesAsync(document, cancellationToken).ConfigureAwait(false); - graphBuilder.AddDeferredPropertySet( - node, DgmlNodeProperties.ContainsChildren, childNodes.Any(), cancellationToken); - } - else - { - var uri = node.Id.GetNestedValueByName(CodeGraphNodeIdName.File); - if (uri != null) - { - // Since a solution load is not yet completed, there is no document available to answer this query. - // The solution explorer presumes that if somebody doesn't answer for a file, they never will. - // See Providers\GraphContextAttachedCollectionSource.cs for more. Therefore we should answer by setting - // ContainsChildren property to either true or false, so any following updates will be tractable. - // We will set it to false since the solution explorer assumes the default for this query response is 'false'. - - // Todo: we may need fallback to check if this node actually represents a C# or VB language - // even when its extension fails to say so. One option would be to call DTEWrapper.IsRegisteredForLangService, - // which may not be called here however since deadlock could happen. - - // The Uri returned by `GetNestedValueByName()` above isn't necessarily absolute and the `OriginalString` is - // the only property that doesn't throw if the UriKind is relative, so `OriginalString` must be used instead - // of `AbsolutePath`. - var path = uri.OriginalString; - - // Recorded in https://github.com/dotnet/roslyn/issues/27805, we - // have seen crashes because the URI in the graph node has the - // following form, including the quotes (which are invalid path - // characters): - // C:\path\to\"some path\App.config" - // So we avoid calling Path.GetExtension here. Alternatively, we - // could check for illegal path characters directly first, but then - // that check would actually happen twice because GetExtension will - // also perform the check. - if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".vb", StringComparison.OrdinalIgnoreCase)) - { - graphBuilder.AddDeferredPropertySet( - node, DgmlNodeProperties.ContainsChildren, value: false, cancellationToken); - } - } - } - } - } - } - - return graphBuilder; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsGraphQuery.cs deleted file mode 100644 index f8fa2b2f5dbb6..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ContainsGraphQuery.cs +++ /dev/null @@ -1,67 +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.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class ContainsGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_Contains, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - var nodesToProcess = context.InputNodes; - - for (var depth = 0; depth < context.LinkDepth; depth++) - { - // This is the list of nodes we created and will process - var newNodes = new HashSet(); - - foreach (var node in nodesToProcess) - { - cancellationToken.ThrowIfCancellationRequested(); - - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol != null) - { - foreach (var newSymbol in SymbolContainment.GetContainedSymbols(symbol)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var newNode = await graphBuilder.AddNodeAsync( - newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode, cancellationToken); - } - } - else if (node.HasCategory(CodeNodeCategories.File)) - { - var document = graphBuilder.GetContextDocument(node, cancellationToken); - if (document != null) - { - foreach (var newSymbol in await SymbolContainment.GetContainedSymbolsAsync(document, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var newNode = await graphBuilder.AddNodeAsync( - newSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(node, GraphCommonSchema.Contains, newNode, cancellationToken); - } - } - } - } - - nodesToProcess = newNodes; - } - - return graphBuilder; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementedByGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementedByGraphQuery.cs deleted file mode 100644 index d6b6967233b7b..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementedByGraphQuery.cs +++ /dev/null @@ -1,46 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class ImplementedByGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.GraphQuery_ImplementedBy, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol is INamedTypeSymbol or - IMethodSymbol or - IPropertySymbol or - IEventSymbol) - { - var implementations = await SymbolFinder.FindImplementationsAsync(symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); - - foreach (var implementation in implementations) - { - var symbolNode = await graphBuilder.AddNodeAsync( - implementation, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink( - symbolNode, CodeLinkCategories.Implements, node, cancellationToken); - } - } - } - - return graphBuilder; - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementsGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementsGraphQuery.cs deleted file mode 100644 index d165a54688759..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ImplementsGraphQuery.cs +++ /dev/null @@ -1,56 +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.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class ImplementsGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.GraphQuery_Implements, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol is INamedTypeSymbol namedType) - { - var implementedSymbols = ImmutableArray.CastUp(namedType.AllInterfaces); - - await AddImplementedSymbolsAsync(graphBuilder, node, implementedSymbols, cancellationToken).ConfigureAwait(false); - } - else if (symbol is IMethodSymbol or - IPropertySymbol or - IEventSymbol) - { - var implements = await SymbolFinder.FindImplementedInterfaceMembersArrayAsync(symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); - await AddImplementedSymbolsAsync(graphBuilder, node, implements, cancellationToken).ConfigureAwait(false); - } - } - - return graphBuilder; - } - } - - private static async Task AddImplementedSymbolsAsync( - GraphBuilder graphBuilder, GraphNode node, ImmutableArray implementedSymbols, CancellationToken cancellationToken) - { - foreach (var interfaceType in implementedSymbols) - { - var interfaceTypeNode = await graphBuilder.AddNodeAsync( - interfaceType, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(node, CodeLinkCategories.Implements, interfaceTypeNode, cancellationToken); - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritedByGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritedByGraphQuery.cs deleted file mode 100644 index 3652bb8972666..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritedByGraphQuery.cs +++ /dev/null @@ -1,58 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class InheritedByGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_InheritedBy, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol is not INamedTypeSymbol namedType) - continue; - - if (namedType.TypeKind == TypeKind.Class) - { - var derivedTypes = await SymbolFinder.FindDerivedClassesArrayAsync( - namedType, solution, transitive: false, cancellationToken: cancellationToken).ConfigureAwait(false); - foreach (var derivedType in derivedTypes) - { - var symbolNode = await graphBuilder.AddNodeAsync( - derivedType, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node, cancellationToken); - } - } - else if (namedType.TypeKind == TypeKind.Interface) - { - var implementingClassesAndStructs = await SymbolFinder.FindImplementationsArrayAsync( - namedType, solution, transitive: false, cancellationToken: cancellationToken).ConfigureAwait(false); - var derivedInterfaces = await SymbolFinder.FindDerivedInterfacesArrayAsync( - namedType, solution, transitive: false, cancellationToken: cancellationToken).ConfigureAwait(false); - foreach (var derivedType in implementingClassesAndStructs.ConcatFast(derivedInterfaces)) - { - var symbolNode = await graphBuilder.AddNodeAsync( - derivedType, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, CodeLinkCategories.InheritsFrom, node, cancellationToken); - } - } - } - - return graphBuilder; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritsGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritsGraphQuery.cs deleted file mode 100644 index 3bd99611b9691..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/InheritsGraphQuery.cs +++ /dev/null @@ -1,60 +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.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class InheritsGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_Inherits, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - var nodesToProcess = context.InputNodes; - - for (var depth = 0; depth < context.LinkDepth; depth++) - { - // This is the list of nodes we created and will process - var newNodes = new HashSet(); - - foreach (var node in nodesToProcess) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol is INamedTypeSymbol namedType) - { - if (namedType.BaseType != null) - { - var baseTypeNode = await graphBuilder.AddNodeAsync( - namedType.BaseType, relatedNode: node, cancellationToken).ConfigureAwait(false); - newNodes.Add(baseTypeNode); - graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode, cancellationToken); - } - else if (namedType.TypeKind == TypeKind.Interface && !namedType.OriginalDefinition.AllInterfaces.IsEmpty) - { - foreach (var baseNode in namedType.OriginalDefinition.AllInterfaces.Distinct()) - { - var baseTypeNode = await graphBuilder.AddNodeAsync( - baseNode, relatedNode: node, cancellationToken).ConfigureAwait(false); - newNodes.Add(baseTypeNode); - graphBuilder.AddLink(node, CodeLinkCategories.InheritsFrom, baseTypeNode, cancellationToken); - } - } - } - } - - nodesToProcess = newNodes; - } - - return graphBuilder; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/IsCalledByGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/IsCalledByGraphQuery.cs deleted file mode 100644 index 837cc7c5a6e24..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/IsCalledByGraphQuery.cs +++ /dev/null @@ -1,43 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class IsCalledByGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.GraphQuery_IsCalledBy, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol != null) - { - var callers = await SymbolFinder.FindCallersAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - - foreach (var caller in callers.Where(c => c.IsDirect)) - { - var callerNode = await graphBuilder.AddNodeAsync( - caller.CallingSymbol, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(callerNode, CodeLinkCategories.Calls, node, cancellationToken); - } - } - } - - return graphBuilder; - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/IsUsedByGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/IsUsedByGraphQuery.cs deleted file mode 100644 index 5709c6d9c0818..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/IsUsedByGraphQuery.cs +++ /dev/null @@ -1,74 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.VisualStudio.GraphModel.Schemas; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class IsUsedByGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.GraphQuery_IsUsedBy, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - var references = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - - foreach (var reference in references) - { - var referencedSymbol = reference.Definition; - var projectId = graphBuilder.GetContextProject(node, cancellationToken).Id; - - var allLocations = referencedSymbol.Locations.Concat(reference.Locations.Select(r => r.Location)) - .Where(l => l != null && l.IsInSource); - - foreach (var location in allLocations) - { - var locationNode = GetLocationNode(location, context, projectId, cancellationToken); - if (locationNode != null) - graphBuilder.AddLink(node, CodeLinkCategories.SourceReferences, locationNode, cancellationToken); - } - } - } - - return graphBuilder; - } - } - - private static GraphNode? GetLocationNode(Location location, IGraphContext context, ProjectId projectId, CancellationToken cancellationToken) - { - var span = location.GetLineSpan(); - if (location.SourceTree == null) - return null; - - var lineText = location.SourceTree.GetText(cancellationToken).Lines[span.StartLinePosition.Line].ToString(); - var filePath = location.SourceTree.FilePath; - var sourceLocation = GraphBuilder.TryCreateSourceLocation(filePath, span.Span); - if (sourceLocation == null) - return null; - - var label = string.Format("{0} ({1}, {2}): {3}", - System.IO.Path.GetFileName(filePath), - span.StartLinePosition.Line + 1, - span.StartLinePosition.Character + 1, - lineText.TrimStart()); - var locationNode = context.Graph.Nodes.GetOrCreate(sourceLocation.Value.CreateGraphNodeId(), label, CodeNodeCategories.SourceLocation); - locationNode[CodeNodeProperties.SourceLocation] = sourceLocation.Value; - locationNode[RoslynGraphProperties.ContextProjectId] = projectId; - locationNode[DgmlNodeProperties.Icon] = IconHelper.GetIconName("Reference", Accessibility.NotApplicable); - - return locationNode; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/OverriddenByGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/OverriddenByGraphQuery.cs deleted file mode 100644 index ade96133d757e..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/OverriddenByGraphQuery.cs +++ /dev/null @@ -1,39 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class OverriddenByGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_OverriddenBy, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol != null) - { - var overriddenMember = symbol.GetOverriddenMember(); - if (overriddenMember != null) - { - var symbolNode = await graphBuilder.AddNodeAsync( - overriddenMember, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(node, RoslynGraphCategories.Overrides, symbolNode, cancellationToken); - } - } - } - - return graphBuilder; - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/OverridesGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/OverridesGraphQuery.cs deleted file mode 100644 index 668e8915ba742..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/OverridesGraphQuery.cs +++ /dev/null @@ -1,41 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed class OverridesGraphQuery : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.GraphQuery_Overrides, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) - { - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - - foreach (var node in context.InputNodes) - { - var symbol = graphBuilder.GetSymbol(node, cancellationToken); - if (symbol is IMethodSymbol or - IPropertySymbol or - IEventSymbol) - { - var overrides = await SymbolFinder.FindOverridesAsync(symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); - foreach (var o in overrides) - { - var symbolNode = await graphBuilder.AddNodeAsync(o, relatedNode: node, cancellationToken).ConfigureAwait(false); - graphBuilder.AddLink(symbolNode, RoslynGraphCategories.Overrides, node, cancellationToken); - } - } - } - - return graphBuilder; - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs deleted file mode 100644 index ec9b40e83ed8e..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs +++ /dev/null @@ -1,56 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.GraphModel; -using Microsoft.CodeAnalysis.NavigateTo; -using System.Collections.Immutable; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed partial class SearchGraphQuery -{ - private sealed class ProgressionNavigateToSearchCallback : INavigateToSearchCallback - { - private readonly Solution _solution; - private readonly IGraphContext _context; - private readonly GraphBuilder _graphBuilder; - - public ProgressionNavigateToSearchCallback(Solution solution, IGraphContext context, GraphBuilder graphBuilder) - { - _solution = solution; - _context = context; - _graphBuilder = graphBuilder; - } - - public void Done(bool isFullyLoaded) - { - // Do nothing here. Even though the navigate to search completed, we still haven't passed any - // information along to progression. That will happen in GraphQueryManager.PopulateContextGraphAsync - } - - public void ReportProgress(int current, int maximum) - => _context.ReportProgress(current, maximum, null); - - public void ReportIncomplete() - { - } - - public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) - { - foreach (var result in results) - { - var node = await _graphBuilder.CreateNodeAsync(_solution, result, cancellationToken).ConfigureAwait(false); - if (node != null) - { - // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. - lock (this) - _context.OutputNodes.Add(node); - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs deleted file mode 100644 index 526dd59ef0bd8..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs +++ /dev/null @@ -1,55 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.NavigateTo; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal sealed partial class SearchGraphQuery( - string searchPattern, - NavigateToDocumentSupport searchScope) : IGraphQuery -{ - public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) - { - using var _ = Logger.LogBlock(FunctionId.GraphQuery_Search, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - - var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - var callback = new ProgressionNavigateToSearchCallback(solution, context, graphBuilder); - - // We have a specialized host for progression vs normal nav-to. Progression itself will tell the client if - // the project is fully loaded or not. But after that point, the client will be considered fully loaded and - // results should reflect that. So we create a host here that will always give complete results once the - // solution is loaded and not give cached/incomplete results at that point. - var statusService = solution.Services.GetRequiredService(); - var isFullyLoaded = await statusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); - var host = new SearchGraphQueryNavigateToSearchHost(isFullyLoaded); - - var searcher = NavigateToSearcher.Create( - solution, - callback, - searchPattern, - NavigateToUtilities.GetKindsProvided(solution), - host); - - await searcher.SearchAsync(NavigateToSearchScope.Solution, searchScope, cancellationToken).ConfigureAwait(false); - - return graphBuilder; - } - - private sealed class SearchGraphQueryNavigateToSearchHost(bool isFullyLoaded) : INavigateToSearcherHost - { - public INavigateToSearchService? GetNavigateToSearchService(Project project) - => project.GetLanguageService(); - - public ValueTask IsFullyLoadedAsync(CancellationToken cancellationToken) - => new(isFullyLoaded); - } -} diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs b/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs deleted file mode 100644 index e5c81b32345f2..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs +++ /dev/null @@ -1,169 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Threading; -using Microsoft.VisualStudio.GraphModel; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -using Workspace = Microsoft.CodeAnalysis.Workspace; - -internal sealed class GraphQueryManager -{ - private readonly Workspace _workspace; - - /// - /// This gate locks manipulation of . - /// - private readonly object _gate = new(); - private ImmutableArray<(WeakReference context, ImmutableArray queries)> _trackedQueries = []; - - private readonly AsyncBatchingWorkQueue _updateQueue; - - internal GraphQueryManager( - Workspace workspace, - IThreadingContext threadingContext, - IAsynchronousOperationListener asyncListener) - { - _workspace = workspace; - - // Update any existing live/tracking queries 1.5 seconds after every workspace changes. - _updateQueue = new AsyncBatchingWorkQueue( - DelayTimeSpan.Idle, - UpdateExistingQueriesAsync, - asyncListener, - threadingContext.DisposalToken); - - // Note: this ends up always listening for workspace events, even if we have no active 'live' queries that - // need updating. But this should basically be practically no cost. The queue just holds a single item - // indicating a change happened. And when UpdateExistingQueriesAsync fires, it will just see that there are - // no live queries and immediately return. So it's just simple to do things this way instead of trying to - // have state management where we try to decide if we should listen or not. - _ = _workspace.RegisterWorkspaceChangedHandler((_) => _updateQueue.AddWork()); - } - - public async Task AddQueriesAsync(IGraphContext context, ImmutableArray graphQueries, CancellationToken disposalToken) - { - try - { - var solution = _workspace.CurrentSolution; - - // Perform the actual graph query first. - await PopulateContextGraphAsync(solution, context, graphQueries, disposalToken).ConfigureAwait(false); - - // If this context would like to be continuously updated with live changes to this query, then add the - // tracked query to our tracking list, keeping it alive as long as those is keeping the context alive. - if (context.TrackChanges) - { - lock (_gate) - { - _trackedQueries = _trackedQueries.Add((new WeakReference(context), graphQueries)); - } - } - } - finally - { - // We want to ensure that no matter what happens, this initial context is completed - context.OnCompleted(); - } - } - - private async ValueTask UpdateExistingQueriesAsync(CancellationToken disposalToken) - { - ImmutableArray<(IGraphContext context, ImmutableArray queries)> liveQueries; - lock (_gate) - { - // First, grab the set of contexts that are still live. We'll update them below. - liveQueries = _trackedQueries - .SelectAsArray(t => (context: t.context.GetTarget(), t.queries)) - .WhereAsArray(t => t.context != null)!; - - // Next, clear out any context that are now no longer alive (or have been canceled). We no longer care - // about these. - _trackedQueries = _trackedQueries.RemoveAll(t => - { - var target = t.context.GetTarget(); - return target is null || target.CancelToken.IsCancellationRequested; - }); - } - - var solution = _workspace.CurrentSolution; - - // Update all the live queries in parallel. - var tasks = liveQueries.Select(t => PopulateContextGraphAsync(solution, t.context, t.queries, disposalToken)).ToArray(); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - /// - /// Populate the graph of the context with the values for the given Solution. - /// - private static async Task PopulateContextGraphAsync( - Solution solution, - IGraphContext context, - ImmutableArray graphQueries, - CancellationToken disposalToken) - { - try - { - // Compute all queries in parallel. Then as each finishes, update the graph. - - // Cancel the work if either the context wants us to cancel, or our host is getting disposed. - using var source = CancellationTokenSource.CreateLinkedTokenSource(context.CancelToken, disposalToken); - var cancellationToken = source.Token; - - var tasks = graphQueries.Select(q => Task.Run(() => q.GetGraphAsync(solution, context, cancellationToken), cancellationToken)).ToHashSet(); - - var first = true; - while (tasks.Count > 0) - { - cancellationToken.ThrowIfCancellationRequested(); - - var completedTask = await Task.WhenAny(tasks).ConfigureAwait(false); - tasks.Remove(completedTask); - - // if this is the first task finished, clear out the existing results and add all the new - // results as a single transaction. Doing this as a single transaction is vital for - // solution-explorer as that is how it can map the prior elements to the new ones, preserving the - // view-state (like ensuring the same nodes stay collapsed/expanded). - // - // As additional queries finish, add those results in after without clearing the results of the - // prior queries. - - var graphBuilder = await completedTask.ConfigureAwait(false); - using var transaction = new GraphTransactionScope(); - - if (first) - { - first = false; - context.Graph.Links.Clear(); - } - - graphBuilder.ApplyToGraph(context.Graph, cancellationToken); - context.OutputNodes.AddAll(graphBuilder.GetCreatedNodes(cancellationToken)); - - transaction.Complete(); - } - } - catch (OperationCanceledException) - { - // Don't bubble this cancellation outwards. The queue's cancellation token is mixed with the context's - // token to make a final token that controls the work we do above. We don't want any of the wrong - // cancellations leaking outwards. - } - catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, ErrorSeverity.Diagnostic)) - { - throw ExceptionUtilities.Unreachable(); - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/IGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/IGraphQuery.cs deleted file mode 100644 index c5a9cf73b70d6..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/IGraphQuery.cs +++ /dev/null @@ -1,15 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal interface IGraphQuery -{ - Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken); -} diff --git a/src/VisualStudio/Core/Def/Progression/IProgressionLanguageService.cs b/src/VisualStudio/Core/Def/Progression/IProgressionLanguageService.cs deleted file mode 100644 index 1b548f7e86877..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/IProgressionLanguageService.cs +++ /dev/null @@ -1,17 +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.Collections.Generic; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal interface IProgressionLanguageService : ILanguageService -{ - IEnumerable GetTopLevelNodesFromDocument(SyntaxNode root, CancellationToken cancellationToken); - string GetDescriptionForSymbol(ISymbol symbol, bool includeContainingSymbol); - string GetLabelForSymbol(ISymbol symbol, bool includeContainingSymbol); -} diff --git a/src/VisualStudio/Core/Def/Progression/IconHelper.cs b/src/VisualStudio/Core/Def/Progression/IconHelper.cs deleted file mode 100644 index 102705aa3875a..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/IconHelper.cs +++ /dev/null @@ -1,87 +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.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Progression; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal static class IconHelper -{ - private static string GetIconName(string groupName, string itemName) - => string.Format("Microsoft.VisualStudio.{0}.{1}", groupName, itemName); - - public static string GetIconName(string groupName, Accessibility symbolAccessibility) - { - switch (symbolAccessibility) - { - case Accessibility.Private: - return GetIconName(groupName, "Private"); - - case Accessibility.Protected: - case Accessibility.ProtectedAndInternal: - case Accessibility.ProtectedOrInternal: - return GetIconName(groupName, "Protected"); - - case Accessibility.Internal: - return GetIconName(groupName, "Internal"); - - case Accessibility.Public: - case Accessibility.NotApplicable: - return GetIconName(groupName, "Public"); - - default: - throw new ArgumentException(); - } - } - - public static void Initialize(IGlyphService glyphService, IIconService iconService) - { - var supportedGlyphGroups = new Dictionary - { - { StandardGlyphGroup.GlyphGroupError, "Error" }, - { StandardGlyphGroup.GlyphGroupDelegate, "Delegate" }, - { StandardGlyphGroup.GlyphGroupEnum, "Enum" }, - { StandardGlyphGroup.GlyphGroupStruct, "Struct" }, - { StandardGlyphGroup.GlyphGroupClass, "Class" }, - { StandardGlyphGroup.GlyphGroupInterface, "Interface" }, - { StandardGlyphGroup.GlyphGroupModule, "Module" }, - { StandardGlyphGroup.GlyphGroupConstant, "Constant" }, - { StandardGlyphGroup.GlyphGroupEnumMember, "EnumMember" }, - { StandardGlyphGroup.GlyphGroupEvent, "Event" }, - { StandardGlyphGroup.GlyphExtensionMethodPrivate, "ExtensionMethodPrivate" }, - { StandardGlyphGroup.GlyphExtensionMethodProtected, "ExtensionMethodProtected" }, - { StandardGlyphGroup.GlyphExtensionMethodInternal, "ExtensionMethodInternal" }, - { StandardGlyphGroup.GlyphExtensionMethod, "ExtensionMethod" }, - { StandardGlyphGroup.GlyphGroupMethod, "Method" }, - { StandardGlyphGroup.GlyphGroupProperty, "Property" }, - { StandardGlyphGroup.GlyphGroupField, "Field" }, - { StandardGlyphGroup.GlyphGroupOperator, "Operator" }, - { StandardGlyphGroup.GlyphReference, "Reference" } - }; - - var supportedGlyphItems = new Dictionary - { - { StandardGlyphItem.GlyphItemPrivate, "Private" }, - { StandardGlyphItem.GlyphItemProtected, "Protected" }, - { StandardGlyphItem.GlyphItemInternal, "Internal" }, - { StandardGlyphItem.GlyphItemPublic, "Public" }, - { StandardGlyphItem.GlyphItemFriend, "Friend" } - }; - - foreach (var groupKvp in supportedGlyphGroups) - { - foreach (var itemKvp in supportedGlyphItems) - { - var iconName = GetIconName(groupKvp.Value, itemKvp.Value); - var localGroup = groupKvp.Key; - var localItem = itemKvp.Key; - iconService.AddIcon(iconName, iconName, () => glyphService.GetGlyph(localGroup, localItem)); - } - } - } -} diff --git a/src/VisualStudio/Core/Def/Progression/RoslynGraphCategories.cs b/src/VisualStudio/Core/Def/Progression/RoslynGraphCategories.cs deleted file mode 100644 index 040f6671f1af3..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/RoslynGraphCategories.cs +++ /dev/null @@ -1,23 +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 Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal static class RoslynGraphCategories -{ - public static readonly GraphSchema Schema; - - public static readonly GraphCategory Overrides; - - static RoslynGraphCategories() - { - Schema = RoslynGraphProperties.Schema; - - Overrides = Schema.Categories.AddNewCategory( - "Overrides", - () => new GraphMetadata(GraphMetadataOptions.Sharable)); - } -} diff --git a/src/VisualStudio/Core/Def/Progression/RoslynGraphProperties.cs b/src/VisualStudio/Core/Def/Progression/RoslynGraphProperties.cs deleted file mode 100644 index 4cdf37c577932..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/RoslynGraphProperties.cs +++ /dev/null @@ -1,141 +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.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.VisualStudio.GraphModel; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal static class RoslynGraphProperties -{ - public static readonly GraphSchema Schema; - - /// - /// A graph property that holds the SymbolId of the symbol. - /// - public static readonly GraphProperty SymbolId; - - /// - /// A graph property that holds the ProjectId where you can find the symbol. Note this is - /// not strictly the project that defines the symbol in the case the symbol is from metadata. - /// It's simply a project that has a compilation which you can use to get to the symbol. - /// - public static readonly GraphProperty ContextProjectId; - - /// - /// A graph property that holds the DocumentId where you can find the symbol. This is used - /// to distinguish between multiple locations for partial types. This will only exist - /// for symbols in source that have partial implementations. - /// - public static readonly GraphProperty ContextDocumentId; - - /// - /// A graph property to hold the label we have generated for the node. - /// - public static readonly GraphProperty Label; - - /// - /// A graph property to hold the formatted label we have generated for the node. - /// - public static readonly GraphProperty FormattedLabelWithoutContainingSymbol; - - /// - /// A graph property to hold the formatted label that has the containing symbol name. - /// - public static readonly GraphProperty FormattedLabelWithContainingSymbol; - - /// - /// A graph property to hold the description we have generated for the node. - /// - public static readonly GraphProperty Description; - - /// - /// A graph property to hold the description that has the containing symbol name. - /// - public static readonly GraphProperty DescriptionWithContainingSymbol; - - public static readonly GraphProperty SymbolKind; - public static readonly GraphProperty TypeKind; - public static readonly GraphProperty MethodKind; - public static readonly GraphProperty DeclaredAccessibility; - public static readonly GraphProperty SymbolModifiers; - public static readonly GraphProperty ExplicitInterfaceImplementations; - - static RoslynGraphProperties() - { - Schema = new GraphSchema("Roslyn"); - - SymbolKind = Schema.Properties.AddNewProperty( - id: "SymbolKind", - dataType: typeof(SymbolKind), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - TypeKind = Schema.Properties.AddNewProperty( - id: "TypeKind", - dataType: typeof(TypeKind), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - MethodKind = Schema.Properties.AddNewProperty( - id: "MethodKind", - dataType: typeof(MethodKind), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - DeclaredAccessibility = Schema.Properties.AddNewProperty( - id: "DeclaredAccessibility", - dataType: typeof(Accessibility), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - SymbolModifiers = Schema.Properties.AddNewProperty( - id: "SymbolModifiers", - dataType: typeof(DeclarationModifiers), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - ExplicitInterfaceImplementations = Schema.Properties.AddNewProperty( - id: "ExplicitInterfaceImplementations", - dataType: typeof(IList), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - SymbolId = Schema.Properties.AddNewProperty( - id: "SymbolId", - dataType: typeof(SymbolKey?), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - ContextProjectId = Schema.Properties.AddNewProperty( - id: "ContextProjectId", - dataType: typeof(ProjectId), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - ContextDocumentId = Schema.Properties.AddNewProperty( - id: "ContextDocumentId", - dataType: typeof(DocumentId), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - Label = Schema.Properties.AddNewProperty( - id: "Label", - dataType: typeof(string), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - FormattedLabelWithoutContainingSymbol = Schema.Properties.AddNewProperty( - id: "FormattedLabelWithoutContainingSymbol", - dataType: typeof(string), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - FormattedLabelWithContainingSymbol = Schema.Properties.AddNewProperty( - id: "FormattedLabelWithContainingSymbol", - dataType: typeof(string), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - Description = Schema.Properties.AddNewProperty( - id: "Description", - dataType: typeof(string), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - - DescriptionWithContainingSymbol = Schema.Properties.AddNewProperty( - id: "DescriptionWithContainingSymbol", - dataType: typeof(string), - callback: () => new GraphMetadata(options: GraphMetadataOptions.Sharable | GraphMetadataOptions.Removable)); - } -} diff --git a/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs b/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs deleted file mode 100644 index fb56cc3f795b9..0000000000000 --- a/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs +++ /dev/null @@ -1,91 +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 System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; - -internal static class SymbolContainment -{ - public static async Task> GetContainedSyntaxNodesAsync(Document document, CancellationToken cancellationToken) - { - var progressionLanguageService = document.GetLanguageService(); - if (progressionLanguageService == null) - return []; - - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - return progressionLanguageService.GetTopLevelNodesFromDocument(root, cancellationToken); - } - - public static async Task> GetContainedSymbolsAsync(Document document, CancellationToken cancellationToken) - { - var syntaxNodes = await GetContainedSyntaxNodesAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var symbols); - - foreach (var syntaxNode in syntaxNodes) - { - cancellationToken.ThrowIfCancellationRequested(); - - var symbol = semanticModel.GetDeclaredSymbol(syntaxNode, cancellationToken); - if (symbol != null && - !string.IsNullOrEmpty(symbol.Name) && - IsTopLevelSymbol(symbol)) - { - symbols.Add(symbol); - } - } - - return symbols.ToImmutableAndClear(); - } - - private static bool IsTopLevelSymbol(ISymbol symbol) - { - switch (symbol.Kind) - { - case SymbolKind.NamedType: - case SymbolKind.Method: - case SymbolKind.Field: - case SymbolKind.Property: - case SymbolKind.Event: - return true; - - default: - return false; - } - } - - public static IEnumerable GetContainedSymbols(ISymbol symbol) - { - if (symbol is INamedTypeSymbol namedType) - { - foreach (var member in namedType.GetMembers()) - { - if (member.IsImplicitlyDeclared) - { - continue; - } - - if (member is IMethodSymbol method && method.AssociatedSymbol != null) - { - continue; - } - - if (!string.IsNullOrEmpty(member.Name)) - { - yield return member; - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs b/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs index 6c7a4349d66ee..7013912097a7e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs @@ -340,7 +340,7 @@ public void Dispose() return; } - var runningDocumentTableForEvents = (IVsRunningDocumentTable)_runningDocumentTable; + var runningDocumentTableForEvents = (IVsRunningDocumentTable)_runningDocumentTable.Value; runningDocumentTableForEvents.UnadviseRunningDocTableEvents(_runningDocumentTableEventsCookie); _runningDocumentTableEventsCookie = 0; diff --git a/src/VisualStudio/Core/Def/Storage/FileDownloader.cs b/src/VisualStudio/Core/Def/Storage/FileDownloader.cs index 5688f09332169..8a4a220742c64 100644 --- a/src/VisualStudio/Core/Def/Storage/FileDownloader.cs +++ b/src/VisualStudio/Core/Def/Storage/FileDownloader.cs @@ -30,40 +30,9 @@ public IFileDownloader CreateClient(string hostId, string serverPath, int pollin private FileDownloader(RemoteControlClient client) => _client = client; -#if NET - - /// - /// The netcore version of doesn't support ReturnStale. It will download the file - /// (on a separate thread), but then not cache it because it doesn't have access to the normal IE component that - /// does proper header reading/caching. Then, when we call in to read the file, we get nothing back, since nothing - /// was cached. - /// The temporary solution to this is to force the download to happen. This is not ideal as we will no - /// longer be respecting the server "Cache-Control:Max-Age" header. Which means we'll continually download the - /// files, even if not needed (since the server says to use the local value). This is not great, but is not - /// terrible either. First, we will only download the full DB file once, when it is actually missing on - /// the user's machine. From that point on, we'll only be querying the server for the delta-patch file for the DB - /// version we have locally. The vast majority of the time that is a tiny document of the form ]]> (around 70 bytes) which simply tells the user - /// they are up to date. Only about once every three months will they actually download a large patch file. Also, - /// this patch download will only happen once a day tops (as that is our cadence for checking if there are new index - /// versions out). - /// https://github.com/dotnet/roslyn/issues/71014 tracks this issue. Once RemoteControlClient is updated to - /// support this again, we can remove this specialized code for netcore. - /// - public async Task ReadFileAsync() - // Note: we try .ReturnStale first so this will automatically light up once they fix their issue, without - // us having to do anything on our end. Once we do get around to making a change, we'll remove the - // .ForceDownload part entirely. - => await _client.ReadFileAsync(BehaviorOnStale.ReturnStale).ConfigureAwait(false) ?? - await _client.ReadFileAsync(BehaviorOnStale.ForceDownload).ConfigureAwait(false); - -#else - public Task ReadFileAsync() => _client.ReadFileAsync(BehaviorOnStale.ReturnStale); -#endif - public void Dispose() => _client.Dispose(); } diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf index f9bdc63e865e7..c66bc3639fa48 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.cs.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Přejít na implementaci + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Synchronizovat obory &názvů diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf index 3308cd21f2bd2..efa6ca5abcdfb 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.de.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Zur Implementierung wechseln + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces &Namespaces synchronisieren diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf index b32bea9149faf..a99ea2be20e36 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.es.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Ir a la implementación + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Sincronizar &espacio de nombres diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf index 409be3dc72829..14c85711caa0d 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.fr.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Accéder à l'implémentation + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces &Synchroniser les espaces de noms diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf index 8cf86e9eb3fbe..dfb1cdf374d56 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.it.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Vai all'implementazione + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Sincronizza spazi dei &nomi diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf index 9346873158dd3..f5840c1d1e88d 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ja.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ 実装に移動 + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces 名前空間の同期(&N) diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf index fae98049f2f5f..049d98eed01f0 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ko.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ 구현으로 이동 + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces 네임스페이스 동기화(&N) diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf index 078d491445636..672549210baa9 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pl.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Przejdź do implementacji + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Synchronizuj &przestrzenie nazw diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf index c44d090d4fde0..930c1438706a7 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.pt-BR.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Ir para Implementação + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Sincronizar &Namespaces diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf index f61de2c07789e..052ad998650c0 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.ru.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Перейти к реализации + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Синхронизация пространства имен diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf index 79db0634ba511..2774ea5dde806 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.tr.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ Uygulamaya Git + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces Eşitle &Ad Alanları diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf index 124820e5fe220..13d69770b1b41 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hans.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ 转到实现 + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces 同步命名空间(&N) diff --git a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf index bf9a023f17311..7b799ee03fd39 100644 --- a/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/Commands.vsct.zh-Hant.xlf @@ -23,8 +23,8 @@ - CleearStackTraceExplorerCommandName - CleearStackTraceExplorerCommandName + ClearStackTraceExplorerCommandName + CleearStackTraceExplorerCommandName @@ -587,6 +587,46 @@ 前往實作 + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Solution Explorer Symbol Tree + Solution Explorer Symbol Tree + + + + Find All References + Find All References + + + + FindAllReferences + FindAllReferences + + + + Go To Base + Go To Base + + + + GoToBase + GoToBase + + + + Go To Implementation + Go To Implementation + + + + GoToImplementation + GoToImplementation + + Sync &Namespaces 同步命名空間(&N) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs index ae051779293c0..992e16a6f0056 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore /// Microsoft.VisualStudio.Shell.TreeNavigation.HierarchyProvider.dll and /// Microsoft.VisualStudio.Shell.TreeNavigation.GraphProvider.dll. /// -internal abstract class BaseItem : +internal abstract class BaseItem(string name, bool canPreview = false) : LocalizableProperties, ITreeDisplayItem, IInteractionPatternProvider, @@ -31,14 +31,11 @@ internal abstract class BaseItem : ISupportDisposalNotification, IPrioritizedComparable { - public virtual event PropertyChangedEventHandler PropertyChanged { add { } remove { } } + public bool CanPreview { get; } = canPreview; - protected readonly string Name; + public string Name { get; } = name; - public BaseItem(string name) - { - Name = name; - } + public virtual event PropertyChangedEventHandler PropertyChanged { add { } remove { } } public IEnumerable Children => []; @@ -103,8 +100,6 @@ public BaseItem(string name) return null; } - public bool CanPreview => false; - public virtual IInvocationController? InvocationController => null; public virtual IContextMenuController? ContextMenuController => null; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/BulkObservableCollectionWithInit.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/BulkObservableCollectionWithInit.cs new file mode 100644 index 0000000000000..5f3f57c922e5a --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/BulkObservableCollectionWithInit.cs @@ -0,0 +1,43 @@ +// 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.ObjectModel; +using System.ComponentModel; +using Microsoft.VisualStudio.Language.Intellisense; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// This derivation of also supports raising an initialized event through +/// . This is used to show the spinning icon in the solution explorer +/// the first time you expand it. +/// +internal sealed class BulkObservableCollectionWithInit : BulkObservableCollection, ISupportInitializeNotification +{ + private bool _isInitialized; + + public bool IsInitialized + { + get => _isInitialized; + set + { + if (_isInitialized != value) + { + _isInitialized = value; + Initialized?.Invoke(this, EventArgs.Empty); + } + } + } + + public event EventHandler? Initialized; + + void ISupportInitialize.BeginInit() + { + } + + void ISupportInitialize.EndInit() + { + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/ContextMenuController.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/ContextMenuController.cs index 6e16b60ffbfbe..c67811b83d66e 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/ContextMenuController.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/ContextMenuController.cs @@ -36,12 +36,11 @@ public bool ShowContextMenu(IEnumerable items, Point location) _updateMenu(); var guidContextMenu = Guids.RoslynGroupId; - var locationPoints = new[] { new POINTS() { x = (short)location.X, y = (short)location.Y } }; return Shell.Package.GetGlobalService(typeof(SVsUIShell)) is IVsUIShell shell && ErrorHandler.Succeeded(shell.ShowContextMenu( 0, ref guidContextMenu, _menuId, - locationPoints, + [new POINTS() { x = (short)location.X, y = (short)location.Y }], pCmdTrgtActive: null)); } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/Search/RoslynSolutionExplorerSearchProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/RoslynSolutionExplorerSearchProvider.cs new file mode 100644 index 0000000000000..ddac9a2e5729f --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/RoslynSolutionExplorerSearchProvider.cs @@ -0,0 +1,121 @@ +// 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.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Wpf; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Entrypoint that exposes roslyn search capabilities from solution explorer. +/// +[AppliesToProject("CSharp | VB")] +[Export(typeof(ISearchProvider))] +[Name(nameof(RoslynSolutionExplorerSearchProvider))] +[Order(Before = "GraphSearchProvider")] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RoslynSolutionExplorerSearchProvider( + VisualStudioWorkspace workspace, + IAsynchronousOperationListenerProvider listenerProvider, + IThreadingContext threadingContext) : ISearchProvider +{ + private readonly VisualStudioWorkspace _workspace = workspace; + private readonly IThreadingContext _threadingContext = threadingContext; + public readonly SolutionExplorerNavigationSupport NavigationSupport = new(workspace, threadingContext, listenerProvider); + + public void Search(IRelationshipSearchParameters parameters, Action resultAccumulator) + { + if (!parameters.Options.SearchFileContents) + return; + + // Have to synchronously block on the search finishing as otherwise the caller will think we are + // done prior to us reporting any results. + _threadingContext.JoinableTaskFactory.Run(SearchAsync); + + async Task SearchAsync() + { + try + { + var solution = _workspace.CurrentSolution; + var searcher = NavigateToSearcher.Create( + solution, + new SolutionExplorerNavigateToSearchCallback(this, resultAccumulator), + parameters.SearchQuery.SearchString.Trim(), + NavigateToUtilities.GetKindsProvided(solution), + new SolutionExplorerNavigateToSearcherHost(_workspace)); + + await searcher.SearchAsync(NavigateToSearchScope.Solution, parameters.CancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (Exception ex) when (FatalError.ReportAndCatch(ex)) + { + } + } + } + + private sealed class SolutionExplorerNavigateToSearchCallback( + RoslynSolutionExplorerSearchProvider provider, + Action resultAccumulator) : INavigateToSearchCallback + { + public void Done(bool isFullyLoaded) { } + public void ReportIncomplete() { } + public void ReportProgress(int current, int maximum) { } + + public Task AddResultsAsync( + ImmutableArray results, + CancellationToken cancellationToken) + { + foreach (var result in results) + { + // Compute the name on the BG to avoid UI work. + var name = result.NavigableItem.DisplayTaggedParts.JoinText(); + var imageMoniker = result.NavigableItem.Glyph.GetImageMoniker(); + resultAccumulator(new SolutionExplorerSearchResult(provider, result, name, imageMoniker)); + } + + return Task.CompletedTask; + } + } + + private sealed class SolutionExplorerSearchResult( + RoslynSolutionExplorerSearchProvider provider, + INavigateToSearchResult result, + string name, + ImageMoniker imageMoniker) : ISearchResult + { + public object GetDisplayItem() + => new SolutionExplorerSearchDisplayItem(provider, result, name, imageMoniker); + } + + private sealed class SolutionExplorerNavigateToSearcherHost(Workspace workspace) : INavigateToSearcherHost + { + public INavigateToSearchService? GetNavigateToSearchService(Microsoft.CodeAnalysis.Project project) + => project.GetLanguageService(); + + public async ValueTask IsFullyLoadedAsync(CancellationToken cancellationToken) + { + var statusService = workspace.Services.GetRequiredService(); + var isFullyLoaded = await statusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); + return isFullyLoaded; + } + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItem.cs new file mode 100644 index 0000000000000..29b81c1a5d117 --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItem.cs @@ -0,0 +1,40 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.CodeAnalysis.Editor.Wpf; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +internal sealed class SolutionExplorerSearchDisplayItem( + RoslynSolutionExplorerSearchProvider provider, + INavigateToSearchResult result, + string name, + ImageMoniker imageMoniker) + : BaseItem(name, canPreview: true), + IInvocationController +{ + public readonly INavigateToSearchResult Result = result; + + public override ImageMoniker IconMoniker { get; } = imageMoniker; + + public override IInvocationController? InvocationController => this; + + public bool Invoke(IEnumerable items, InputSource inputSource, bool preview) + { + if (items.FirstOrDefault() is not SolutionExplorerSearchDisplayItem displayItem) + return false; + + provider.NavigationSupport.NavigateTo( + displayItem.Result.NavigableItem.Document.Id, + displayItem.Result.NavigableItem.SourceSpan.Start, + preview: true); + return true; + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItemSourceProvider.cs new file mode 100644 index 0000000000000..72b05a8e00f2f --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/Search/SolutionExplorerSearchDisplayItemSourceProvider.cs @@ -0,0 +1,59 @@ +// 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; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Responsible for taking search result items and parenting them with their corresponding real document +/// (i.e. an IVhHierarchy + itemid) in the solution explorer window. In other words, this source provider +/// providers the "contained by" relation, mapping to +/// . +/// +[Export(typeof(IAttachedCollectionSourceProvider))] +[Name(nameof(SolutionExplorerSearchDisplayItemSourceProvider))] +[Order(Before = HierarchyItemsProviderNames.Contains)] +[AppliesToProject("CSharp | VB")] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed partial class SolutionExplorerSearchDisplayItemSourceProvider( + VisualStudioWorkspace workspace, + IVsHierarchyItemManager hierarchyItemManager) + : AttachedCollectionSourceProvider +{ + protected override IAttachedCollectionSource? CreateCollectionSource( + SolutionExplorerSearchDisplayItem item, string relationshipName) + { + if (relationshipName != KnownRelationships.ContainedBy) + return null; + + var document = workspace.CurrentSolution.GetDocument(item.Result.NavigableItem.Document.Id); + if (document is null) + return null; + + if (!VisualStudioWorkspaceUtilities.TryGetVsHierarchyItem( + hierarchyItemManager, document, out var hierarchyItem)) + { + return null; + } + + return new SolutionExplorerSearchDisplayItemCollectionSource(item, hierarchyItem); + } + + private sealed class SolutionExplorerSearchDisplayItemCollectionSource( + SolutionExplorerSearchDisplayItem item, IVsHierarchyItem hierarchyItem) : IAttachedCollectionSource + { + public object SourceItem => item; + public bool HasItems => true; + public IEnumerable Items => ImmutableArray.Create(hierarchyItem); + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SolutionExplorerNavigationSupport.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SolutionExplorerNavigationSupport.cs new file mode 100644 index 0000000000000..a69e83839d469 --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SolutionExplorerNavigationSupport.cs @@ -0,0 +1,44 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Helper type for navigating to items shown in the solution explorer tree. Used for navigating to symbol +/// tree items, as well as the results of symbol tree search. +/// +internal sealed class SolutionExplorerNavigationSupport( + Workspace workspace, + IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider listenerProvider) +{ + private readonly CancellationSeries _cancellationSeries = new(threadingContext.DisposalToken); + private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.SolutionExplorer); + + public void NavigateTo(DocumentId documentId, int position, bool preview) + { + // Cancel any in flight navigation and kick off a new one. + var cancellationToken = _cancellationSeries.CreateNext(); + var navigationService = workspace.Services.GetRequiredService(); + + var token = _listener.BeginAsyncOperation(nameof(NavigateTo)); + navigationService.TryNavigateToPositionAsync( + threadingContext, + workspace, + documentId, + position, + virtualSpace: 0, + // May be calling this on stale data. Allow the position to be invalid + allowInvalidPosition: true, + new NavigationOptions(PreferProvisionalTab: preview), + cancellationToken).ReportNonFatalErrorUnlessCancelledAsync(cancellationToken).CompletesAsyncOperation(token); + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs index 3d88e6dbcd0cf..6772e9d925fb0 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs @@ -20,7 +20,7 @@ internal sealed partial class SourceGeneratedFileItem( string hintName, string languageName, Workspace workspace) - : BaseItem(name: hintName) + : BaseItem(hintName) { private readonly IThreadingContext _threadingContext = threadingContext; private readonly string _languageName = languageName; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index 9ec77585af733..545236d70563a 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -4,8 +4,6 @@ using System; using System.Collections; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,7 +12,6 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Threading; using Microsoft.Internal.VisualStudio.PlatformUI; -using Microsoft.VisualStudio.Language.Intellisense; namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; @@ -138,7 +135,7 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel finally { _items.EndBulkOperation(); - _items.MarkAsInitialized(); + _items.IsInitialized = true; } } @@ -237,33 +234,4 @@ private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) } } } - - /// - /// This derivation of also supports raising an initialized event through - /// . This is used to show the spinning icon in the solution explorer - /// the first time you expand it. - /// - private sealed class BulkObservableCollectionWithInit : BulkObservableCollection, ISupportInitializeNotification - { - public bool IsInitialized { get; private set; } = false; - - public event EventHandler? Initialized; - - void ISupportInitialize.BeginInit() - { - } - - void ISupportInitialize.EndInit() - { - } - - public void MarkAsInitialized() - { - if (!IsInitialized) - { - IsInitialized = true; - Initialized?.Invoke(this, EventArgs.Empty); - } - } - } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/NonRootSymbolTreeItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/NonRootSymbolTreeItemSourceProvider.cs new file mode 100644 index 0000000000000..6d1b37f9bd8e9 --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/NonRootSymbolTreeItemSourceProvider.cs @@ -0,0 +1,36 @@ +// 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.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Provides a collection source for getting the children of a given SymbolTreeItem on demand. This can be done +/// trivially as hold onto to the syntax node that they were created for. Note: if the root item is not expanded +/// then no actual calls into the syntax model are done to avoid creating parse trees unnecessarily. +/// +[Export(typeof(IAttachedCollectionSourceProvider))] +[Name(nameof(NonRootSymbolTreeItemSourceProvider))] +[Order(Before = HierarchyItemsProviderNames.Contains)] +[AppliesToProject("CSharp | VB")] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class NonRootSymbolTreeItemSourceProvider() : AttachedCollectionSourceProvider +{ + protected override IAttachedCollectionSource? CreateCollectionSource(SymbolTreeItem item, string relationshipName) + { + if (relationshipName != KnownRelationships.Contains) + return null; + + // A SymbolTreeItem is its own collection source. In other words, it points at its own children + // and can be queried directly for them. + return item; + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemCollectionSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemCollectionSource.cs new file mode 100644 index 0000000000000..8202f5dd5bbaa --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemCollectionSource.cs @@ -0,0 +1,155 @@ +// 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; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.SolutionExplorer; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +internal sealed partial class RootSymbolTreeItemSourceProvider +{ + private sealed class RootSymbolTreeItemCollectionSource( + RootSymbolTreeItemSourceProvider rootProvider, + IVsHierarchyItem hierarchyItem) : IAttachedCollectionSource, INotifyPropertyChanged + { + private readonly RootSymbolTreeItemSourceProvider _rootProvider = rootProvider; + private readonly IVsHierarchyItem _hierarchyItem = hierarchyItem; + + // Mark hasItems as null as we don't know up front if we have items, and instead have to compute it on demand. + private readonly SymbolTreeChildCollection _childCollection = new( + rootProvider, hierarchyItem, hasItemsDefault: GetHasItemsDefaultValue(hierarchyItem)); + + /// + /// Whether or not this root solution explorer node has been expanded or not. Until it is first expanded, + /// we do no work so as to avoid CPU time and rooting things like syntax nodes. + /// + private volatile int _hasEverBeenExpanded; + + private static bool? GetHasItemsDefaultValue(IVsHierarchyItem hierarchyItem) + // If this is not a c#/vb file initially, then mark this file as having no symbolic children. + // If it is c#/vb, then mark it as null (which means 'unknown') so that we show the arrow next + // to the item, but compute only once expanded. + => Path.GetExtension(hierarchyItem.CanonicalName).ToLowerInvariant() is ".cs" or ".vb" + ? null + : false; + + public void Reset() + { + _rootProvider.ThreadingContext.ThrowIfNotOnUIThread(); + _childCollection.ResetToUncomputedState(GetHasItemsDefaultValue(_hierarchyItem)); + + // Note: we intentionally do not touch _hasEverBeenExpanded. The platform only ever calls "Items" + // at most once (even if we notify that it changed). So if we reset _hasEverBeenExpanded to 0, then + // it will never leave that state from that point on, and we'll be stuck in an invalid state. + } + + public async Task UpdateIfEverExpandedAsync(CancellationToken cancellationToken) + { + // If we haven't been initialized yet, then we don't have to do anything. We will get called again + // in the future as documents are mutated, and we'll ignore until the point that the user has at + // least expanded this node once. + if (_hasEverBeenExpanded == 0) + return; + + // Try to find a roslyn document for this file path. Note: it is intentional that we continue onwards, + // even if this returns null. We still want to put ourselves into the final "i have no items" state, + // instead of bailing out and potentially leaving either stale items, or leaving ourselves in the + // "i don't know what items are in me" state. + var documentId = DetermineDocumentId(); + + var solution = _rootProvider._workspace.CurrentSolution; + + var document = solution.GetDocument(documentId); + var itemProvider = document?.GetLanguageService(); + + if (document != null && itemProvider != null) + { + // Compute the items on the BG. + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var items = itemProvider.GetItems(document.Id, root, cancellationToken); + + // Then switch to the UI thread to actually update the collection. + await _rootProvider.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + _childCollection.SetItemsAndMarkComputed_OnMainThread(itemProvider, items); + } + else + { + // If we can't find this document anymore, clear everything out. + await _rootProvider.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + _childCollection.ClearAndMarkComputed_OnMainThread(); + } + } + + private DocumentId? DetermineDocumentId() + { + var filePath = TryGetCanonicalName(); + + if (filePath != null) + { + var idMap = _rootProvider._workspace.Services.GetRequiredService(); + if (idMap.TryGetProject(_hierarchyItem.Parent, targetFrameworkMoniker: null, out var project)) + { + var documentIds = project.Solution.GetDocumentIdsWithFilePath(filePath); + return documentIds.FirstOrDefault(static (d, projectId) => d.ProjectId == projectId, project.Id); + } + } + + return null; + + string? TryGetCanonicalName() + { + // Quick check that will be correct the majority of the time. + if (!_hierarchyItem.IsDisposed) + { + // We are running in the background. So it's possible that the type may be disposed between + // the above check and retrieving the canonical name. So have to guard against that just in case. + try + { + return _hierarchyItem.CanonicalName; + } + catch (ObjectDisposedException) + { + } + } + + return null; + } + } + + object IAttachedCollectionSource.SourceItem => _childCollection.SourceItem; + + bool IAttachedCollectionSource.HasItems => _childCollection.HasItems; + + IEnumerable IAttachedCollectionSource.Items + { + get + { + if (Interlocked.CompareExchange(ref _hasEverBeenExpanded, 1, 0) == 0) + { + // This was the first time this node was expanded. Kick off the initial work to + // compute the items for it. + _rootProvider._updateSourcesQueue.AddWork(_hierarchyItem.CanonicalName); + } + + return _childCollection.Items; + } + } + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add => _childCollection.PropertyChanged += value; + remove => _childCollection.PropertyChanged -= value; + } + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemSourceProvider.cs new file mode 100644 index 0000000000000..8e338bc9aac3a --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/RootSymbolTreeItemSourceProvider.cs @@ -0,0 +1,221 @@ +// 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.ComponentModel; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.GoOrFind; +using Microsoft.CodeAnalysis.GoToBase; +using Microsoft.CodeAnalysis.GoToImplementation; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Source provider responsible for hearing about C#/VB files and attaching the root 'symbol tree' node. +/// Users can then expand that node to get access to the symbols within the file. Note: this tree is +/// built lazily (one level at a time), and only uses syntax so it can be done extremely quickly. +/// +[Export(typeof(IAttachedCollectionSourceProvider))] +[Name(nameof(RootSymbolTreeItemSourceProvider))] +[Order(Before = HierarchyItemsProviderNames.Contains)] +[AppliesToProject("CSharp | VB")] +internal sealed partial class RootSymbolTreeItemSourceProvider : AttachedCollectionSourceProvider +{ + /// + /// Mapping from filepath to the collection sources made for it. Is a multi dictionary because the same + /// file may appear in multiple projects, but each will have its own collection soure to represent the view + /// of that file through that project. + /// + /// Lock this instance when reading/writing as it is used over different threads. + private readonly Dictionary> _filePathToCollectionSources = new( + StringComparer.OrdinalIgnoreCase); + + /// + /// Queue of notifications we've heard about for changed document file paths. We'll then go update the + /// symbol tree item for each of these documents so that it is up to date. Note: if the symbol tree has + /// never been expanded, this will bail immediately to avoid doing unnecessary work. + /// + private readonly AsyncBatchingWorkQueue _updateSourcesQueue; + private readonly Workspace _workspace; + + private readonly IGoOrFindNavigationService _goToBaseNavigationService; + private readonly IGoOrFindNavigationService _goToImplementationNavigationService; + private readonly IGoOrFindNavigationService _findReferencesNavigationService; + + public readonly SolutionExplorerNavigationSupport NavigationSupport; + public readonly IThreadingContext ThreadingContext; + public readonly IAsynchronousOperationListener Listener; + + public readonly IContextMenuController ContextMenuController; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RootSymbolTreeItemSourceProvider( + IThreadingContext threadingContext, + VisualStudioWorkspace workspace, + GoToBaseNavigationService goToBaseNavigationService, + GoToImplementationNavigationService goToImplementationNavigationService, + FindReferencesNavigationService findReferencesNavigationService, + IAsynchronousOperationListenerProvider listenerProvider) + { + ThreadingContext = threadingContext; + _workspace = workspace; + _goToBaseNavigationService = goToBaseNavigationService; + _goToImplementationNavigationService = goToImplementationNavigationService; + _findReferencesNavigationService = findReferencesNavigationService; + Listener = listenerProvider.GetListener(FeatureAttribute.SolutionExplorer); + NavigationSupport = new(workspace, threadingContext, listenerProvider); + + _updateSourcesQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.Medium, + UpdateCollectionSourcesAsync, + // Ignore case as we're comparing file paths here. + StringComparer.OrdinalIgnoreCase, + this.Listener, + this.ThreadingContext.DisposalToken); + + this._workspace.RegisterWorkspaceChangedHandler( + e => + { + var oldPath = e.OldSolution.GetDocument(e.DocumentId)?.FilePath; + var newPath = e.NewSolution.GetDocument(e.DocumentId)?.FilePath; + + if (oldPath != null) + _updateSourcesQueue.AddWork(oldPath); + + if (newPath != null) + _updateSourcesQueue.AddWork(newPath); + }, + options: new WorkspaceEventOptions(RequiresMainThread: false)); + + this.ContextMenuController = new SymbolItemContextMenuController(this); + } + + private async ValueTask UpdateCollectionSourcesAsync( + ImmutableSegmentedList updatedFilePaths, CancellationToken cancellationToken) + { + using var _ = Microsoft.CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var sources); + + lock (_filePathToCollectionSources) + { + foreach (var filePath in updatedFilePaths) + { + if (_filePathToCollectionSources.TryGetValue(filePath, out var pathSources)) + sources.AddRange(pathSources); + } + } + + // Update all the affected documents in parallel. + await RoslynParallel.ForEachAsync( + sources, + cancellationToken, + async (source, cancellationToken) => + { + await source.UpdateIfEverExpandedAsync(cancellationToken) + .ReportNonFatalErrorUnlessCancelledAsync(cancellationToken) + .ConfigureAwait(false); + }).ConfigureAwait(false); + } + + protected override IAttachedCollectionSource? CreateCollectionSource(IVsHierarchyItem item, string relationshipName) + { + if (item == null || + item.IsDisposed || + item.HierarchyIdentity == null || + item.HierarchyIdentity.NestedHierarchy == null || + relationshipName != KnownRelationships.Contains) + { + return null; + } + + var hierarchy = item.HierarchyIdentity.NestedHierarchy; + var itemId = item.HierarchyIdentity.NestedItemID; + + if (hierarchy.GetProperty(itemId, (int)__VSHPROPID7.VSHPROPID_ProjectTreeCapabilities, out var capabilitiesObj) != VSConstants.S_OK || + capabilitiesObj is not string capabilities) + { + return null; + } + + if (!capabilities.Contains(nameof(VisualStudio.ProjectSystem.ProjectTreeFlags.SourceFile)) || + !capabilities.Contains(nameof(VisualStudio.ProjectSystem.ProjectTreeFlags.FileOnDisk))) + { + return null; + } + + // Important: currentFilePath is mutable state captured *AND UPDATED* in the local function + // OnItemPropertyChanged below. It allows us to know the file path of the item *prior* to + // it being changed *when* we hear the update about it having changed (since hte event doesn't + // contain the old value). + if (item.CanonicalName is not string currentFilePath) + return null; + + var source = new RootSymbolTreeItemCollectionSource(this, item); + lock (_filePathToCollectionSources) + { + _filePathToCollectionSources.MultiAdd(currentFilePath, source); + } + + // Register to hear about if this hierarchy is disposed. We'll stop watching it if so. + item.PropertyChanged += OnItemPropertyChanged; + + return source; + + void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ISupportDisposalNotification.IsDisposed) && item.IsDisposed) + { + // We are notified when the IVsHierarchyItem is removed from the tree via its INotifyPropertyChanged + // event for the IsDisposed property. When this fires, we remove the filePath->sourcce mapping we're holding. + lock (_filePathToCollectionSources) + { + _filePathToCollectionSources.MultiRemove(currentFilePath, source); + } + + item.PropertyChanged -= OnItemPropertyChanged; + } + else if (e.PropertyName == nameof(IVsHierarchyItem.CanonicalName)) + { + var newPath = item.CanonicalName; + if (newPath != currentFilePath) + { + lock (_filePathToCollectionSources) + { + + // Unlink the oldPath->source mapping, and add a new line for the newPath->source. + _filePathToCollectionSources.MultiRemove(currentFilePath, source); + _filePathToCollectionSources.MultiAdd(newPath, source); + + // Keep track of the 'newPath'. + currentFilePath = newPath; + } + + // If the filepath changes for an item (which can happen when it is renamed), place a notification + // in the queue to update it in the future. This will ensure all the items presented for it have hte + // right document id. Also reset the state of the source. The filepath could change to something + // no longer valid (like .cs to .txt), or vice versa. + source.Reset(); + _updateSourcesQueue.AddWork(newPath); + } + } + } + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolItemContextMenuController.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolItemContextMenuController.cs new file mode 100644 index 0000000000000..70b533c9c4e60 --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolItemContextMenuController.cs @@ -0,0 +1,80 @@ +// 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.Linq; +using System.Windows; +using Microsoft.CodeAnalysis; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.ProjectSystem.VS; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +internal sealed partial class RootSymbolTreeItemSourceProvider +{ + private sealed class SymbolItemContextMenuController( + RootSymbolTreeItemSourceProvider rootProvider) : IContextMenuController + { + public bool ShowContextMenu(IEnumerable items, Point location) + { + if (items.FirstOrDefault() is not SymbolTreeItem item) + return false; + + var guidContextMenu = Guids.RoslynGroupId; + if (Shell.Package.GetGlobalService(typeof(SVsUIShell)) is not IVsUIShell shell) + return false; + + var result = shell.ShowContextMenu( + dwCompRole: 0, + ref guidContextMenu, + //0x400, + ID.RoslynCommands.SolutionExplorerSymbolItemContextMenu, + [new() { x = (short)location.X, y = (short)location.Y }], + pCmdTrgtActive: new SymbolTreeItemOleCommandTarget(rootProvider, item)); + return ErrorHandler.Succeeded(result); + } + + private sealed class SymbolTreeItemOleCommandTarget( + RootSymbolTreeItemSourceProvider rootProvider, + SymbolTreeItem item) : IOleCommandTarget + { + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + // All commands are available on all items currently. Note: we could refine this if we want to. + // For example "go to base/derived" doesn't really make sense on fields. For now though, we just + // keep things simple. This is similar to how you always get all these nav options in the editor + // when you right click on any location in a C# file. + return HResult.OK; + } + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + var navigationService = nCmdID switch + { + ID.RoslynCommands.SolutionExplorerSymbolItemGoToBase => rootProvider._goToBaseNavigationService, + ID.RoslynCommands.SolutionExplorerSymbolItemGoToImplementation => rootProvider._goToImplementationNavigationService, + ID.RoslynCommands.SolutionExplorerSymbolItemFindAllReferences => rootProvider._findReferencesNavigationService, + _ => null, + }; + + if (navigationService != null) + { + var document = rootProvider._workspace.CurrentSolution.GetDocument(item.ItemKey.DocumentId); + if (document != null) + { + navigationService.ExecuteCommand( + document, item.ItemSyntax.NavigationToken.SpanStart, + // May be calling this on stale data. Allow the position to be invalid + allowInvalidPosition: true); + } + } + + return HResult.OK; + } + } + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeChildCollection.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeChildCollection.cs new file mode 100644 index 0000000000000..031c9d269f33a --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeChildCollection.cs @@ -0,0 +1,141 @@ +// 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.Collections; +using System.Collections.Immutable; +using System.ComponentModel; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionExplorer; +using Microsoft.VisualStudio.LanguageServices.Extensions; +using Microsoft.VisualStudio.Shell; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// If non-null, the known value for . If null, +/// then only known once is initialized +internal sealed class SymbolTreeChildCollection( + RootSymbolTreeItemSourceProvider rootProvider, + object parentItem, + bool? hasItemsDefault) : IAttachedCollectionSource, INotifyPropertyChanged +{ + private readonly BulkObservableCollectionWithInit _symbolTreeItems = []; + private readonly RootSymbolTreeItemSourceProvider _rootProvider = rootProvider; + + public object SourceItem { get; } = parentItem; + + private bool? _hasItemsDefault = hasItemsDefault; + + /// + /// Whether or not we think we have items. If we aren't fully initialized, then we'll guess that we do have items. + /// Once fully initialized, we'll return the real result based on what is in our child item list. + /// + public bool HasItems + { + get + { + // Owner initialized us with a known value for this property. Can just return that value. + if (_hasItemsDefault.HasValue) + return _hasItemsDefault.Value; + + // If we're not initialized yet, we don't know if we have values or not. Return that we are + // so the user can at least try to expand this node. + if (!_symbolTreeItems.IsInitialized) + return true; + + // We are initialized. So return the actual state based on what has been computed. + return _symbolTreeItems.Count > 0; + } + } + + public IEnumerable Items => _symbolTreeItems; + + public event PropertyChangedEventHandler? PropertyChanged; + + public void ResetToUncomputedState(bool? hasItemsDefault) + { + _hasItemsDefault = hasItemsDefault; + _symbolTreeItems.Clear(); + + // Move back to the state where the children are not initialized. That way the next attemp to open + // them will compute them. + MarkInitialized(isInitialized: false); + } + + public void SetItemsAndMarkComputed_OnMainThread( + ISolutionExplorerSymbolTreeItemProvider itemProvider, + ImmutableArray itemDatas) + { + Contract.ThrowIfFalse(_rootProvider.ThreadingContext.JoinableTaskContext.IsOnMainThread); + + using var _ = PooledDictionary>.GetInstance(out var keyToItems); + foreach (var item in _symbolTreeItems) + keyToItems.MultiAdd(item.ItemKey, item); + + using (this._symbolTreeItems.GetBulkOperation()) + { + // We got some item datas. Attempt to reuse existing symbol tree items that match up to preserve + // identity in the tree between changes. + // Clear out the old items we have. Then go through setting the final list of items. + // Attempt to reuse old items if they have the same visible data from before. + _symbolTreeItems.Clear(); + + foreach (var itemData in itemDatas) + { + if (keyToItems.TryGetValue(itemData.ItemKey, out var matchingItems)) + { + // Found a matching item we can use. Remove it from the list of items so we don't reuse it again. + var matchingItem = matchingItems[0]; + matchingItems.RemoveAt(0); + if (matchingItems.Count == 0) + keyToItems.Remove(itemData.ItemKey); + + Contract.ThrowIfFalse(matchingItem.ItemProvider == itemProvider); + Contract.ThrowIfFalse(matchingItem.ItemKey == itemData.ItemKey); + + // And update it to point to the new syntax information. + matchingItem.ItemSyntax = itemData.ItemSyntax; + _symbolTreeItems.Add(matchingItem); + } + else + { + // If we didn't find an existing item, create a new one. + _symbolTreeItems.Add(new(_rootProvider, itemProvider, itemData.ItemKey) + { + ItemSyntax = itemData.ItemSyntax + }); + } + } + } + + keyToItems.FreeValues(); + + // Once we've been initialized once, mark us that way so that we we move out of the 'spinning/computing' state. + MarkInitialized(isInitialized: true); + } + + public void ClearAndMarkComputed_OnMainThread() + { + Contract.ThrowIfFalse(_rootProvider.ThreadingContext.JoinableTaskContext.IsOnMainThread); + + using (this._symbolTreeItems.GetBulkOperation()) + { + _symbolTreeItems.Clear(); + } + + // Once we've been initialized once, mark us that way so that we we move out of the 'spinning/computing' state. + MarkInitialized(isInitialized: true); + } + + private void MarkInitialized(bool isInitialized) + { + Contract.ThrowIfFalse(_rootProvider.ThreadingContext.JoinableTaskContext.IsOnMainThread); + + _symbolTreeItems.IsInitialized = isInitialized; + + // Notify any listenerrs that our items have changed. + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasItems))); + } +} diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeItem.cs new file mode 100644 index 0000000000000..4d5a59f904786 --- /dev/null +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SymbolTree/SymbolTreeItem.cs @@ -0,0 +1,133 @@ +// 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.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Wpf; +using Microsoft.CodeAnalysis.SolutionExplorer; +using Microsoft.Internal.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Shell; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer; + +/// +/// Actual in-memory object that will be presented in the solution explorer tree. Note: +/// we attempt to reuse instances of these to maintain visual persistence (like selection state) +/// when items are recomputed. +/// +internal sealed class SymbolTreeItem : BaseItem, + IInvocationController, + IAttachedCollectionSource, + ISupportExpansionEvents, + INotifyPropertyChanged +{ + public readonly RootSymbolTreeItemSourceProvider RootProvider; + public readonly ISolutionExplorerSymbolTreeItemProvider ItemProvider; + public readonly SymbolTreeItemKey ItemKey; + + private readonly SymbolTreeChildCollection _childCollection; + + private bool _expanded; + private SymbolTreeItemSyntax _itemSyntax; + + public SymbolTreeItem( + RootSymbolTreeItemSourceProvider rootProvider, + ISolutionExplorerSymbolTreeItemProvider itemProvider, + SymbolTreeItemKey itemKey) : base(itemKey.Name, canPreview: true) + { + RootProvider = rootProvider; + ItemProvider = itemProvider; + ItemKey = itemKey; + _childCollection = new(rootProvider, this, hasItemsDefault: ItemKey.HasItems); + } + + private void ThrowIfNotOnUIThread() + => this.RootProvider.ThreadingContext.ThrowIfNotOnUIThread(); + + public SymbolTreeItemSyntax ItemSyntax + { + get => _itemSyntax; + set + { + ThrowIfNotOnUIThread(); + + // When the syntax node for this item is changed, we want to recompute the children for it + // (if this node is expanded). Otherwise, we can just throw away what we have and recompute + // the next time when asked. + _itemSyntax = value; + UpdateChildren(); + } + } + + public override ImageMoniker IconMoniker => this.ItemKey.Glyph.GetImageMoniker(); + + // We act as our own invocation controller. + public override IInvocationController? InvocationController => this; + + public bool Invoke(IEnumerable items, InputSource inputSource, bool preview) + { + if (items.FirstOrDefault() is not SymbolTreeItem item) + return false; + + RootProvider.NavigationSupport.NavigateTo( + item.ItemKey.DocumentId, item.ItemSyntax.NavigationToken.SpanStart, preview); + return true; + } + + public override IContextMenuController? ContextMenuController + => RootProvider.ContextMenuController; + + public void BeforeExpand() + { + ThrowIfNotOnUIThread(); + _expanded = true; + UpdateChildren(); + } + + public void AfterCollapse() + { + ThrowIfNotOnUIThread(); + _expanded = false; + UpdateChildren(); + } + + private void UpdateChildren() + { + ThrowIfNotOnUIThread(); + + if (_expanded) + { + // When we update the item syntax we can reset ourselves to the initial state (if collapsed). + // Then when we're expanded the next time, we'll recompute the child items properly. If we + // are already expanded, then recompute our children which will recursively push the change + // down further. + var items = this.ItemProvider.GetItems( + this.ItemKey.DocumentId, _itemSyntax.DeclarationNode, this.RootProvider.ThreadingContext.DisposalToken); + _childCollection.SetItemsAndMarkComputed_OnMainThread(this.ItemProvider, items); + } + else + { + // Otherwise, return the child collection to the uninitialized state. + _childCollection.ResetToUncomputedState(this.ItemKey.HasItems); + } + } + + object IAttachedCollectionSource.SourceItem => _childCollection.SourceItem; + + bool IAttachedCollectionSource.HasItems => _childCollection.HasItems; + + IEnumerable IAttachedCollectionSource.Items => _childCollection.Items; + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add => _childCollection.PropertyChanged += value; + remove => _childCollection.PropertyChanged -= value; + } +} diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index a24b8aa145c24..48c89e6af35e5 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -45,13 +45,7 @@ - - - - - - diff --git a/src/VisualStudio/Core/Test/Progression/CSharpSymbolLabelTests.vb b/src/VisualStudio/Core/Test/Progression/CSharpSymbolLabelTests.vb deleted file mode 100644 index cd58e8270ce4d..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/CSharpSymbolLabelTests.vb +++ /dev/null @@ -1,195 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class CSharpSymbolLabelTests - - Public Async Function TestNamedType() As Task - Using testState = ProgressionTestState.Create( - - - - class $$C { } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "C", "C") - End Using - End Function - - - Public Async Function TestGenericNamedType() As Task - Using testState = ProgressionTestState.Create( - - - { } - ]]> - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "C", "C") - End Using - End Function - - - Public Async Function TestGenericMethod() As Task - Using testState = ProgressionTestState.Create( - - - () { } } - ]]> - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M() : void", "C.M() : void") - End Using - End Function - - - Public Async Function TestMethodWithParamsParameter() As Task - Using testState = ProgressionTestState.Create( - - - - class C { void $$M(params string[] goo) { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M(params string[]) : void", "C.M(params string[]) : void") - End Using - End Function - - - Public Async Function TestMethodWithOptionalParameter() As Task - Using testState = ProgressionTestState.Create( - - - - class C { void $$M(int i = 0) { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M([int]) : void", "C.M([int]) : void") - End Using - End Function - - - Public Async Function TestMethodWithRefAndOutParameters() As Task - Using testState = ProgressionTestState.Create( - - - - class C { void $$M(out string goo, ref string bar) { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M(out string, ref string) : void", "C.M(out string, ref string) : void") - End Using - End Function - - - Public Async Function TestEnumMember() As Task - Using testState = ProgressionTestState.Create( - - - - enum E { $$M } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M", "E.M") - End Using - End Function - - - Public Async Function TestConstructor() As Task - Using testState = ProgressionTestState.Create( - - - - class C { $$C() { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "C()", "C.C()") - End Using - End Function - - - Public Async Function TestDestructor() As Task - Using testState = ProgressionTestState.Create( - - - - class C { ~$$C() { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "~C()", "C.~C()") - End Using - End Function - - - Public Async Function TestExplicitlyImplementedInterface() As Task - Using testState = ProgressionTestState.Create( - - - - using System; - class C : IDisposable { void IDisposable.$$Dispose() { } } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "IDisposable.Dispose() : void", "C.Dispose() : void") - End Using - End Function - - - Public Async Function TestFixedFieldInStruct() As Task - Using testState = ProgressionTestState.Create( - - - - struct C { fixed int $$f[42]; } - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "f : int*", "C.f : int*") - End Using - End Function - - - - Public Async Function TestDelegateStyle() As Task - Using testState = ProgressionTestState.Create( - - - - delegate void $$Goo(); - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "Goo() : void", "Goo : void") - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/CallsGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/CallsGraphQueryTests.vb deleted file mode 100644 index 7ae2fc97e7581..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/CallsGraphQueryTests.vb +++ /dev/null @@ -1,235 +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. - -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class CallsGraphQueryTests - - Public Async Function CallsSimpleTests() As Task - Using testState = ProgressionTestState.Create( - - - - class A - { - public A() { } - public void Run() { } - static void $$Main(string[] args) { new A().Run(); } - } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New CallsGraphQuery(), GraphContextDirection.Source) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function CallsLambdaTests() As Task - Using testState = ProgressionTestState.Create( - - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -class A - { - static void $$Goo(String[] args) - { - int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; - int oddNumbers = numbers.Count(n => n % 2 == 1); - } - } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New CallsGraphQuery(), GraphContextDirection.Source) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function CallsPropertiesTests() As Task - Using testState = ProgressionTestState.Create( - - - - class A - { - static public int Get() { return 1; } - public int $$PropertyA = A.Get(); - } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New CallsGraphQuery(), GraphContextDirection.Source) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function CallsDelegatesTests() As Task - Using testState = ProgressionTestState.Create( - - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -delegate void MyDelegate1(int x, float y); - -class C -{ - public void DelegatedMethod(int x, float y = 3.0f) { System.Console.WriteLine(y); } - static void $$Main(string[] args) - { - C mc = new C(); - MyDelegate1 md1 = null; - md1 += mc.DelegatedMethod; - md1(1, 5); - md1 -= mc.DelegatedMethod; - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New CallsGraphQuery(), GraphContextDirection.Source) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function CallsDelegateCreationExpressionTests() As Task - Using testState = ProgressionTestState.Create( - - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -delegate void MyEvent(); - -class Test -{ - event MyEvent Clicked; - void Handler() { } - - public void $$Run() - { - Test t = new Test(); - t.Clicked += new MyEvent(Handler); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New CallsGraphQuery(), GraphContextDirection.Source) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb deleted file mode 100644 index bc9061ce5293d..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb +++ /dev/null @@ -1,162 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.GraphModel.Schemas -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class ContainsChildrenGraphQueryTests - - Public Async Function ContainsChildrenForDocument() As Task - Using testState = ProgressionTestState.Create( - - - - class C { } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsChildrenGraphQuery(), GraphContextDirection.Self) - - Dim node = outputContext.Graph.Nodes.Single() - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function ContainsChildrenForEmptyDocument() As Task - Using testState = ProgressionTestState.Create( - - - - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsChildrenGraphQuery(), GraphContextDirection.Self) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - - - - Public Async Function ContainsChildrenForFileWithIllegalPath() As Task - Using testState = ProgressionTestState.Create() - Dim graph = New Graph - graph.Nodes.GetOrCreate( - GraphNodeId.GetNested(GraphNodeId.GetPartial(CodeGraphNodeIdName.File, New Uri("C:\path\to\""some folder\App.config""", UriKind.RelativeOrAbsolute))), - label:=String.Empty, - CodeNodeCategories.File) - - ' Just making sure it doesn't throw. - Dim outputContext = Await testState.GetGraphContextAfterQuery(graph, New ContainsChildrenGraphQuery(), GraphContextDirection.Self) - End Using - End Function - - - - Public Async Function ContainsChildrenForNotYetLoadedSolution() As Task - Using testState = ProgressionTestState.Create( - - - - class C { } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - - ' To simulate the situation where a solution is not yet loaded and project info is not available, - ' remove a project from the solution. - - Dim oldSolution = testState.GetSolution() - Dim newSolution = oldSolution.RemoveProject(oldSolution.ProjectIds.FirstOrDefault()) - Dim outputContext = Await testState.GetGraphContextAfterQueryWithSolution(inputGraph, newSolution, New ContainsChildrenGraphQuery(), GraphContextDirection.Self) - - ' ContainsChildren should be set to false, so following updates will be tractable. - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - - End Using - End Function - - - Public Async Function ContainsChildrenForNodeWithRelativeUriPath() As Task - Using testState = ProgressionTestState.Create( - - - - Class C - End Class - - - ) - - ' Force creation of a graph node that has a nested relative URI file path. This simulates nodes that - ' other project types can give us for non-code files. E.g., `favicon.ico` for web projects. - Dim nodeId = GraphNodeId.GetNested(GraphNodeId.GetPartial(CodeGraphNodeIdName.File, New Uri("/Z:/Project.vb", UriKind.Relative))) - Dim inputGraph = New Graph() - Dim node = inputGraph.Nodes.GetOrCreate(nodeId) - node.AddCategory(CodeNodeCategories.File) - - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsChildrenGraphQuery(), GraphContextDirection.Any) - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb deleted file mode 100644 index eaf1373204612..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ContainsGraphQueryTests.vb +++ /dev/null @@ -1,392 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class ContainsGraphQueryTests - - Public Async Function TypesContainedInCSharpDocument() As Task - Using testState = ProgressionTestState.Create( - - - - class C { } - enum E { } - interface I { } - struct S { } - record R1 { } - record R2; - record class R3; - record struct R4 { } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TypesContainedInCSharpDocumentInsideNamespace() As Task - Using testState = ProgressionTestState.Create( - - - - namespace N.M - { - class C { } - enum E { } - interface I { } - struct S { } - } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TypesContainedInVisualBasicDocument() As Task - Using testState = ProgressionTestState.Create( - - - - Class C - End Class - - Enum E - End Enum - - Interface I - End Interface - - Module M - End Module - - Structure S - End Structure - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.vb") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function MembersContainedInCSharpScriptDocument() As Task - Using testState = ProgressionTestState.Create( - - - - - int F; - int P { get; set; } - void M() - { - } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.csx") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains).ConfigureAwait(False) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function MembersContainedInClass() As Task - Using testState = ProgressionTestState.Create( - - - - class $$C { void M(); event System.EventHandler E; } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function NestedTypesContainedInClass() As Task - Using testState = ProgressionTestState.Create( - - - - class C { class $$D { class E { } } } - - - ) - - Dim inputGraph = await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function EnumMembersInEnum() As Task - Using testState = ProgressionTestState.Create( - - - - enum $$E { M } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function NothingInBrokenCode() As Task - Using testState = ProgressionTestState.Create( - - - - [(delegate static - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function NothingInBrokenCode2() As Task - Using testState = ProgressionTestState.Create( - - - - int this[] { get { int x; } } - - - ) - - Dim inputGraph = testState.GetGraphWithDocumentNode(filePath:="Z:\Project.cs") - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function NothingInBrokenCode3() As Task - Using testState = ProgressionTestState.Create( - - - - Module $$Module1 - Public Class - End Module - - - ) - - Dim inputGraph = await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ContainsGraphQuery(), GraphContextDirection.Contains) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/GraphNodeCreationTests.vb b/src/VisualStudio/Core/Test/Progression/GraphNodeCreationTests.vb deleted file mode 100644 index b2854f9dd0195..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/GraphNodeCreationTests.vb +++ /dev/null @@ -1,811 +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. - -Imports System.Threading -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Microsoft.VisualStudio.LanguageServices.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class GraphNodeCreationTests - Private Shared Async Function AssertCreatedNodeIsAsync(code As String, expectedId As String, xml As XElement, Optional language As String = "C#") As Task - Using testState = ProgressionTestState.Create( - - CommonReferences="true" FilePath="Z:\Project.csproj"> - - <%= code %> - - - ) - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim node = GraphNodeCreation.CreateNodeIdAsync(symbol, testState.GetSolution(), CancellationToken.None).Result - Assert.Equal(expectedId, node.ToString()) - - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, xml) - End Using - End Function - - - Public Async Function TestSimpleType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class $$C { } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C)", - - - - - - - - - ) - End Function - - - Public Async Function TestNamespaceType() As Task - Await AssertCreatedNodeIsAsync("namespace $$N { class C { } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N)", - - - - - - - - - ) - End Function - - - Public Async Function TestLongNamespaceType() As Task - Await AssertCreatedNodeIsAsync("namespace N.$$N1.N11 { class C { } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N.N1)", - - - - - - - - - ) - End Function - - - Public Async Function TestSimpleParameterType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { void M(int $$x) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32)]) ParameterIdentifier=x)", - - - - - - - - - - ) - End Function - - - Public Async Function TestDelegateType() As Task - Await AssertCreatedNodeIsAsync("namespace N { delegate void D(string $$m); }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=D Member=(Name=Invoke OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=String)]) ParameterIdentifier=m)", - - - - - - - - - - ) - End Function - - - Public Async Function TestLambdaParameterType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { void M(Func $$x) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/bin/CSharpAssembly1.dll Type=(Name=Func GenericParameterCount=2 GenericArguments=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32),(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32)]))]) ParameterIdentifier=x)", - - - - - - - - - - ) - End Function - - - Public Async Function TestLocalType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { int M() { int $$y = 0; return y; } } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=M LocalVariable=y)", - - - - - - - - - ) - End Function - - - Public Async Function TestFirstLocalWithSameNameType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { int M() { { int $$y = 0; } { int y = 1;} } } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=M LocalVariable=y)", - - - - - - - - - ) - End Function - - - Public Async Function TestSecondLocalWithSameNameType() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { int M() { { int y = 0; } { int $$y = 1;} } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=M LocalVariable=y LocalVariableIndex=1)", - - - - - - - - - ) - End Function - - - Public Async Function TestErrorType() As Task - Await AssertCreatedNodeIsAsync( - "Class $$C : Inherits D : End Class", - "(Assembly=file:///Z:/bin/VisualBasicAssembly1.dll Type=C)", - - - - - - - - - , - LanguageNames.VisualBasic) - End Function - - - Public Async Function TestSimpleMethodSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("Goo(string[]) : void", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("Goo", graphNode.Label) - End Using - End Function - - - Public Async Function TestReferenceParameterSymbolTest() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { void $$Goo(ref int i) { i = i + 1; } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=Goo OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32 ParamKind=Ref)]))", - - - - - - - - - - ) - End Function - - - Public Async Function TestReferenceOutParameterSymbolTest() As Task - Await AssertCreatedNodeIsAsync("namespace N { class C { void $$Goo(out int i) { i = 1; } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=Goo OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32 ParamKind=Ref)]))", - - - - - - - - - - ) - End Function - - - Public Async Function TestSimpleIndexerTest() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestAttributedIndexerTest() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestParameterWithConversionOperatorTest() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestLocalVBVariableType() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestLocalVBRangeTypeVariable() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestLocalVBVariableWithinBlockType() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestLocalVariableIndexTest() As Task - Using testState = ProgressionTestState.Create( - - - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestGenericArgumentsTest() As Task - Using testState = ProgressionTestState.Create( - - - - { - public class MgtpTestInnerClass - { - public void MgtpTestMethod( - T1 p1, - T2[][,] p2, - T3 p3, - T4[][,] p4, - T5 p5, - MgtpTestOuterClass> p6) - { - } - - public void MgtpTestMethod2( - T1 p1, - T2[][,] p2, - T3 p3, - T4[][,] p4, - T5 p5, - MgtpTestOuterClass> p6) - { - } - } - } - } - ]]> - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestGenericArgumentsTest2() As Task - Using testState = ProgressionTestState.Create( - - - - { - public class MgtpTestInnerClass - { - public void MgtpTestMethod( - T1 p1, - T2[][,] p2, - T3 p3, - T4[][,] p4, - T5 p5, - MgtpTestOuterClass> p6) - { - } - - public void MgtpTestMethod2( - T1 p1, - T2[][,] p2, - T3 p3, - T4[][,] p4, - T5 p5, - MgtpTestOuterClass> p6) - { - } - } - } - } - ]]> - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestGenericCSharpMethodSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - { - void $$Goo() {} - } - ]]> - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("Goo() : void", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("Goo", graphNode.Label) - End Using - End Function - - - Public Async Function TestGenericCSharpTypeSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - { - } - ]]> - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("C", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("C", graphNode.Label) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestGenericCSharpMethodTypeSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - (T param); - } - ]]> - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestGenericVBMethodSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - - Module Module1 - Public Class Goo(Of T) - Public Sub $$Goo(ByVal x As T) - End Sub - End Class - End Module - - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("Goo(T)", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("Goo", graphNode.Label) - End Using - End Function - - - Public Async Function TestGenericVBTypeSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - - Module Module1 - Public Class $$Goo(Of T) - End Class - End Module - - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("Goo(Of T)", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("Goo(Of T)", graphNode.Label) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestMultiGenericVBTypeSymbolTest() As Task - Using testState = ProgressionTestState.Create( - - - - Module Module1 - Public Class $$Goo(Of T, X) - End Class - End Module - - - ) - - Dim graphNode = (Await testState.GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - Assert.Equal("Goo(Of T, X)", formattedLabelExtension.Label(graphNode, GraphCommandDefinition.Contains.Id)) - Assert.Equal("Goo(Of T, X)", graphNode.Label) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestFilteringPropertiesTest() As Task - Using testState = ProgressionTestState.Create( - - - -Public Class TestEvents - Public Custom Event CustomEvent As EventHandler(Of Object) - AddHandler($$value As EventHandler(Of Object)) - - End AddHandler - - RemoveHandler(value As EventHandler(Of Object)) - - End RemoveHandler - - RaiseEvent(sender As Object, e As Object) - - End RaiseEvent - End Event -End Class - - - ) - - Dim symbol = Await testState.GetMarkedSymbolAsync() - Dim graph = New Graph() - Await graph.CreateNodeAsync(symbol, testState.GetSolution(), CancellationToken.None) - AssertSimplifiedGraphIs(graph, - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/GraphNodeIdTests.vb b/src/VisualStudio/Core/Test/Progression/GraphNodeIdTests.vb deleted file mode 100644 index 79e1b53c52cbf..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/GraphNodeIdTests.vb +++ /dev/null @@ -1,133 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class GraphNodeIdTests - Private Shared Async Function AssertMarkedNodeIdIsAsync(code As String, expectedId As String, Optional language As String = "C#", Optional symbolTransform As Func(Of ISymbol, ISymbol) = Nothing) As Task - Using testState = ProgressionTestState.Create( - - CommonReferences="true" FilePath="Z:\Project.csproj"> - - <%= code %> - - - ) - - Dim graph = await testState.GetGraphWithMarkedSymbolNodeAsync(symbolTransform) - Dim node = graph.Nodes.Single() - Assert.Equal(expectedId, node.Id.ToString()) - End Using - End Function - - - Public Async Function TestSimpleType() As Task - Await AssertMarkedNodeIdIsAsync("namespace N { class $$C { } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C)") - End Function - - - Public Async Function TestNestedType() As Task - Await AssertMarkedNodeIdIsAsync("namespace N { class C { class $$E { } } }", "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=E ParentType=C))") - End Function - - - Public Async Function TestMemberWithSimpleArrayType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(int[] p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=(Name=Int32 ArrayRank=1 ParentType=Int32))]))") - End Function - - - Public Async Function TestMemberWithNestedArrayType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(int[][,] p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=(Name=Int32 ArrayRank=1 ParentType=(Name=Int32 ArrayRank=2 ParentType=Int32)))]))") - End Function - - - Public Async Function TestMemberWithPointerType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { struct S { } unsafe void $$M(S** p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=S Indirection=2 ParentType=C))]))") - End Function - - - Public Async Function TestMemberWithVoidPointerType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { unsafe void $$M(void* p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=(Name=Void Indirection=1))]))") - End Function - - - Public Async Function TestMemberWithGenericTypeParameters() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(T t, U u) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=C GenericParameterCount=1) Member=(Name=M GenericParameterCount=1 OverloadingParameters=[(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=C GenericParameterCount=1) ParameterIdentifier=0),(ParameterIdentifier=0)]))") - End Function - - - Public Async Function TestMemberWithParameterTypeConstructedWithMemberTypeParameter() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(T t, System.Func u) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M GenericParameterCount=1 OverloadingParameters=[(ParameterIdentifier=0),(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=(Name=Func GenericParameterCount=2 GenericArguments=[(ParameterIdentifier=0),(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32)]))]))") - End Function - - - Public Async Function TestMemberWithArraysOfGenericTypeParameters() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(T[] t, U[] u) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=C GenericParameterCount=1) Member=(Name=M GenericParameterCount=1 OverloadingParameters=[(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(ArrayRank=1 ParentType=(Type=(Name=C GenericParameterCount=1) ParameterIdentifier=0))),(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(ArrayRank=1 ParentType=(ParameterIdentifier=0)))]))") - End Function - - - Public Async Function TestMemberWithArraysOfGenericTypeParameters2() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(T[][,] t, U[][,] u) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(Name=C GenericParameterCount=1) Member=(Name=M GenericParameterCount=1 OverloadingParameters=[(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(ArrayRank=1 ParentType=(ArrayRank=2 ParentType=(Type=(Name=C GenericParameterCount=1) ParameterIdentifier=0)))),(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=(ArrayRank=1 ParentType=(ArrayRank=2 ParentType=(ParameterIdentifier=0))))]))") - End Function - - - Public Async Function TestMemberWithGenericType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(System.Collections.Generic.List p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System.Collections.Generic Type=(Name=List GenericParameterCount=1 GenericArguments=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System Type=Int32)]))]))") - End Function - - - Public Async Function TestMemberWithDynamicType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(dynamic d) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Namespace=System Type=Object)]))") - End Function - - - Public Async Function TestMemberWithGenericTypeOfDynamicType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(System.Collections.Generic.List p) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Assembly=file:///Z:/FxReferenceAssembliesUri Namespace=System.Collections.Generic Type=(Name=List GenericParameterCount=1 GenericArguments=[(Namespace=System Type=Object)]))]))") - End Function - - - Public Async Function TestMemberWithArrayOfDynamicType() As Task - Await AssertMarkedNodeIdIsAsync( - "namespace N { class C { void $$M(dynamic[] d) { } } }", - "(Assembly=file:///Z:/bin/CSharpAssembly1.dll Namespace=N Type=C Member=(Name=M OverloadingParameters=[(Namespace=System Type=(Name=Object ArrayRank=1 ParentType=Object))]))") - End Function - - - Public Async Function TestErrorType() As Task - Await AssertMarkedNodeIdIsAsync( - "Class $$C : Inherits D : End Class", - "Type=D", - LanguageNames.VisualBasic, - Function(s) DirectCast(s, INamedTypeSymbol).BaseType) - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb b/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb deleted file mode 100644 index 98c8bcef7d1f1..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb +++ /dev/null @@ -1,37 +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. - -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.GraphModel.Schemas -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Moq -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class GraphProviderTests - - Public Sub TestGetContainsGraphQueries() - Dim context = CreateGraphContext(GraphContextDirection.Contains, Array.Empty(Of GraphCategory)()) - Dim queries = RoslynGraphProvider.GetGraphQueries(context) - Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) - End Sub - - - Public Sub TestGetContainsGraphQueriesWithTarget() - Dim context = CreateGraphContext(GraphContextDirection.Target, {CodeLinkCategories.Contains}) - Dim queries = RoslynGraphProvider.GetGraphQueries(context) - Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) - End Sub - - Private Shared Function CreateGraphContext(direction As GraphContextDirection, linkCategories As IEnumerable(Of GraphCategory)) As IGraphContext - Dim context = New Mock(Of IGraphContext)(MockBehavior.Strict) - context.Setup(Function(x) x.Direction).Returns(direction) - context.Setup(Function(x) x.LinkCategories).Returns(linkCategories) - Return context.Object - End Function - End Class - -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ImplementedByGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ImplementedByGraphQueryTests.vb deleted file mode 100644 index fa776780712f6..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ImplementedByGraphQueryTests.vb +++ /dev/null @@ -1,73 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - - Public Class ImplementedByGraphQueryTests - - Public Async Function TestImplementedBy1() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -interface $$IBlah { -} - -abstract class Base -{ - public abstract int CompareTo(object obj); -} - -class Goo : Base, IComparable, IBlah -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - -class Goo2 : Base, IBlah -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ImplementedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - End Class - -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ImplementsGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ImplementsGraphQueryTests.vb deleted file mode 100644 index 1cce66f485dc7..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ImplementsGraphQueryTests.vb +++ /dev/null @@ -1,87 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - - Public Class ImplementsGraphQueryTests - - Public Async Function TestClassImplementsInterface1() As Task - Using testState = ProgressionTestState.Create( - - - - class $$C : System.IDisposable { } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ImplementsGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestMethodImplementsInterfaceMethod1() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -class Goo : IComparable -{ - public int $$CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New ImplementsGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/InheritedByGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/InheritedByGraphQueryTests.vb deleted file mode 100644 index 333c3bbf6ef51..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/InheritedByGraphQueryTests.vb +++ /dev/null @@ -1,238 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - - Public Class InheritedByGraphQueryTests - - Public Async Function TestInheritedByClassesCSharp() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -interface IBlah { -} - -abstract class $$Base -{ - public abstract int CompareTo(object obj); -} - -class Goo : Base, IComparable, IBlah -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - -class Goo2 : Base, IBlah -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - -class ReallyDerived : Goo // should not be shown as inherited by Base -{ -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestInheritedByInterfacesCSharp() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -public interface $$I { } - -public class C : I { } // should appear as being derived from (implementing) I - -public class C2 : C { } // should not appear as being derived from (implementing) I - -interface I2 : I, IComparable -{ - void M(); -} - -interface I3 : I2 // should not be shown as inherited by I -{ -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestInheritedByClassesVisualBasic() As Task - Using testState = ProgressionTestState.Create( - - - -Imports System - -Interface IBlah -End Interface - -MustInherit Class $$Base - Public MustOverride Function CompareTo(obj As Object) As Integer -End Class - -Class Goo - Inherits Base - Implements IComparable, IBlah - Public Overrides Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo - Throw New NotImplementedException() - End Function -End Class - -Class Goo2 - Inherits Base - Implements IBlah - Public Overrides Function CompareTo(obj As Object) As Integer - Throw New NotImplementedException() - End Function -End Class - -Class ReallyDerived ' should not be shown as inherited by Base - Inherits Goo -End Class - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestInheritedByInterfacesVisualBasic() As Task - Using testState = ProgressionTestState.Create( - - - -Imports System - -Public Interface $$I -End Interface - -Public Class C ' should appear as being derived from (implementing) I - Implements I -End Class - -Public Class C2 ' should not appear as being derived from (implementing) I - Inherits C -End Class - -Interface I2 - Inherits I, IComparable - Sub M() -End Interface - -Interface I3 ' should not be shown as inherited by I - Inherits I2 -End Interface - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - ) - End Using - End Function - End Class - -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb deleted file mode 100644 index 1faa562860c9e..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb +++ /dev/null @@ -1,119 +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. - -Imports System.IO -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.LanguageServer -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class InheritsGraphQueryTests - - Public Async Function BaseTypesOfSimpleType() As Task - Using testState = ProgressionTestState.Create( - - - - class $$C : System.IDisposable { } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritsGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestErrorBaseType() As Task - Using testState = ProgressionTestState.Create( - - - - class $$C : A { } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritsGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestSolutionWithMultipleProjects() As Task - Using testState = ProgressionTestState.Create( - - - public class A { } - - - ProjectA - public class B : A { } - - - ProjectB - public class C : B$$ { } - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New InheritsGraphQuery(), GraphContextDirection.Target) - - Dim dirUri = ProtocolConversions.GetAbsoluteUriString(Path.Combine(TestWorkspace.RootDirectory, "bin")) & "/" - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - /> - /> - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/IsCalledByGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/IsCalledByGraphQueryTests.vb deleted file mode 100644 index e25024ffc3003..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/IsCalledByGraphQueryTests.vb +++ /dev/null @@ -1,65 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class IsCalledByGraphQueryTests - - Public Async Function IsCalledBySimpleTests() As Task - Using testState = ProgressionTestState.Create( - - - - class A - { - public $$A() { } - public virtual void Run() { } - } - - class B : A - { - public B() { } - override public void Run() { var x = new A(); x.Run(); } - } - - class C - { - public C() { } - public void Goo() - { - var x = new B(); - x.Run(); - } - } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New IsCalledByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/IsUsedByGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/IsUsedByGraphQueryTests.vb deleted file mode 100644 index 7c1914ffe879c..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/IsUsedByGraphQueryTests.vb +++ /dev/null @@ -1,59 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class IsUsedByGraphQueryTests - - Public Async Function IsUsedByTests() As Task - Using testState = ProgressionTestState.Create( - - - - public class C { - public int $$X; - public int Y = X * X; - public void M() { - int x = 10; - int y = x + X; - } - } - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New IsUsedByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb b/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb deleted file mode 100644 index a534cee5a3462..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/MockGraphContext.vb +++ /dev/null @@ -1,121 +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. - -Imports System.Threading -Imports Microsoft.VisualStudio.GraphModel - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - Friend Class MockGraphContext - Implements IGraphContext - - Private ReadOnly _direction As GraphContextDirection - Private ReadOnly _graph As Graph - Private ReadOnly _inputNodes As ISet(Of GraphNode) - Private ReadOnly _outputNodes As New HashSet(Of GraphNode) - - Public Sub New(direction As GraphContextDirection, graph As Graph, inputNodes As IEnumerable(Of GraphNode)) - _direction = direction - _graph = graph - _inputNodes = New HashSet(Of GraphNode)(inputNodes) - End Sub - - Public Event Canceled(sender As Object, e As EventArgs) Implements IGraphContext.Canceled - - Public ReadOnly Property CancelToken As CancellationToken Implements IGraphContext.CancelToken - Get - - End Get - End Property - - Public Event Completed(sender As Object, e As EventArgs) Implements IGraphContext.Completed - - Public ReadOnly Property Direction As GraphContextDirection Implements IGraphContext.Direction - Get - Return _direction - End Get - End Property - - Public ReadOnly Property Errors As IEnumerable(Of Exception) Implements IGraphContext.Errors - Get - Throw New NotImplementedException() - End Get - End Property - - Public Function GetValue(Of T)(name As String) As T Implements IGraphContext.GetValue - Return Nothing - End Function - - Public Property Graph As Graph Implements IGraphContext.Graph - Get - Return _graph - End Get - - Set(value As Graph) - Throw New NotImplementedException() - End Set - End Property - - Public Function HasValue(name As String) As Boolean Implements IGraphContext.HasValue - Return False - End Function - - Public ReadOnly Property InputNodes As ISet(Of GraphNode) Implements IGraphContext.InputNodes - Get - Return _inputNodes - End Get - End Property - - Public ReadOnly Property LinkCategories As IEnumerable(Of GraphCategory) Implements IGraphContext.LinkCategories - Get - Throw New NotImplementedException() - - End Get - End Property - - Public ReadOnly Property LinkDepth As Integer Implements IGraphContext.LinkDepth - Get - Return 1 - End Get - End Property - - Public ReadOnly Property NodeCategories As IEnumerable(Of GraphCategory) Implements IGraphContext.NodeCategories - Get - Throw New NotImplementedException() - End Get - End Property - - Public Sub OnCompleted() Implements IGraphContext.OnCompleted - End Sub - - Public ReadOnly Property OutputNodes As ISet(Of GraphNode) Implements IGraphContext.OutputNodes - Get - Return _outputNodes - End Get - End Property - - Public Sub ReportError(exception As Exception) Implements IGraphContext.ReportError - - End Sub - - Public Sub ReportProgress(current As Integer, maximum As Integer, message As String) Implements IGraphContext.ReportProgress - - End Sub - - Public ReadOnly Property RequestedProperties As IEnumerable(Of GraphProperty) Implements IGraphContext.RequestedProperties - Get - Throw New NotImplementedException() - End Get - End Property - - Public Sub SetValue(Of T)(name As String, value As T) Implements IGraphContext.SetValue - - End Sub - - Public ReadOnly Property TrackChanges As Boolean Implements IGraphContext.TrackChanges - Get - Return False - End Get - End Property - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/OverriddenByGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/OverriddenByGraphQueryTests.vb deleted file mode 100644 index 2dc28dc9c5bb0..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/OverriddenByGraphQueryTests.vb +++ /dev/null @@ -1,102 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - - Public Class OverriddenByGraphQueryTests - - Public Async Function TestOverriddenByMethod1() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -abstract class Base -{ - public abstract int $$CompareTo(object obj); -} - -class Goo : Base, IComparable -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New OverriddenByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestOverriddenByMethod2() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -abstract class Base -{ - public abstract int CompareTo(object obj); -} - -class Goo : Base, IComparable -{ - public override int $$CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New OverriddenByGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/OverridesGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/OverridesGraphQueryTests.vb deleted file mode 100644 index ef9f77e03ca9d..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/OverridesGraphQueryTests.vb +++ /dev/null @@ -1,102 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - - Public Class OverridesGraphQueryTests - - Public Async Function TestOverridesMethod1() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -abstract class Base -{ - public abstract int $$CompareTo(object obj); -} - -class Goo : Base, IComparable -{ - public override int CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New OverridesGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function TestOverridesMethod2() As Task - Using testState = ProgressionTestState.Create( - - - -using System; - -abstract class Base -{ - public abstract int CompareTo(object obj); -} - -class Goo : Base, IComparable -{ - public override int $$CompareTo(object obj) - { - throw new NotImplementedException(); - } -} - - - ) - - Dim inputGraph = Await testState.GetGraphWithMarkedSymbolNodeAsync() - Dim outputContext = Await testState.GetGraphContextAfterQuery(inputGraph, New OverridesGraphQuery(), GraphContextDirection.Target) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ProgressionTestHelpers.vb b/src/VisualStudio/Core/Test/Progression/ProgressionTestHelpers.vb deleted file mode 100644 index 63a1bccc7a9be..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ProgressionTestHelpers.vb +++ /dev/null @@ -1,52 +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. - -Imports System.Runtime.CompilerServices -Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.Composition -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.CSharp.Progression -Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.Progression -Imports - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - Friend Module ProgressionTestHelpers - - Public Function ToSimplifiedXDocument(graph As Graph) As XDocument - Dim document = XDocument.Parse(graph.ToXml(graphNodeIdAliasThreshold:=1000000)) - - document.Root..Remove() - document.Root..Remove() - document.Root..Remove() - - For Each node In document.Descendants(XName.Get("Node", "http://schemas.microsoft.com/vs/2009/dgml")) - Dim attribute = node.Attribute("SourceLocation") - If attribute IsNot Nothing Then - attribute.Remove() - End If - Next - - Return document - End Function - - Public Sub AssertSimplifiedGraphIs(graph As Graph, xml As XElement) - Dim graphXml = graph.ToSimplifiedXDocument() - If Not XNode.DeepEquals(graphXml.Root, xml) Then - ' They aren't equal, so therefore the text representations definitely aren't equal. - ' We'll Assert.Equal those, so that way xunit will show nice before/after text - 'Assert.Equal(xml.ToString(), graphXml.ToString()) - - ' In an attempt to diagnose some flaky tests, the whole contents of both objects will be output - Throw New Exception($"Graph XML was not equal, check for out-of-order elements. -Expected: -{xml.ToString()} - -Actual: -{graphXml.ToString()} -") - End If - End Sub - End Module -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb b/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb deleted file mode 100644 index 636f822ce01a5..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/ProgressionTestState.vb +++ /dev/null @@ -1,87 +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. - -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.FindSymbols -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - Friend Class ProgressionTestState - Implements IDisposable - - Public ReadOnly Workspace As EditorTestWorkspace - - Public Sub New(workspace As EditorTestWorkspace) - Me.Workspace = workspace - End Sub - - Public Shared Function Create(workspaceXml As XElement) As ProgressionTestState - Dim workspace = EditorTestWorkspace.Create(workspaceXml, composition:=VisualStudioTestCompositions.LanguageServices) - - Return New ProgressionTestState(workspace) - End Function - - Public Function GetGraphWithDocumentNode(filePath As String) As Graph - Dim graphBuilder As New GraphBuilder(Workspace.CurrentSolution) - Dim documentId = Workspace.Documents.Single(Function(d) d.FilePath = filePath).Id - Assert.NotNull(graphBuilder.TryAddNodeForDocument(Workspace.CurrentSolution.GetDocument(documentId), CancellationToken.None)) - Return graphBuilder.Graph - End Function - - Public Async Function GetGraphWithMarkedSymbolNodeAsync(Optional symbolTransform As Func(Of ISymbol, ISymbol) = Nothing) As Task(Of Graph) - Dim hostDocument As TestHostDocument = Workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim document = Workspace.CurrentSolution.GetDocument(hostDocument.Id) - Dim symbol = Await GetMarkedSymbolAsync() - - If symbolTransform IsNot Nothing Then - symbol = symbolTransform(symbol) - End If - - Dim graphBuilder As New GraphBuilder(Workspace.CurrentSolution) - Await graphBuilder.AddNodeAsync(symbol, document.Project, document, CancellationToken.None) - Return graphBuilder.Graph - End Function - - Public Async Function GetGraphContextAfterQuery(graph As Graph, graphQuery As IGraphQuery, direction As GraphContextDirection) As Task(Of IGraphContext) - Dim graphContext As New MockGraphContext(direction, graph.Copy(), graph.Nodes) - Dim graphBuilder = Await graphQuery.GetGraphAsync(Workspace.CurrentSolution, graphContext, CancellationToken.None) - graphBuilder.ApplyToGraph(graphContext.Graph, CancellationToken.None) - - Return graphContext - End Function - - Public Async Function GetGraphContextAfterQueryWithSolution(graph As Graph, solution As Solution, graphQuery As IGraphQuery, direction As GraphContextDirection) As Task(Of IGraphContext) - Dim graphContext As New MockGraphContext(direction, graph.Copy(), graph.Nodes) - Dim graphBuilder = Await graphQuery.GetGraphAsync(solution, graphContext, CancellationToken.None) - graphBuilder.ApplyToGraph(graphContext.Graph, CancellationToken.None) - - Return graphContext - End Function - - Private Sub Dispose() Implements IDisposable.Dispose - Workspace.Dispose() - End Sub - - Public Async Function AssertMarkedSymbolLabelIsAsync(graphCommandId As String, label As String, description As String) As Task - Dim graphNode = (Await GetGraphWithMarkedSymbolNodeAsync()).Nodes.Single() - Dim formattedLabelExtension As New GraphFormattedLabelExtension() - - Assert.Equal(label, formattedLabelExtension.Label(graphNode, graphCommandId)) - Assert.Equal(description, formattedLabelExtension.Description(graphNode, graphCommandId)) - End Function - - Public Function GetMarkedSymbolAsync() As Task(Of ISymbol) - Dim hostDocument As TestHostDocument = Workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim document = Workspace.CurrentSolution.GetDocument(hostDocument.Id) - Return SymbolFinder.FindSymbolAtPositionAsync(document, hostDocument.CursorPosition.Value) - End Function - - Public Function GetSolution() As Solution - Return Workspace.CurrentSolution - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb deleted file mode 100644 index 7b2bca02f1228..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb +++ /dev/null @@ -1,409 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.NavigateTo -Imports Microsoft.CodeAnalysis.Shared.TestHooks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class SearchGraphQueryTests_NavigateTo - - Public Async Function SearchForType() As Task - Using testState = ProgressionTestState.Create( - - - - class C { } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForNestedType() As Task - Using testState = ProgressionTestState.Create( - - - - class C { class F { } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("F", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForMember() As Task - Using testState = ProgressionTestState.Create( - - - - class C { void M(); } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("M", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForPartialType() As Task - Using testState = ProgressionTestState.Create( - - - -Namespace N - Partial Class C - Sub Goo() - End Sub - End Class -End Namespace - - -Namespace N - Partial Class C - Sub Bar() - End Sub - End Class -End Namespace - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForMethodInPartialType() As Task - Using testState = ProgressionTestState.Create( - - - -Namespace N - Partial Class C - Sub Goo() - End Sub - End Class -End Namespace - - -Namespace N - Partial Class C - Sub Bar() - End Sub - End Class -End Namespace - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("Goo", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchWithResultsAcrossMultipleTypeParts() As Task - Using testState = ProgressionTestState.Create( - - - -Namespace N - Partial Class C - Sub ZGoo() - End Sub - End Class -End Namespace - - -Namespace N - Partial Class C - Sub ZBar() - End Sub - End Class -End Namespace - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("Z", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForDottedName1() As Task - Using testState = ProgressionTestState.Create( - - - - class Dog { void Bark() { } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForDottedName2() As Task - Using testState = ProgressionTestState.Create( - - - - class Dog { void Bark() { } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - ) - End Using - End Function - - - Public Async Function SearchForDottedName3() As Task - Using testState = ProgressionTestState.Create( - - - - namespace Animal { class Dog<X> { void Bark() { } } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchForDottedName4() As Task - Using testState = ProgressionTestState.Create( - - - - namespace Animal { class Dog<X> { void Bark() { } } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("A.D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - - - - - - - - - - ) - End Using - End Function - - - Public Async Function SearchWithNullFilePathsOnProject() As Task - Using testState = ProgressionTestState.Create( - - > - - namespace Animal { class Dog<X> { void Bark() { } } } - - - ) - - Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("A.D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) - - ' When searching, don't descend into projects with a null FilePath because they are artifacts and not - ' representable in the Solution Explorer, e.g., Venus projects create sub-projects with a null file - ' path for each .aspx file. Documents, on the other hand, are never allowed to have a null file path - ' and as such are not tested here. The project/document structure for these scenarios would look - ' similar to this: - ' - ' Project: SomeVenusProject, FilePath=C:\path\to\project.csproj - ' + Document: SomeVenusDocument.aspx, FilePath=C:\path\to\SomeVenusDocument.aspx - ' + Project: 1_SomeNamespace_SomeVenusDocument.aspx, FilePath=null <- the problem is here - ' + Document: SomeVenusDocument.aspx.cs - AssertSimplifiedGraphIs( - outputContext.Graph, - - - - ) - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/Progression/VisualBasicSymbolLabelTests.vb b/src/VisualStudio/Core/Test/Progression/VisualBasicSymbolLabelTests.vb deleted file mode 100644 index e12ac6dacd82a..0000000000000 --- a/src/VisualStudio/Core/Test/Progression/VisualBasicSymbolLabelTests.vb +++ /dev/null @@ -1,82 +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. - -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.GraphModel -Imports Roslyn.Test.Utilities - -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression - - Public Class VisualBasicSymbolLabelTests - - Public Async Function TestMethodWithOptionalParameter() As Task - Using testState = ProgressionTestState.Create( - - - - Class C - Sub $$S(Optional i As Integer = 42) - End Sub - End Class - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "S([Integer])", "C.S([Integer])") - End Using - End Function - - - Public Async Function TestMethodWithByRefParameter() As Task - Using testState = ProgressionTestState.Create( - - - - Class C - Sub $$S(ByRef i As Integer) - End Sub - End Class - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "S(ByRef Integer)", "C.S(ByRef Integer)") - End Using - End Function - - - Public Async Function TestEnumMember() As Task - Using testState = ProgressionTestState.Create( - - - - Enum E - $$M - End Enum - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "M", "E.M") - End Using - End Function - - - Public Async Function TestGenericType() As Task - Using testState = ProgressionTestState.Create( - - - - Class $$C(Of T) - End Class - - - ) - - Await testState.AssertMarkedSymbolLabelIsAsync(GraphCommandDefinition.Contains.Id, "C(Of T)", "C(Of T)") - End Using - End Function - End Class -End Namespace diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs index 53c584f5b439a..a452462f33c42 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs @@ -162,8 +162,6 @@ public override async Task InitializeAsync() await TestServices.Workarounds.RemoveConflictingKeyBindingsAsync(HangMitigatingCancellationToken); await TestServices.StateReset.ResetGlobalOptionsAsync(HangMitigatingCancellationToken); await TestServices.StateReset.ResetHostSettingsAsync(HangMitigatingCancellationToken); - - await TestServices.Workarounds.WaitForGitHubCoPilotAsync(HangMitigatingCancellationToken); } public override async Task DisposeAsync() diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpImmediate.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpImmediate.cs index d67a67dea5fcb..04001f04d0cc7 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpImmediate.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpImmediate.cs @@ -47,12 +47,13 @@ static void Main(string[] args) await TestServices.Debugger.SetBreakpointAsync(ProjectName, "Program.cs", "}", HangMitigatingCancellationToken); await TestServices.Debugger.GoAsync(waitForBreakMode: true, HangMitigatingCancellationToken); await TestServices.ImmediateWindow.ShowAsync(HangMitigatingCancellationToken); - await TestServices.ImmediateWindow.ClearAllAsync(HangMitigatingCancellationToken); - await TestServices.Input.SendWithoutActivateAsync("?n", HangMitigatingCancellationToken); + var existingText = await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken); await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.CompletionSet, HangMitigatingCancellationToken); - await TestServices.Input.SendWithoutActivateAsync(["1", VirtualKeyCode.TAB, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); + await TestServices.Input.SendWithoutActivateAsync("?", HangMitigatingCancellationToken); + await TestServices.Input.SendWithoutActivateAsync(["n1", VirtualKeyCode.TAB, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); + var immediateText = await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken); // Skip checking the EE result "42" (see https://github.com/dotnet/roslyn/issues/75456), without // skipping the test completely (see https://github.com/dotnet/roslyn/issues/75478). - Assert.Contains("?n1Var\r\n", await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken)); + Assert.Contains("?n1Var\r\n", immediateText.Substring(existingText.Length)); } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs index f41aae591ed0c..aa1e6c3957241 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel.Design; using System.ComponentModel; +using System.ComponentModel.Design; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -1017,11 +1017,11 @@ public async Task ConfigureAsyncNavigation(AsyncNavigationKind kind, Cancellatio }; var componentModelService = await GetRequiredGlobalServiceAsync(cancellationToken); - var commandHandlers = componentModelService.DefaultExportProvider.GetExports(); - var goToImplementation = (GoToImplementationCommandHandler)commandHandlers.Single(handler => handler.Metadata.Name == PredefinedCommandHandlerNames.GoToImplementation).Value; + + var goToImplementation = componentModelService.DefaultExportProvider.GetExportedValue(); goToImplementation.GetTestAccessor().DelayHook = delayHook; - var goToBase = (GoToBaseCommandHandler)commandHandlers.Single(handler => handler.Metadata.Name == PredefinedCommandHandlerNames.GoToBase).Value; + var goToBase = componentModelService.DefaultExportProvider.GetExportedValue(); goToBase.GetTestAccessor().DelayHook = delayHook; } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkaroundsInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkaroundsInProcess.cs index 688195abf4817..7dcb16d81f1ca 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkaroundsInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkaroundsInProcess.cs @@ -66,40 +66,6 @@ await TestServices.Workspace.WaitForAllAsyncOperationsAsync( await WaitForApplicationIdleAsync(cancellationToken); } - private static bool s_gitHubCopilotWorkaroundApplied = false; - - /// - /// GitHub Copilot opens it's output window and steals focus randomly after a file is open. This forces that to happen sooner. - /// - public async Task WaitForGitHubCoPilotAsync(CancellationToken cancellationToken) - { - if (s_gitHubCopilotWorkaroundApplied) - return; - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var shell = await TestServices.Shell.GetRequiredGlobalServiceAsync(cancellationToken); - var packageGuid = new Guid("{22818076-b98c-4525-b959-c9e12ff2433c}"); - - if (ErrorHandler.Succeeded(shell.IsPackageInstalled(packageGuid, out var fInstalled)) && fInstalled != 0) - { - shell.LoadPackage(packageGuid, out _); - - var tempFile = Path.Combine(Path.GetTempPath(), "GitHubCopilotWorkaround.txt"); - File.WriteAllText(tempFile, ""); - VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, tempFile, VSConstants.LOGVIEWID.Code_guid, out _, out _, out var windowFrame, out _); - - await Task.Delay(TimeSpan.FromSeconds(10)); - - windowFrame.CloseFrame(grfSaveOptions: 0); - - // Opening a file implicitly created a "solution" so close it so other tests don't care - await TestServices.SolutionExplorer.CloseSolutionAsync(cancellationToken); - - s_gitHubCopilotWorkaroundApplied = true; - } - } - public async Task DisableAutoSurroundAsync(CancellationToken cancellationToken) { // Disable auto surround because it will exit the completion session diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicImmediate.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicImmediate.cs index 7503f233d8c17..3081cfb95799e 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicImmediate.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicImmediate.cs @@ -45,12 +45,13 @@ End Module await TestServices.Debugger.SetBreakpointAsync(ProjectName, "Module1.vb", "End Sub", HangMitigatingCancellationToken); await TestServices.Debugger.GoAsync(waitForBreakMode: true, HangMitigatingCancellationToken); await TestServices.ImmediateWindow.ShowAsync(HangMitigatingCancellationToken); - await TestServices.ImmediateWindow.ClearAllAsync(HangMitigatingCancellationToken); - await TestServices.Input.SendWithoutActivateAsync("?", HangMitigatingCancellationToken); + var existingText = await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken); await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.CompletionSet, HangMitigatingCancellationToken); + await TestServices.Input.SendWithoutActivateAsync("?", HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync(["n1", VirtualKeyCode.TAB, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); + var immediateText = await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken); // Skip checking the EE result "42" (see https://github.com/dotnet/roslyn/issues/75456), without // skipping the test completely (see https://github.com/dotnet/roslyn/issues/75478). - Assert.Contains("?n1Var\r\n", await TestServices.ImmediateWindow.GetTextAsync(HangMitigatingCancellationToken)); + Assert.Contains("?n1Var\r\n", immediateText.Substring(existingText.Length)); } } diff --git a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs index ddaf94bf6bf2c..182e63ecfbdd1 100644 --- a/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs +++ b/src/VisualStudio/IntegrationTest/TestSetup/TestExtensionErrorHandler.cs @@ -22,12 +22,6 @@ public TestExtensionErrorHandler() public void HandleError(object sender, Exception exception) { - if (exception.Message == "RemotePartyTerminated" && new System.Diagnostics.StackTrace().ToString().Contains("CodeLens") || - exception.Message == "Cannot access a disposed object.\r\nObject name: 'CodeLensHubClient'.") - { - return; - } - FatalError.ReportAndPropagate(exception); TestTraceListener.Instance.AddException(exception); } diff --git a/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj b/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj index 6a0164f31a59b..5d01ebc9e4626 100644 --- a/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj +++ b/src/VisualStudio/LiveShare/Impl/Microsoft.VisualStudio.LanguageServices.LiveShare.csproj @@ -13,15 +13,11 @@ false - - - - diff --git a/src/VisualStudio/TestUtilities2/MockServiceProvider.vb b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb index 7eee5783f3821..a9db0ba131a4e 100644 --- a/src/VisualStudio/TestUtilities2/MockServiceProvider.vb +++ b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb @@ -3,12 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.ComponentModel.Composition -Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.Internal.VisualStudio.Shell.Interop Imports Microsoft.VisualStudio.ComponentModelHost -Imports Microsoft.VisualStudio.Settings -Imports Microsoft.VisualStudio.Settings.Internal Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop Imports Moq @@ -28,8 +24,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests Private ReadOnly _exportProvider As Composition.ExportProvider Private ReadOnly _fileChangeEx As New MockVsFileChangeEx - Private ReadOnly _localRegistry As New StubLocalRegistry - Private _settingsManager As ISettingsManager Public MockMonitorSelection As IVsMonitorSelection @@ -61,22 +55,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests Case GetType(SVsFileChangeEx) Return _fileChangeEx - Case GetType(SLocalRegistry) - Return _localRegistry - - Case GetType(SVsSettingsPersistenceManager) - If _settingsManager Is Nothing Then - LoggerFactory.Reset() - _settingsManager = SettingsManagerFactory.CreateInstance(New StubSettingsManagerHost()) - End If - - Return _settingsManager - - Case GetType(SVsFeatureFlags) - ' The only places that we consume this treat it as optional, so we can skip it here, and remove this in - ' https://github.com/dotnet/roslyn/pull/69160. - Return Nothing - Case Else Throw New Exception($"{NameOf(MockServiceProvider)} does not implement {serviceType.FullName}.") End Select diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 882c0cb290ff9..af2c680a287ca 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -49,7 +49,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr ' GetType(MockDiagnosticUpdateSourceRegistrationService), ' GetType(MockWorkspaceEventListenerProvider)) - Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf _ + Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeatures _ .AddParts( GetType(FileChangeWatcherProvider), GetType(MockVisualStudioWorkspace), diff --git a/src/VisualStudio/TestUtilities2/StubVsEditorAdaptersFactoryService.vb b/src/VisualStudio/TestUtilities2/StubVsEditorAdaptersFactoryService.vb new file mode 100644 index 0000000000000..aee3d9f89a54b --- /dev/null +++ b/src/VisualStudio/TestUtilities2/StubVsEditorAdaptersFactoryService.vb @@ -0,0 +1,79 @@ +' 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. + +Imports System.ComponentModel.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.VisualStudio.Editor +Imports Microsoft.VisualStudio.OLE.Interop +Imports Microsoft.VisualStudio.Text +Imports Microsoft.VisualStudio.Text.Editor +Imports Microsoft.VisualStudio.TextManager.Interop +Imports Microsoft.VisualStudio.Utilities + + + +Friend Class StubVsEditorAdaptersFactoryService + Implements IVsEditorAdaptersFactoryService + + + + Public Sub New() + End Sub + + Public Sub SetDataBuffer(bufferAdapter As IVsTextBuffer, dataBuffer As ITextBuffer) Implements IVsEditorAdaptersFactoryService.SetDataBuffer + Throw New NotImplementedException() + End Sub + + Public Function CreateVsTextBufferAdapter(serviceProvider As IServiceProvider) As IVsTextBuffer Implements IVsEditorAdaptersFactoryService.CreateVsTextBufferAdapter + Throw New NotImplementedException() + End Function + + Public Function CreateVsTextBufferAdapter(serviceProvider As IServiceProvider, contentType As IContentType) As IVsTextBuffer Implements IVsEditorAdaptersFactoryService.CreateVsTextBufferAdapter + Throw New NotImplementedException() + End Function + + Public Function CreateVsTextBufferAdapterForSecondaryBuffer(serviceProvider As IServiceProvider, secondaryBuffer As ITextBuffer) As IVsTextBuffer Implements IVsEditorAdaptersFactoryService.CreateVsTextBufferAdapterForSecondaryBuffer + Throw New NotImplementedException() + End Function + + Public Function CreateVsTextViewAdapter(serviceProvider As IServiceProvider) As IVsTextView Implements IVsEditorAdaptersFactoryService.CreateVsTextViewAdapter + Throw New NotImplementedException() + End Function + + Public Function CreateVsTextViewAdapter(serviceProvider As IServiceProvider, roles As ITextViewRoleSet) As IVsTextView Implements IVsEditorAdaptersFactoryService.CreateVsTextViewAdapter + Throw New NotImplementedException() + End Function + + Public Function CreateVsCodeWindowAdapter(serviceProvider As IServiceProvider) As IVsCodeWindow Implements IVsEditorAdaptersFactoryService.CreateVsCodeWindowAdapter + Throw New NotImplementedException() + End Function + + Public Function CreateVsTextBufferCoordinatorAdapter() As IVsTextBufferCoordinator Implements IVsEditorAdaptersFactoryService.CreateVsTextBufferCoordinatorAdapter + Throw New NotImplementedException() + End Function + + Public Function GetDataBuffer(bufferAdapter As IVsTextBuffer) As ITextBuffer Implements IVsEditorAdaptersFactoryService.GetDataBuffer + Throw New NotImplementedException() + End Function + + Public Function GetDocumentBuffer(bufferAdapter As IVsTextBuffer) As ITextBuffer Implements IVsEditorAdaptersFactoryService.GetDocumentBuffer + Throw New NotImplementedException() + End Function + + Public Function GetWpfTextView(viewAdapter As IVsTextView) As IWpfTextView Implements IVsEditorAdaptersFactoryService.GetWpfTextView + Throw New NotImplementedException() + End Function + + Public Function GetWpfTextViewHost(viewAdapter As IVsTextView) As IWpfTextViewHost Implements IVsEditorAdaptersFactoryService.GetWpfTextViewHost + Throw New NotImplementedException() + End Function + + Public Function GetBufferAdapter(textBuffer As ITextBuffer) As IVsTextBuffer Implements IVsEditorAdaptersFactoryService.GetBufferAdapter + Throw New NotImplementedException() + End Function + + Public Function GetViewAdapter(textView As ITextView) As IVsTextView Implements IVsEditorAdaptersFactoryService.GetViewAdapter + Throw New NotImplementedException() + End Function +End Class diff --git a/src/VisualStudio/TestUtilities2/StubVsServiceExporter`1.vb b/src/VisualStudio/TestUtilities2/StubVsServiceExporter`1.vb new file mode 100644 index 0000000000000..f452db68ba4bf --- /dev/null +++ b/src/VisualStudio/TestUtilities2/StubVsServiceExporter`1.vb @@ -0,0 +1,27 @@ +' 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. + +Imports System +Imports System.ComponentModel.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.VisualStudio +Imports Microsoft.VisualStudio.Shell +Imports Microsoft.VisualStudio.Shell.Interop +Imports Microsoft.VisualStudio.Threading + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests + + + + Friend NotInheritable Class StubVsServiceExporter(Of T As Class) + Inherits StubVsServiceExporter(Of T, T) + + + + Public Sub New( + asyncServiceProvider As IAsyncServiceProvider2, joinableTaskContext As JoinableTaskContext) + MyBase.New(asyncServiceProvider, joinableTaskContext) + End Sub + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/StubVsServiceExporter`2.vb b/src/VisualStudio/TestUtilities2/StubVsServiceExporter`2.vb new file mode 100644 index 0000000000000..efe71775d2fda --- /dev/null +++ b/src/VisualStudio/TestUtilities2/StubVsServiceExporter`2.vb @@ -0,0 +1,68 @@ +' 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. + +Imports System.ComponentModel.Composition +Imports System.Threading +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.VisualStudio +Imports Microsoft.VisualStudio.Shell +Imports Microsoft.VisualStudio.Shell.Interop +Imports Microsoft.VisualStudio.Threading + +' Import Roslyn.Utilities with an alias to avoid conflicts with AsyncLazy(Of T). This implementation relies on +' AsyncLazy(Of T) from vs-threading, and not the one from Roslyn. +Imports RoslynUtilities = Roslyn.Utilities + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests + + + + Friend Class StubVsServiceExporter(Of TService As Class, TInterface As Class) + Implements IVsService(Of TService, TInterface) + + Private ReadOnly _serviceGetter As AsyncLazy(Of TInterface) + + + + Public Sub New( + asyncServiceProvider As IAsyncServiceProvider2, joinableTaskContext As JoinableTaskContext) + + _serviceGetter = New AsyncLazy(Of TInterface)( + Function() asyncServiceProvider.GetServiceAsync(Of TService, TInterface)(True, CancellationToken.None), + joinableTaskContext.Factory) + End Sub + + ''' + Public Function GetValueAsync(Optional cancellationToken As CancellationToken = Nothing) As Task(Of TInterface) Implements IVsService(Of TInterface).GetValueAsync + Return _serviceGetter.GetValueAsync(cancellationToken) + End Function + + ''' + Public Function GetValueOrNullAsync(Optional cancellationToken As CancellationToken = Nothing) As Task(Of TInterface) Implements IVsService(Of TService, TInterface).GetValueOrNullAsync + Dim value = GetValueAsync(cancellationToken) + + If value.IsCompleted Then + Return TransformResult(value) + End If + + Return value.ContinueWith( + Function(t) TransformResult(t), + CancellationToken.None, ' token is already passed to antecedent, and this is a tiny sync continuation, so no need to make it also cancelable. + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default).Unwrap() + End Function + + Private Shared Function TransformResult(task As Task(Of TInterface)) As Task(Of TInterface) + Debug.Assert(task.IsCompleted) + + If task.Status = TaskStatus.Faulted Then + ' Our caller never wants exceptions, so return a cached null value + Return RoslynUtilities.SpecializedTasks.Null(Of TInterface)() + Else + ' Whether this is cancelled or ran to completion, we return the value as-is + Return RoslynUtilities.SpecializedTasks.AsNullable(task) + End If + End Function + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb index 3502426d2846f..02f0ba17845c9 100644 --- a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb +++ b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb @@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests Private Sub New() End Sub - Public Shared ReadOnly LanguageServices As TestComposition = EditorTestCompositions.EditorFeaturesWpf. + Public Shared ReadOnly LanguageServices As TestComposition = EditorTestCompositions.EditorFeatures. AddAssemblies( GetType(ServicesVSResources).Assembly, GetType(CSharpVSResources).Assembly, @@ -26,6 +26,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests GetType(VisualStudioRemoteHostClientProvider.Factory), ' Do not use ServiceHub in VS unit tests, run services locally. GetType(IStreamingFindUsagesPresenter), ' TODO: should we be using the actual implementation (https://github.com/dotnet/roslyn/issues/46380)? GetType(HACK_ThemeColorFixer), - GetType(Implementation.Notification.VSNotificationServiceFactory)) + GetType(Implementation.Notification.VSNotificationServiceFactory), + GetType(Options.VisualStudioOptionPersisterProvider)) End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj index 491f2a1e80c77..3dd19da145884 100644 --- a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj +++ b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj @@ -56,7 +56,7 @@ - + true VSPackage Designer diff --git a/src/VisualStudio/VisualBasic/Impl/Progression/VisualBasicProgressionLanguageService.vb b/src/VisualStudio/VisualBasic/Impl/Progression/VisualBasicProgressionLanguageService.vb deleted file mode 100644 index 992dfd14e75fe..0000000000000 --- a/src/VisualStudio/VisualBasic/Impl/Progression/VisualBasicProgressionLanguageService.vb +++ /dev/null @@ -1,95 +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. - -Imports System.Composition -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Host -Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.CodeAnalysis.Shared.Extensions -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Progression - -Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Progression - - Partial Friend Class VisualBasicProgressionLanguageService - Implements IProgressionLanguageService - - - - Public Sub New() - End Sub - - Public Function GetTopLevelNodesFromDocument(root As SyntaxNode, cancellationToken As CancellationToken) As IEnumerable(Of SyntaxNode) Implements IProgressionLanguageService.GetTopLevelNodesFromDocument - ' TODO: Implement this lazily like in C#? - Dim nodes = New Stack(Of SyntaxNode)() - - Dim result = New List(Of SyntaxNode) - - nodes.Push(root) - - While nodes.Count > 0 - cancellationToken.ThrowIfCancellationRequested() - - Dim node = nodes.Pop() - - If node.Kind = SyntaxKind.ClassBlock OrElse - node.Kind = SyntaxKind.DelegateFunctionStatement OrElse - node.Kind = SyntaxKind.DelegateSubStatement OrElse - node.Kind = SyntaxKind.EnumBlock OrElse - node.Kind = SyntaxKind.ModuleBlock OrElse - node.Kind = SyntaxKind.InterfaceBlock OrElse - node.Kind = SyntaxKind.StructureBlock OrElse - node.Kind = SyntaxKind.FieldDeclaration OrElse - node.Kind = SyntaxKind.SubBlock OrElse - node.Kind = SyntaxKind.FunctionBlock OrElse - node.Kind = SyntaxKind.PropertyBlock Then - result.Add(node) - Else - For Each child In node.ChildNodes() - nodes.Push(child) - Next - End If - End While - - Return result - End Function - - Private Shared ReadOnly s_descriptionFormat As SymbolDisplayFormat = New SymbolDisplayFormat( - globalNamespaceStyle:=SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, - typeQualificationStyle:=SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, - genericsOptions:=SymbolDisplayGenericsOptions.IncludeTypeParameters, - memberOptions:=SymbolDisplayMemberOptions.IncludeParameters Or SymbolDisplayMemberOptions.IncludeContainingType, - parameterOptions:=SymbolDisplayParameterOptions.IncludeType Or SymbolDisplayParameterOptions.IncludeParamsRefOut Or SymbolDisplayParameterOptions.IncludeOptionalBrackets, - miscellaneousOptions:=SymbolDisplayMiscellaneousOptions.UseSpecialTypes) - - Public Function GetDescriptionForSymbol(symbol As ISymbol, includeContainingSymbol As Boolean) As String Implements IProgressionLanguageService.GetDescriptionForSymbol - Return GetSymbolText(symbol, False, s_descriptionFormat) - End Function - - Private Shared ReadOnly s_labelFormat As SymbolDisplayFormat = New SymbolDisplayFormat( - genericsOptions:=SymbolDisplayGenericsOptions.IncludeTypeParameters, - memberOptions:=SymbolDisplayMemberOptions.IncludeParameters Or SymbolDisplayMemberOptions.IncludeType, - parameterOptions:=SymbolDisplayParameterOptions.IncludeType Or SymbolDisplayParameterOptions.IncludeParamsRefOut Or SymbolDisplayParameterOptions.IncludeOptionalBrackets, - miscellaneousOptions:=SymbolDisplayMiscellaneousOptions.UseSpecialTypes) - - Public Function GetLabelForSymbol(symbol As ISymbol, includeContainingSymbol As Boolean) As String Implements IProgressionLanguageService.GetLabelForSymbol - Return GetSymbolText(symbol, includeContainingSymbol, s_labelFormat) - End Function - - Private Shared Function GetSymbolText(symbol As ISymbol, includeContainingSymbol As Boolean, displayFormat As SymbolDisplayFormat) As String - If symbol.Kind = SymbolKind.Field AndAlso symbol.ContainingType.TypeKind = TypeKind.Enum Then - displayFormat = displayFormat.RemoveMemberOptions(SymbolDisplayMemberOptions.IncludeType) - End If - - Dim label As String = symbol.ToDisplayString(displayFormat) - - If includeContainingSymbol AndAlso symbol.ContainingSymbol IsNot Nothing Then - label += " (" + symbol.ContainingSymbol.ToDisplayString(displayFormat) + ")" - End If - - Return label - End Function - End Class -End Namespace diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index 09d3d8e4dbe6b..9f2bb68d9c374 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -24,6 +24,8 @@ namespace Microsoft.CodeAnalysis.CSharp.FindSymbols; +using static FindSymbolsUtilities; + [ExportLanguageService(typeof(IDeclaredSymbolInfoFactoryService), LanguageNames.CSharp), Shared] internal sealed class CSharpDeclaredSymbolInfoFactoryService : AbstractDeclaredSymbolInfoFactoryService< CompilationUnitSyntax, @@ -232,15 +234,7 @@ protected override void AddLocalFunctionInfos( fullyQualifiedContainerName, typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword), typeDeclaration.AttributeLists.Any(), - typeDeclaration.Kind() switch - { - SyntaxKind.ClassDeclaration => DeclaredSymbolInfoKind.Class, - SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface, - SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct, - SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record, - SyntaxKind.RecordStructDeclaration => DeclaredSymbolInfoKind.RecordStruct, - _ => throw ExceptionUtilities.UnexpectedValue(typeDeclaration.Kind()), - }, + GetDeclaredSymbolInfoKind(typeDeclaration), GetAccessibility(container, typeDeclaration.Modifiers), typeDeclaration.Identifier.Span, GetInheritanceNames(stringTable, typeDeclaration.BaseList), @@ -600,50 +594,6 @@ protected override string GetContainerDisplayName(MemberDeclarationSyntax node) protected override string GetFullyQualifiedContainerName(MemberDeclarationSyntax node, string rootNamespace) => CSharpSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeNamespaces); - private static Accessibility GetAccessibility(SyntaxNode container, SyntaxTokenList modifiers) - { - var sawInternal = false; - foreach (var modifier in modifiers) - { - switch (modifier.Kind()) - { - case SyntaxKind.PublicKeyword: return Accessibility.Public; - case SyntaxKind.PrivateKeyword: return Accessibility.Private; - case SyntaxKind.ProtectedKeyword: return Accessibility.Protected; - case SyntaxKind.InternalKeyword: - sawInternal = true; - continue; - } - } - - if (sawInternal) - return Accessibility.Internal; - - // No accessibility modifiers: - switch (container.Kind()) - { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - // Anything without modifiers is private if it's in a class/struct declaration. - return Accessibility.Private; - case SyntaxKind.InterfaceDeclaration: - // Anything without modifiers is public if it's in an interface declaration. - return Accessibility.Public; - case SyntaxKind.CompilationUnit: - // Things are private by default in script - if (((CSharpParseOptions)container.SyntaxTree.Options).Kind == SourceCodeKind.Script) - return Accessibility.Private; - - return Accessibility.Internal; - - default: - // Otherwise it's internal - return Accessibility.Internal; - } - } - private static string GetTypeName(TypeSyntax type) { if (type is SimpleNameSyntax simpleName) diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/FindSymbolsUtilities.cs b/src/Workspaces/CSharp/Portable/FindSymbols/FindSymbolsUtilities.cs new file mode 100644 index 0000000000000..5d4db96c73a7b --- /dev/null +++ b/src/Workspaces/CSharp/Portable/FindSymbols/FindSymbolsUtilities.cs @@ -0,0 +1,74 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; + +namespace Microsoft.CodeAnalysis.CSharp.FindSymbols; + +internal static class FindSymbolsUtilities +{ + public static Accessibility GetAccessibility(SyntaxNode container, SyntaxTokenList modifiers) + { + var sawInternal = false; + foreach (var modifier in modifiers) + { + switch (modifier.Kind()) + { + case SyntaxKind.PublicKeyword: return Accessibility.Public; + case SyntaxKind.PrivateKeyword: return Accessibility.Private; + case SyntaxKind.ProtectedKeyword: return Accessibility.Protected; + case SyntaxKind.InternalKeyword: + sawInternal = true; + continue; + } + } + + if (sawInternal) + return Accessibility.Internal; + + // No accessibility modifiers: + switch (container.Kind()) + { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExtensionBlockDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + // Anything without modifiers is private if it's in a class/struct declaration. + return Accessibility.Private; + case SyntaxKind.InterfaceDeclaration: + // Anything without modifiers is public if it's in an interface declaration. + return Accessibility.Public; + case SyntaxKind.CompilationUnit: + // Things are private by default in script + if (((CSharpParseOptions)container.SyntaxTree.Options).Kind == SourceCodeKind.Script) + return Accessibility.Private; + + return Accessibility.Internal; + + default: + // Otherwise it's internal + return Accessibility.Internal; + } + } + + public static DeclaredSymbolInfoKind GetDeclaredSymbolInfoKind(TypeDeclarationSyntax typeDeclaration) + { + return typeDeclaration.Kind() switch + { + SyntaxKind.ClassDeclaration => DeclaredSymbolInfoKind.Class, + SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface, + SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct, + SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record, + SyntaxKind.RecordStructDeclaration => DeclaredSymbolInfoKind.RecordStruct, + _ => throw ExceptionUtilities.UnexpectedValue(typeDeclaration.Kind()), + }; + } +} diff --git a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj index bbced73477841..150d011eb0286 100644 --- a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj +++ b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj @@ -49,6 +49,7 @@ + diff --git a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs index ab1209fb94670..97cab33d7bedd 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.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.Threading; using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; diff --git a/src/Workspaces/Core/Portable/ChangeNamespace/IChangeNamespaceService.cs b/src/Workspaces/Core/Portable/ChangeNamespace/IChangeNamespaceService.cs index 971b57a606e9a..491404e0e62d2 100644 --- a/src/Workspaces/Core/Portable/ChangeNamespace/IChangeNamespaceService.cs +++ b/src/Workspaces/Core/Portable/ChangeNamespace/IChangeNamespaceService.cs @@ -5,11 +5,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.ChangeNamespace; internal interface IChangeNamespaceService : ILanguageService { + AbstractReducer NameReducer { get; } + /// /// Determine whether we can change the namespace for given in the document. /// Linked documents are not supported, except for a regular document in a multi-targeting project, diff --git a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs index 3d1cbab0dc203..6ea1a68682c2f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs @@ -23,7 +23,7 @@ internal abstract partial class AbstractSyntaxIndex /// that we will not try to read previously cached data from a prior version of roslyn with a different format and /// will instead regenerate all the indices with the new format. /// - private static readonly Checksum s_serializationFormatChecksum = CodeAnalysis.Checksum.Create("47"); + private static readonly Checksum s_serializationFormatChecksum = CodeAnalysis.Checksum.Create("48"); /// /// Cache of ParseOptions to a checksum for the contained diff --git a/src/Workspaces/Core/Portable/FindSymbols/TopLevelSyntaxTree/DeclaredSymbolInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/TopLevelSyntaxTree/DeclaredSymbolInfo.cs index 89f45b723e2ae..bb6492341618b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/TopLevelSyntaxTree/DeclaredSymbolInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/TopLevelSyntaxTree/DeclaredSymbolInfo.cs @@ -30,6 +30,7 @@ internal enum DeclaredSymbolInfoKind : byte Method, Module, Namespace, + Operator, Property, Record, RecordStruct, diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 62b8d4de443a2..086d2cf25dbdd 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -145,7 +145,8 @@ - + + diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 1d19cac5f4bd5..87ade22811eaf 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -68,6 +68,7 @@ internal static class FeatureAttribute public const string SolutionChecksumUpdater = nameof(SolutionChecksumUpdater); public const string SolutionCrawlerLegacy = nameof(SolutionCrawlerLegacy); public const string SolutionCrawlerUnitTesting = nameof(SolutionCrawlerUnitTesting); + public const string SolutionExplorer = nameof(SolutionExplorer); public const string SourceGenerators = nameof(SourceGenerators); public const string StringIndentation = nameof(StringIndentation); public const string Tagger = nameof(Tagger); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs index 4bafa8a44f84b..b13a1ae6cecf8 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; @@ -69,6 +70,18 @@ private sealed class BatchingDocumentCollection private readonly Func _documentTextLoaderChangedAction; private readonly WorkspaceChangeKind _documentChangedWorkspaceKind; + /// + /// An for processing updates to dynamic files. This is lazily created the first time we see + /// a change to process, since dynamic files are only used in certain Razor scenarios and most projects won't ever have one. + /// + /// + /// This is used for two reasons: first, if we have a flurry of events we want to deduplicate them. But it also ensures ordering -- if we were to get a change to + /// a dynamic file while we're already processing another change, we want to ensure that the first change is processed before the second one. Otherwise we might + /// end up with the updates being applied out of order (since we're not always holding a lock while calling to the dynamic file info provider) and we might end up with + /// an old version stuck in the workspace. + /// + private AsyncBatchingWorkQueue<(string projectSystemFilePath, string workspaceFilePath)>? _dynamicFilesToRefresh; + public BatchingDocumentCollection(ProjectSystemProject project, Func documentAlreadyInWorkspace, Action documentAddAction, @@ -213,7 +226,7 @@ public void AddDynamicFile_NoLock(IDynamicFileInfoProvider fileInfoProvider, Dyn _documentIdToDynamicFileInfoProvider.Add(documentId, fileInfoProvider); - if (_project._eventSubscriptionTracker.Add(fileInfoProvider)) + if (_project._dynamicFileInfoProvidersSubscribedTo.Add(fileInfoProvider)) { // subscribe to the event when we use this provider the first time fileInfoProvider.Updated += _project.OnDynamicFileInfoUpdated; @@ -439,55 +452,67 @@ await _project._projectSystemProjectFactory.ApplyBatchChangeToWorkspaceAsync((so /// /// Process file content changes /// - /// filepath given from project system - /// filepath used in workspace. it might be different than projectSystemFilePath + /// File path given from project system for the .cshtml file + /// File path for the equivalent .cs document used in workspace. it might be different than projectSystemFilePath. public void ProcessDynamicFileChange(string projectSystemFilePath, string workspaceFilePath) { - using (_project._gate.DisposableWait()) + InterlockedOperations.Initialize(ref _dynamicFilesToRefresh, () => { - // If our project has already been removed, this is a stale notification, and we can disregard. - if (_project.HasBeenRemoved) - { - return; - } + return new AsyncBatchingWorkQueue<(string, string)>( + TimeSpan.FromMilliseconds(200), // 200 chosen with absolutely no evidence whatsoever + ProcessDynamicFileChangesAsync, + EqualityComparer<(string, string)>.Default, // uses ordinal string comparison which is what we want + _project._projectSystemProjectFactory.WorkspaceListener, + _project._asynchronousFileChangeProcessingCancellationTokenSource.Token); + }); + + _dynamicFilesToRefresh.AddWork((projectSystemFilePath, workspaceFilePath)); + } - if (_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out var documentId)) + private async ValueTask ProcessDynamicFileChangesAsync(ImmutableSegmentedList<(string projectSystemFilePath, string workspaceFilePath)> batch, CancellationToken cancellationToken) + { + foreach (var (projectSystemPath, workspaceFilePath) in batch) + { + DocumentId? documentId; + IDynamicFileInfoProvider? fileInfoProvider; + + using (await _project._gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // 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)) - { + // If our project has already been removed, and we can disregard everything and just 'return' + if (_project.HasBeenRemoved) return; - } - Contract.ThrowIfFalse(_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)); + // For everything else, if it's not here 'continue' to the next item in the batch. + if (!_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out documentId)) + continue; - _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => + if (!_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out fileInfoProvider)) + continue; + } + + // Now that we've got all our basic data, let's fetch the new document outside the lock, since this could be expensive. + var fileInfo = await fileInfoProvider.GetDynamicFileInfoAsync( + _project.Id, _project._filePath, projectSystemPath, CancellationToken.None).ConfigureAwait(false); + Contract.ThrowIfNull(fileInfo, "We previously received a dynamic file for this path, and we're responding to a change, so we expect to get a new one."); + + await _project._projectSystemProjectFactory.ApplyChangeToWorkspaceAsync(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); - - Contract.ThrowIfNull(fileInfo, "We previously received a dynamic file for this path, and we're responding to a change, so we expect to get a new one."); + // Right now we're only supporting dynamic files as actual source files, so it's OK to call GetDocument here. + // If the document is longer present, that could mean we unloaded the project, or the dynamic file was removed while we had released the lock. + var documentToReload = w.CurrentSolution.GetDocument(documentId); - // Right now we're only supporting dynamic files as actual source files, so it's OK to call GetDocument here - var attributes = w.CurrentSolution.GetRequiredDocument(documentId).State.Attributes; + if (documentToReload is null) + return; - var documentInfo = new DocumentInfo(attributes, fileInfo.TextLoader, fileInfo.DocumentServiceProvider); + var documentInfo = new DocumentInfo(documentToReload.State.Attributes, fileInfo.TextLoader, fileInfo.DocumentServiceProvider); w.OnDocumentReloaded(documentInfo); - }); - } + }, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 4ac028169b435..4f6e9559d07bf 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -124,9 +124,9 @@ internal sealed partial class ProjectSystemProject private readonly IFileChangeContext _documentFileChangeContext; /// - /// track whether we have been subscribed to event + /// The set of dynamic file info providers we have already subscribed to. /// - private readonly HashSet _eventSubscriptionTracker = []; + private readonly HashSet _dynamicFileInfoProvidersSubscribedTo = []; /// /// Map of the original dynamic file path to the that was associated with it. @@ -1054,6 +1054,7 @@ private void OnDynamicFileInfoUpdated(object? sender, string dynamicFilePath) public void AddAnalyzerReference(string fullPath) { CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath)); + CodeAnalysisEventSource.Log.AnalyzerReferenceRequestAddToProject(fullPath, DisplayName); var mappedPaths = GetMappedAnalyzerPaths(fullPath); @@ -1084,6 +1085,7 @@ public void AddAnalyzerReference(string fullPath) // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid // removing it once we apply the batch _projectAnalyzerPaths.Add(mappedFullPath); + CodeAnalysisEventSource.Log.AnalyzerReferenceAddedToProject(mappedFullPath, DisplayName); if (!_analyzersRemovedInBatch.Remove(mappedFullPath)) _analyzersAddedInBatch.Add(mappedFullPath); @@ -1098,6 +1100,8 @@ public void RemoveAnalyzerReference(string fullPath) if (string.IsNullOrEmpty(fullPath)) throw new ArgumentException("message", nameof(fullPath)); + CodeAnalysisEventSource.Log.AnalyzerReferenceRequestRemoveFromProject(fullPath, DisplayName); + var mappedPaths = GetMappedAnalyzerPaths(fullPath); bool containsSdkCodeStyleAnalyzers; @@ -1125,6 +1129,7 @@ public void RemoveAnalyzerReference(string fullPath) foreach (var mappedFullPath in mappedPaths) { _projectAnalyzerPaths.Remove(mappedFullPath); + CodeAnalysisEventSource.Log.AnalyzerReferenceRemovedFromProject(fullPath, DisplayName); // This analyzer may be one we've just added in the same batch; in that case, just don't add it in // the first place. @@ -1175,6 +1180,7 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) if (redirectedPath == null) { redirectedPath = currentlyRedirectedPath; + CodeAnalysisEventSource.Log.AnanlyzerReferenceRedirected(redirector.GetType().Name, fullPath, redirectedPath, DisplayName); } else if (redirectedPath != currentlyRedirectedPath) { @@ -1417,12 +1423,12 @@ public void RemoveFromWorkspace() _asynchronousFileChangeProcessingCancellationTokenSource.Cancel(); // clear tracking to external components - foreach (var provider in _eventSubscriptionTracker) + foreach (var provider in _dynamicFileInfoProvidersSubscribedTo) { provider.Updated -= OnDynamicFileInfoUpdated; } - _eventSubscriptionTracker.Clear(); + _dynamicFileInfoProvidersSubscribedTo.Clear(); } _documentFileChangeContext.Dispose(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index f16b7dbf2463a..b71d70bace636 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -456,6 +456,7 @@ static bool FilterMatches(DeclaredSymbolInfo info, SymbolFilter filter) case DeclaredSymbolInfoKind.Indexer: case DeclaredSymbolInfoKind.Method: case DeclaredSymbolInfoKind.Property: + case DeclaredSymbolInfoKind.Operator: return (filter & SymbolFilter.Member) != 0; default: throw ExceptionUtilities.UnexpectedValue(info.Kind); diff --git a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs index 5600ee46d9ac2..0d58b27a46171 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs @@ -30,6 +30,9 @@ internal sealed class BuildHostProcessManager : IAsyncDisposable private readonly SemaphoreSlim _gate = new(initialCount: 1); private readonly Dictionary _processes = []; + private static string MSBuildWorkspaceDirectory => Path.GetDirectoryName(typeof(BuildHostProcessManager).Assembly.Location)!; + private static bool IsLoadedFromNuGetPackage => File.Exists(Path.Combine(MSBuildWorkspaceDirectory, "..", "..", "microsoft.codeanalysis.workspaces.msbuild.nuspec")); + public BuildHostProcessManager(ImmutableDictionary? globalMSBuildProperties = null, IBinLogPathProvider? binaryLogPathProvider = null, ILoggerFactory? loggerFactory = null) { _globalMSBuildProperties = globalMSBuildProperties ?? ImmutableDictionary.Empty; @@ -186,10 +189,7 @@ private ProcessStartInfo CreateDotNetCoreBuildHostStartInfo(string pipeName) internal static string GetNetCoreBuildHostPath() { - // The .NET Core build host is deployed as a content folder next to the application into the BuildHost-netcore path - var buildHostPath = Path.Combine(Path.GetDirectoryName(typeof(BuildHostProcessManager).Assembly.Location)!, "BuildHost-netcore", "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll"); - AssertBuildHostExists(buildHostPath); - return buildHostPath; + return GetBuildHostPath("BuildHost-netcore", "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll"); } private ProcessStartInfo CreateDotNetFrameworkBuildHostStartInfo(string pipeName) @@ -221,16 +221,34 @@ private ProcessStartInfo CreateMonoBuildHostStartInfo(string pipeName) private static string GetDotNetFrameworkBuildHostPath() { - // The .NET Framework build host is deployed as a content folder next to the application into the BuildHost-net472 path - var netFrameworkBuildHost = Path.Combine(Path.GetDirectoryName(typeof(BuildHostProcessManager).Assembly.Location)!, "BuildHost-net472", "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe"); - AssertBuildHostExists(netFrameworkBuildHost); - return netFrameworkBuildHost; + return GetBuildHostPath("BuildHost-net472", "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe"); } - private static void AssertBuildHostExists(string buildHostPath) + private static string GetBuildHostPath(string contentFolderName, string assemblyName) { + // Possible BuildHost paths are relative to where the Workspaces.MSBuild assembly was loaded. + string buildHostPath; + + if (IsLoadedFromNuGetPackage) + { + // When Workspaces.MSBuild is loaded from the NuGet package (as is the case in .NET Interactive, NCrunch, and possibly other use cases) + // the Build host is deployed under the contentFiles folder. + // + // Workspaces.MSBuild.dll Path - .nuget/packages/microsoft.codeanalysis.workspaces.msbuild/{version}/lib/{tfm}/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll + // MSBuild.BuildHost.dll Path - .nuget/packages/microsoft.codeanalysis.workspaces.msbuild/{version}/contentFiles/any/any/{contentFolderName}/{assemblyName} + + buildHostPath = Path.GetFullPath(Path.Combine(MSBuildWorkspaceDirectory, "..", "..", "contentFiles", "any", "any", contentFolderName, assemblyName)); + } + else + { + // When Workspaces.MSBuild is deployed as part of an application the build host is deployed as a content folder next to the application. + buildHostPath = Path.Combine(MSBuildWorkspaceDirectory, contentFolderName, assemblyName); + } + if (!File.Exists(buildHostPath)) throw new Exception(string.Format(WorkspaceMSBuildResources.The_build_host_could_not_be_found_at_0, buildHostPath)); + + return buildHostPath; } private void AppendBuildHostCommandLineArgumentsConfigureProcess(ProcessStartInfo processStartInfo, string pipeName) @@ -380,7 +398,8 @@ public BuildHostProcess(Process process, string pipeName, ILoggerFactory? logger _process.EnableRaisingEvents = true; _process.Exited += Process_Exited; - _process.ErrorDataReceived += Process_ErrorDataReceived; + _process.OutputDataReceived += (_, e) => LogProcessOutput(e, "stdout"); + _process.ErrorDataReceived += (_, e) => LogProcessOutput(e, "stderr"); var pipeClient = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); pipeClient.Connect(TimeOutMsNewProcess); @@ -394,7 +413,11 @@ public BuildHostProcess(Process process, string pipeName, ILoggerFactory? logger _rpcClient.Disconnected += Process_Exited; BuildHost = new RemoteBuildHost(_rpcClient); - // Call this last so our type is fully constructed before we start firing events + // Close the standard input stream so that if any build tasks were to try reading from the console, they won't deadlock waiting for input. + _process.StandardInput.Close(); + + // Call Begin*ReadLine methods last so so our type is fully constructed before we start firing events. + _process.BeginOutputReadLine(); _process.BeginErrorReadLine(); } @@ -403,14 +426,14 @@ private void Process_Exited(object? sender, EventArgs e) Disconnected?.Invoke(this, EventArgs.Empty); } - private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) + private void LogProcessOutput(DataReceivedEventArgs e, string outputName) { if (e.Data is not null) { lock (_processLogMessages) _processLogMessages.AppendLine(e.Data); - _logger?.LogTrace($"Message from Process: {e.Data}"); + _logger?.LogTrace($"Message on {outputName}: {e.Data}"); } } diff --git a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs index f2d8c7d4db940..97cebc35edcb0 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs @@ -167,7 +167,13 @@ public async Task LoadSolutionInfoAsync( throw new ArgumentNullException(nameof(solutionFilePath)); } - var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, cancellationToken).ConfigureAwait(false); + var reportingMode = GetReportingModeForUnrecognizedProjects(); + + var reportingOptions = new DiagnosticReportingOptions( + onPathFailure: reportingMode, + onLoaderFailure: reportingMode); + + var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, reportingMode, cancellationToken).ConfigureAwait(false); var projectPaths = projects.SelectAsArray(p => p.ProjectPath); using (_dataGuard.DisposableWait(cancellationToken)) @@ -175,12 +181,6 @@ public async Task LoadSolutionInfoAsync( SetSolutionProperties(absoluteSolutionPath); } - var reportingMode = GetReportingModeForUnrecognizedProjects(); - - var reportingOptions = new DiagnosticReportingOptions( - onPathFailure: reportingMode, - onLoaderFailure: reportingMode); - var buildHostProcessManager = new BuildHostProcessManager(Properties, loggerFactory: _loggerFactory); await using var _ = buildHostProcessManager.ConfigureAwait(false); diff --git a/src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.cs b/src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.cs index ebad2a17130a1..ec96f925e7e9c 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -14,12 +14,12 @@ namespace Microsoft.CodeAnalysis.MSBuild; internal partial class SolutionFileReader { - public static Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, CancellationToken cancellationToken) + public static Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken) { - return ReadSolutionFileAsync(solutionFilePath, new PathResolver(diagnosticReporter: null), cancellationToken); + return ReadSolutionFileAsync(solutionFilePath, new PathResolver(diagnosticReporter: null), diagnosticReportingMode, cancellationToken); } - public static async Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, CancellationToken cancellationToken) + public static async Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken) { Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteSolutionPath(solutionFilePath, baseDirectory: Directory.GetCurrentDirectory(), DiagnosticReportingMode.Throw, out var absoluteSolutionPath)); @@ -31,7 +31,7 @@ internal partial class SolutionFileReader throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_filter_0, solutionFilePath)); } - var projects = await TryReadSolutionFileAsync(absoluteSolutionPath, pathResolver, projectFilter, cancellationToken).ConfigureAwait(false); + var projects = await TryReadSolutionFileAsync(absoluteSolutionPath, pathResolver, projectFilter, diagnosticReportingMode, cancellationToken).ConfigureAwait(false); if (!projects.HasValue) { throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_0, absoluteSolutionPath)); @@ -40,7 +40,7 @@ internal partial class SolutionFileReader return (absoluteSolutionPath, projects.Value); } - private static async Task?> TryReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, ImmutableHashSet projectFilter, CancellationToken cancellationToken) + private static async Task?> TryReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, ImmutableHashSet projectFilter, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken) { var serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath); if (serializer == null) @@ -57,17 +57,21 @@ internal partial class SolutionFileReader var builder = ImmutableArray.CreateBuilder<(string ProjectPath, string ProjectGuid)>(); foreach (var projectModel in solutionModel.SolutionProjects) { - // If we are filtering based on a solution filter, then we need to verify the project is included. - if (!projectFilter.IsEmpty) + // If we didn't get an absolute path skip the file. The solution may have an invalid project in it, + // but we don't want to throw on that here. The path resolver will throw / report a diagnostic if it couldn't resolve the path. + if (pathResolver.TryGetAbsoluteProjectPath(projectModel.FilePath, baseDirectory, diagnosticReportingMode, out var absoluteProjectPath)) { - Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteProjectPath(projectModel.FilePath, baseDirectory, DiagnosticReportingMode.Throw, out var absoluteProjectPath)); - if (!projectFilter.Contains(absoluteProjectPath)) + // If we are filtering based on a solution filter, then we need to verify the project is included. + if (!projectFilter.IsEmpty) { - continue; + if (!projectFilter.Contains(absoluteProjectPath)) + { + continue; + } } - } - builder.Add((projectModel.FilePath, projectModel.Id.ToString())); + builder.Add((absoluteProjectPath, projectModel.Id.ToString())); + } } return builder.ToImmutable(); diff --git a/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index fb336b31260da..fb6da908cb6bc 100644 --- a/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -97,6 +97,7 @@ <_NetFrameworkBuildHostProjectReference Include="..\..\..\Workspaces\MSBuild\BuildHost\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj"> net472 + BuildHost-net472 @@ -105,6 +106,7 @@ <_NetFrameworkBuildHostProjectReference Include="..\..\..\Workspaces\MSBuild\BuildHost\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj"> $(NetRoslynBuildHostNetCoreVersion) + BuildHost-netcore @@ -118,6 +120,8 @@ + + GetUpdatesAsync(ImmutableArray GetUpdatesAsync(ImmutableArray>.Empty, - }; + results = EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjectInfos); } // Only store the solution if we have any changes to apply, otherwise CommitUpdatesAsync/DiscardUpdatesAsync won't be called. @@ -326,12 +311,19 @@ public async ValueTask GetUpdatesAsync(ImmutableArray GetProjectPaths(IEnumerable ids) + => ids.SelectAsArray(id => solution.GetRequiredProject(id).FilePath!); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { Disable(); - return new ManagedHotReloadUpdates([], []); + return new ManagedHotReloadUpdates([], [], [], []); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index e6172ce24965e..4d3382c362072 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -154,24 +154,9 @@ public ValueTask> GetDocumentDiagnosticsAsync(Che } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { - return new EmitSolutionUpdateResults.Data() - { - ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Blocked, []), - Diagnostics = GetUnexpectedUpdateError(solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(), e.Message), - RudeEdits = [], - SyntaxError = null, - ProjectsToRebuild = [], - ProjectsToRestart = ImmutableDictionary>.Empty, - }; + return EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjects); } }, cancellationToken); - - static ImmutableArray GetUnexpectedUpdateError(Project project, string message) - { - var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.CannotApplyChangesUnexpectedError); - var diagnostic = Diagnostic.Create(descriptor, Location.None, [message]); - return [DiagnosticData.Create(diagnostic, project)]; - } } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs index 4fc1dee41714e..7381441a9689c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs @@ -23,12 +23,8 @@ protected CSharpHeaderFacts() public override bool IsOnTypeHeader(SyntaxNode root, int position, bool fullHeader, [NotNullWhen(true)] out SyntaxNode? typeDeclaration) { - var node = TryGetAncestorForLocation(root, position); - typeDeclaration = node; - if (node == null) - return false; - - return IsOnHeader(root, position, node, GetLastToken()); + var node = TryGetAncestorForLocation(root, position, out typeDeclaration); + return node != null && IsOnHeader(root, position, node, GetLastToken()); SyntaxToken GetLastToken() { @@ -53,107 +49,51 @@ SyntaxToken GetLastToken() public override bool IsOnPropertyDeclarationHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? propertyDeclaration) { - var node = TryGetAncestorForLocation(root, position); - propertyDeclaration = node; - if (propertyDeclaration == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.Identifier); + var node = TryGetAncestorForLocation(root, position, out propertyDeclaration); + return node != null && IsOnHeader(root, position, node, node.Identifier); } public override bool IsOnParameterHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? parameter) { - var node = TryGetAncestorForLocation(root, position); - parameter = node; - if (parameter == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node); + var node = TryGetAncestorForLocation(root, position, out parameter); + return node != null && IsOnHeader(root, position, node, node); } public override bool IsOnMethodHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? method) { - var node = TryGetAncestorForLocation(root, position); - method = node; - if (method == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.ParameterList); + var node = TryGetAncestorForLocation(root, position, out method); + return node != null && IsOnHeader(root, position, node, node.ParameterList); } public override bool IsOnLocalFunctionHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? localFunction) { - var node = TryGetAncestorForLocation(root, position); - localFunction = node; - if (localFunction == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.ParameterList); + var node = TryGetAncestorForLocation(root, position, out localFunction); + return node != null && IsOnHeader(root, position, node, node.ParameterList); } public override bool IsOnLocalDeclarationHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? localDeclaration) { - var node = TryGetAncestorForLocation(root, position); - localDeclaration = node; - if (localDeclaration == null) - { - return false; - } - - var initializersExpressions = node!.Declaration.Variables + var node = TryGetAncestorForLocation(root, position, out localDeclaration); + return node != null && IsOnHeader(root, position, node, node, holes: node.Declaration.Variables .Where(v => v.Initializer != null) - .SelectAsArray(initializedV => initializedV.Initializer!.Value); - return IsOnHeader(root, position, node, node, holes: initializersExpressions); + .SelectAsArray(initializedV => initializedV.Initializer!.Value)); } public override bool IsOnIfStatementHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? ifStatement) { - var node = TryGetAncestorForLocation(root, position); - ifStatement = node; - if (ifStatement == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.CloseParenToken); + var node = TryGetAncestorForLocation(root, position, out ifStatement); + return node != null && IsOnHeader(root, position, node, node.CloseParenToken); } public override bool IsOnWhileStatementHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? whileStatement) { - var node = TryGetAncestorForLocation(root, position); - whileStatement = node; - if (whileStatement == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.CloseParenToken); + var node = TryGetAncestorForLocation(root, position, out whileStatement); + return node != null && IsOnHeader(root, position, node, node.CloseParenToken); } public override bool IsOnForeachHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? foreachStatement) { - var node = TryGetAncestorForLocation(root, position); - foreachStatement = node; - if (foreachStatement == null) - { - return false; - } - - RoslynDebug.AssertNotNull(node); - return IsOnHeader(root, position, node, node.CloseParenToken); + var node = TryGetAncestorForLocation(root, position, out foreachStatement); + return node != null && IsOnHeader(root, position, node, node.CloseParenToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextMutableIntervalTree.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextMutableIntervalTree.cs index 8831efc680626..a548a1ebea4b1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextMutableIntervalTree.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextMutableIntervalTree.cs @@ -18,52 +18,48 @@ namespace Microsoft.CodeAnalysis.Formatting; internal sealed class ContextMutableIntervalTree : SimpleMutableIntervalTree where TIntrospector : struct, IIntervalIntrospector { - private readonly Func _edgeExclusivePredicate; - private readonly Func _edgeInclusivePredicate; - private readonly Func _containPredicate; - public ContextMutableIntervalTree(in TIntrospector introspector) : base(introspector, values: null) { - _edgeExclusivePredicate = ContainsEdgeExclusive; - _edgeInclusivePredicate = ContainsEdgeInclusive; - _containPredicate = (value, start, end) => IntervalTreeAlgorithms>.Contains(value, start, end, in Introspector); } public T? GetSmallestEdgeExclusivelyContainingInterval(int start, int length) - => GetSmallestContainingIntervalWorker(start, length, _edgeExclusivePredicate); + => GetSmallestContainingIntervalWorker(start, length, ContainsEdgeExclusive); public T? GetSmallestEdgeInclusivelyContainingInterval(int start, int length) - => GetSmallestContainingIntervalWorker(start, length, _edgeInclusivePredicate); + => GetSmallestContainingIntervalWorker(start, length, ContainsEdgeInclusive); public T? GetSmallestContainingInterval(int start, int length) - => GetSmallestContainingIntervalWorker(start, length, _containPredicate); + => GetSmallestContainingIntervalWorker(start, length, Contains); - private bool ContainsEdgeExclusive(T value, int start, int length) + private static bool ContainsEdgeExclusive(T value, int start, int length, TIntrospector introspector) { var otherStart = start; var otherEnd = start + length; - var thisSpan = Introspector.GetSpan(value); + var thisSpan = introspector.GetSpan(value); var thisStart = thisSpan.Start; var thisEnd = thisSpan.End; return thisStart < otherStart && otherEnd < thisEnd; } - private bool ContainsEdgeInclusive(T value, int start, int length) + private static bool ContainsEdgeInclusive(T value, int start, int length, TIntrospector introspector) { var otherStart = start; var otherEnd = start + length; - var thisSpan = Introspector.GetSpan(value); + var thisSpan = introspector.GetSpan(value); var thisStart = thisSpan.Start; var thisEnd = thisSpan.End; return thisStart <= otherStart && otherEnd <= thisEnd; } - private T? GetSmallestContainingIntervalWorker(int start, int length, Func predicate) + private static bool Contains(T value, int start, int length, TIntrospector introspector) + => IntervalTreeAlgorithms>.Contains(value, start, length, introspector); + + private T? GetSmallestContainingIntervalWorker(int start, int length, Func predicate) { var result = default(T); if (root == null || MaxEndValue(root) < start) @@ -117,7 +113,7 @@ private bool ContainsEdgeInclusive(T value, int start, int length) while (spineNodes.TryPop(out currentNode)) { // check whether current node meets condition - if (predicate(currentNode.Value, start, length)) + if (predicate(currentNode.Value, start, length, Introspector)) { // hold onto best answer if (EqualityComparer.Default.Equals(result, default)) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Helpers/RemoveUnnecessaryImports/AbstractUnnecessaryImportsProvider.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Helpers/RemoveUnnecessaryImports/AbstractUnnecessaryImportsProvider.cs index 4864d75aedd43..3d70143d9f6db 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Helpers/RemoveUnnecessaryImports/AbstractUnnecessaryImportsProvider.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Helpers/RemoveUnnecessaryImports/AbstractUnnecessaryImportsProvider.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.RemoveUnnecessaryImports; @@ -26,15 +26,21 @@ public ImmutableArray GetUnnecessaryImports(SemanticModel model, Te public ImmutableArray GetUnnecessaryImports( SemanticModel model, TextSpan? span, Func? predicate, CancellationToken cancellationToken) { - if (span.HasValue) - { - // Bail out if there are no usings/imports in the filter span. - var node = model.SyntaxTree.FindNode(span, findInTrivia: false, getInnermostNodeForTie: false, cancellationToken); - if (node.FirstAncestorOrSelf() is null) - return []; - } + // Bail out if there are no usings/imports in the filter span. + if (span.HasValue && !HasImportThatIntersectsWithSpan(span.Value)) + return []; return GetUnnecessaryImports(model, predicate, cancellationToken); + + bool HasImportThatIntersectsWithSpan(TextSpan span) + { + var root = model.SyntaxTree.GetRoot(cancellationToken); + return root + .DescendantNodes(n => n.FullSpan.IntersectsWith(span)) + .Where(n => n.FullSpan.IntersectsWith(span)) + .OfType() + .Any(); + } } bool IEqualityComparer.Equals([AllowNull] TSyntaxNode x, [AllowNull] TSyntaxNode y) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/HeaderFacts/AbstractHeaderFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/HeaderFacts/AbstractHeaderFacts.cs index 1ed0432144e9a..519d3137097db 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/HeaderFacts/AbstractHeaderFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/HeaderFacts/AbstractHeaderFacts.cs @@ -38,9 +38,17 @@ public bool IsOnHeader( { Debug.Assert(ownerOfHeader.FullSpan.Contains(lastTokenOrNodeOfHeader.Span)); - var headerSpan = TextSpan.FromBounds( - start: GetStartOfNodeExcludingAttributes(root, ownerOfHeader), - end: lastTokenOrNodeOfHeader.FullSpan.End); + // In error cases, we may have a full missing header, followed by attributes. For example: + // + // [X] else { } + // + // This will be an if-statement where the `if(...)` part is entirely missing. In that case, just bail out + // as we aren't likely to produce a reasonable result here. + var startAfterAttributes = GetStartOfNodeExcludingAttributes(root, ownerOfHeader); + if (startAfterAttributes > lastTokenOrNodeOfHeader.FullSpan.End) + return false; + + var headerSpan = TextSpan.FromBounds(startAfterAttributes, lastTokenOrNodeOfHeader.FullSpan.End); // Is in header check is inclusive, being on the end edge of an header still counts if (!headerSpan.IntersectsWith(position)) @@ -63,22 +71,26 @@ public bool IsOnHeader( /// Tries to get an ancestor of a Token on current position or of Token directly to left: /// e.g.: tokenWithWantedAncestor[||]tokenWithoutWantedAncestor /// - protected TNode? TryGetAncestorForLocation(SyntaxNode root, int position) where TNode : SyntaxNode + protected TNode? TryGetAncestorForLocation(SyntaxNode root, int position, out SyntaxNode? untypedResult) where TNode : SyntaxNode { var tokenToRightOrIn = root.FindToken(position); var nodeToRightOrIn = tokenToRightOrIn.GetAncestor(); if (nodeToRightOrIn != null) { + untypedResult = nodeToRightOrIn; return nodeToRightOrIn; } // not at the beginning of a Token -> no (different) token to the left if (tokenToRightOrIn.FullSpan.Start != position && tokenToRightOrIn.RawKind != SyntaxFacts.SyntaxKinds.EndOfFileToken) { + untypedResult = null; return null; } - return tokenToRightOrIn.GetPreviousToken().GetAncestor(); + var result = tokenToRightOrIn.GetPreviousToken().GetAncestor(); + untypedResult = result; + return result; } protected int GetStartOfNodeExcludingAttributes(SyntaxNode root, SyntaxNode node) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs index be373a34a7009..f32490474b7fa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs @@ -92,33 +92,4 @@ public static ImmutableArray ToFlattenedImmutableArrayAndFree(this ArrayBu builders.Free(); } } - - public static bool HasDuplicates(this ArrayBuilder builder) - => builder.HasDuplicates(static x => x); - - public static bool HasDuplicates(this ArrayBuilder builder, Func selector) - { - switch (builder.Count) - { - case 0: - case 1: - return false; - - case 2: - return EqualityComparer.Default.Equals(selector(builder[0]), selector(builder[1])); - - default: - { - using var _ = PooledHashSet.GetInstance(out var set); - - foreach (var element in builder) - { - if (!set.Add(selector(element))) - return true; - } - - return false; - } - } - } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs index ffa34f0455ad1..0915710e67a7b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs @@ -81,6 +81,9 @@ public static ImmutableArray GetReferencedAssemblySymbols(this public static INamedTypeSymbol? ArgumentNullExceptionType(this Compilation compilation) => compilation.GetTypeByMetadataName(typeof(ArgumentNullException).FullName!); + public static INamedTypeSymbol? ArgumentOutOfRangeExceptionType(this Compilation compilation) + => compilation.GetTypeByMetadataName(typeof(ArgumentOutOfRangeException).FullName!); + public static INamedTypeSymbol? ArrayType(this Compilation compilation) => compilation.GetTypeByMetadataName(typeof(Array).FullName!); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicHeaderFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicHeaderFacts.vb index 509c474e75fd3..8952fd71e1577 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicHeaderFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicHeaderFacts.vb @@ -22,13 +22,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService position As Integer, fullHeader As Boolean, ByRef typeDeclaration As SyntaxNode) As Boolean - Dim typeBlock = TryGetAncestorForLocation(Of TypeBlockSyntax)(root, position) + Dim typeBlock = TryGetAncestorForLocation(Of TypeBlockSyntax)(root, position, typeDeclaration) If typeBlock Is Nothing Then Return Nothing End If Dim typeStatement = typeBlock.BlockStatement - typeDeclaration = typeBlock Dim lastToken = If(typeStatement.TypeParameterList?.GetLastToken(), typeStatement.Identifier) If fullHeader Then @@ -41,35 +40,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService End Function Public Overrides Function IsOnPropertyDeclarationHeader(root As SyntaxNode, position As Integer, ByRef propertyDeclaration As SyntaxNode) As Boolean - Dim node = TryGetAncestorForLocation(Of PropertyStatementSyntax)(root, position) - propertyDeclaration = node + Dim node = TryGetAncestorForLocation(Of PropertyStatementSyntax)(root, position, propertyDeclaration) - If propertyDeclaration Is Nothing Then - Return False - End If - - If node.AsClause IsNot Nothing Then + If node?.AsClause IsNot Nothing Then Return IsOnHeader(root, position, node, node.AsClause) End If - Return IsOnHeader(root, position, node, node.Identifier) + Return node IsNot Nothing AndAlso IsOnHeader(root, position, node, node.Identifier) End Function Public Overrides Function IsOnParameterHeader(root As SyntaxNode, position As Integer, ByRef parameter As SyntaxNode) As Boolean - Dim node = TryGetAncestorForLocation(Of ParameterSyntax)(root, position) - parameter = node - - If parameter Is Nothing Then - Return False - End If - - Return IsOnHeader(root, position, node, node) + Dim node = TryGetAncestorForLocation(Of ParameterSyntax)(root, position, parameter) + Return node IsNot Nothing AndAlso IsOnHeader(root, position, node, node) End Function Public Overrides Function IsOnMethodHeader(root As SyntaxNode, position As Integer, ByRef method As SyntaxNode) As Boolean - Dim node = TryGetAncestorForLocation(Of MethodStatementSyntax)(root, position) - method = node - + Dim node = TryGetAncestorForLocation(Of MethodStatementSyntax)(root, position, method) If method Is Nothing Then Return False End If @@ -91,31 +77,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService End Function Public Overrides Function IsOnLocalDeclarationHeader(root As SyntaxNode, position As Integer, ByRef localDeclaration As SyntaxNode) As Boolean - Dim node = TryGetAncestorForLocation(Of LocalDeclarationStatementSyntax)(root, position) - localDeclaration = node - - If localDeclaration Is Nothing Then - Return False - End If - - Dim initializersExpressions = node.Declarators. + Dim node = TryGetAncestorForLocation(Of LocalDeclarationStatementSyntax)(root, position, localDeclaration) + Return node IsNot Nothing AndAlso IsOnHeader(root, position, node, node, node.Declarators. Where(Function(d) d.Initializer IsNot Nothing). - SelectAsArray(Function(initialized) initialized.Initializer.Value) - Return IsOnHeader(root, position, node, node, initializersExpressions) + SelectAsArray(Function(initialized) initialized.Initializer.Value)) End Function Public Overrides Function IsOnIfStatementHeader(root As SyntaxNode, position As Integer, ByRef ifStatement As SyntaxNode) As Boolean - ifStatement = Nothing - - Dim multipleLineNode = TryGetAncestorForLocation(Of MultiLineIfBlockSyntax)(root, position) + Dim multipleLineNode = TryGetAncestorForLocation(Of MultiLineIfBlockSyntax)(root, position, ifStatement) If multipleLineNode IsNot Nothing Then - ifStatement = multipleLineNode Return IsOnHeader(root, position, multipleLineNode.IfStatement, multipleLineNode.IfStatement) End If - Dim singleLineNode = TryGetAncestorForLocation(Of SingleLineIfStatementSyntax)(root, position) + Dim singleLineNode = TryGetAncestorForLocation(Of SingleLineIfStatementSyntax)(root, position, ifStatement) If singleLineNode IsNot Nothing Then - ifStatement = singleLineNode Return IsOnHeader(root, position, singleLineNode, singleLineNode.Condition) End If @@ -123,26 +98,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService End Function Public Overrides Function IsOnWhileStatementHeader(root As SyntaxNode, position As Integer, ByRef whileStatement As SyntaxNode) As Boolean - whileStatement = Nothing - - Dim whileBlock = TryGetAncestorForLocation(Of WhileBlockSyntax)(root, position) - If whileBlock IsNot Nothing Then - whileStatement = whileBlock - Return IsOnHeader(root, position, whileBlock.WhileStatement, whileBlock.WhileStatement) - End If - - Return False + Dim whileBlock = TryGetAncestorForLocation(Of WhileBlockSyntax)(root, position, whileStatement) + Return whileBlock IsNot Nothing AndAlso IsOnHeader(root, position, whileBlock.WhileStatement, whileBlock.WhileStatement) End Function Public Overrides Function IsOnForeachHeader(root As SyntaxNode, position As Integer, ByRef foreachStatement As SyntaxNode) As Boolean - Dim node = TryGetAncestorForLocation(Of ForEachBlockSyntax)(root, position) - foreachStatement = node - - If foreachStatement Is Nothing Then - Return False - End If - - Return IsOnHeader(root, position, node, node.ForEachStatement) + Dim node = TryGetAncestorForLocation(Of ForEachBlockSyntax)(root, position, foreachStatement) + Return node IsNot Nothing AndAlso IsOnHeader(root, position, node, node.ForEachStatement) End Function End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 45c058621bda5..a8617012efe32 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -104,6 +104,8 @@ public virtual ImmutableArray ReturnTypeCustomModifiers public bool IsConditional => false; + public bool IsIterator => false; + public SignatureCallingConvention CallingConvention => SignatureCallingConvention.Default; public ImmutableArray UnmanagedCallingConventionTypes => []; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/StringBuilderExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/StringBuilderExtensions.cs index 05735ef7839b7..c3bb6f12eb9c7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/StringBuilderExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/StringBuilderExtensions.cs @@ -10,15 +10,37 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions; internal static class StringBuilderExtensions { + public static string ToStringAndClear(this StringBuilder builder) + { + var result = builder.ToString(); + builder.Clear(); + return result; + } + + public static StringBuilder AppendJoinedValues( + this StringBuilder builder, string separator, SeparatedSyntaxList values, Action append) + where TNode : SyntaxNode + { + var first = true; + foreach (var value in values) + { + if (!first) + builder.Append(separator); + + first = false; + append(value, builder); + } + + return builder; + } + public static StringBuilder AppendJoinedValues(this StringBuilder builder, string separator, ImmutableArray values, Action append) { var first = true; foreach (var value in values) { if (!first) - { builder.Append(separator); - } first = false; append(value, builder); diff --git a/src/Workspaces/VisualBasic/Portable/FindSymbols/FindSymbolsUtilities.vb b/src/Workspaces/VisualBasic/Portable/FindSymbols/FindSymbolsUtilities.vb new file mode 100644 index 0000000000000..3f5181c246c74 --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/FindSymbols/FindSymbolsUtilities.vb @@ -0,0 +1,64 @@ +' 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. + +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.FindSymbols + Friend Module FindSymbolsUtilities + Public Function GetDeclaredSymbolInfoKind(typeBlock As TypeBlockSyntax) As DeclaredSymbolInfoKind + Select Case typeBlock.Kind() + Case SyntaxKind.ClassBlock + Return DeclaredSymbolInfoKind.Class + Case SyntaxKind.InterfaceBlock + Return DeclaredSymbolInfoKind.Interface + Case SyntaxKind.ModuleBlock + Return DeclaredSymbolInfoKind.Module + Case Else + Return DeclaredSymbolInfoKind.Struct + End Select + End Function + + Public Function GetAccessibility(container As SyntaxNode, node As StatementSyntax, modifiers As SyntaxTokenList) As Accessibility + Dim sawFriend = False + + For Each modifier In modifiers + Select Case modifier.Kind() + Case SyntaxKind.PublicKeyword : Return Accessibility.Public + Case SyntaxKind.PrivateKeyword : Return Accessibility.Private + Case SyntaxKind.ProtectedKeyword : Return Accessibility.Protected + Case SyntaxKind.FriendKeyword + sawFriend = True + Continue For + End Select + Next + + If sawFriend Then + Return Accessibility.Internal + End If + + ' No accessibility modifiers + Select Case container.Kind() + Case SyntaxKind.ClassBlock + ' In a class, fields and shared-constructors are private by default, + ' everything Else Is Public + If node.Kind() = SyntaxKind.FieldDeclaration Then + Return Accessibility.Private + End If + + If node.Kind() = SyntaxKind.SubNewStatement AndAlso + DirectCast(node, SubNewStatementSyntax).Modifiers.Any(SyntaxKind.SharedKeyword) Then + Return Accessibility.Private + End If + + Return Accessibility.Public + Case SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ModuleBlock + ' Everything in a struct/interface/module is public + Return Accessibility.Public + End Select + + ' Otherwise, it's internal + Return Accessibility.Internal + End Function + End Module +End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb index 053dcb4224475..1518e95dd4590 100644 --- a/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb +++ b/src/Workspaces/VisualBasic/Portable/FindSymbols/VisualBasicDeclaredSymbolInfoFactoryService.vb @@ -150,7 +150,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.FindSymbols ' namespaces. If blockStatement.Modifiers.Any(SyntaxKind.PartialKeyword) AndAlso typeDeclaration.Members.Any() AndAlso -typeDeclaration.Members.All(Function(m) TypeOf m Is TypeBlockSyntax) Then + typeDeclaration.Members.All(Function(m) TypeOf m Is TypeBlockSyntax) Then Return Nothing End If @@ -163,9 +163,7 @@ typeDeclaration.Members.All(Function(m) TypeOf m Is TypeBlockSyntax) Then fullyQualifiedContainerName, blockStatement.Modifiers.Any(SyntaxKind.PartialKeyword), blockStatement.AttributeLists.Any(), - If(typeDeclaration.Kind() = SyntaxKind.ClassBlock, DeclaredSymbolInfoKind.Class, - If(typeDeclaration.Kind() = SyntaxKind.InterfaceBlock, DeclaredSymbolInfoKind.Interface, - If(typeDeclaration.Kind() = SyntaxKind.ModuleBlock, DeclaredSymbolInfoKind.Module, DeclaredSymbolInfoKind.Struct))), + GetDeclaredSymbolInfoKind(typeDeclaration), GetAccessibility(container, typeDeclaration, blockStatement.Modifiers), blockStatement.Identifier.Span, GetInheritanceNames(stringTable, typeDeclaration), @@ -402,48 +400,6 @@ typeDeclaration.Members.All(Function(m) TypeOf m Is TypeBlockSyntax) Then Return TypeOf node.Parent Is TypeBlockSyntax End Function - Private Shared Function GetAccessibility(container As SyntaxNode, node As StatementSyntax, modifiers As SyntaxTokenList) As Accessibility - Dim sawFriend = False - - For Each modifier In modifiers - Select Case modifier.Kind() - Case SyntaxKind.PublicKeyword : Return Accessibility.Public - Case SyntaxKind.PrivateKeyword : Return Accessibility.Private - Case SyntaxKind.ProtectedKeyword : Return Accessibility.Protected - Case SyntaxKind.FriendKeyword - sawFriend = True - Continue For - End Select - Next - - If sawFriend Then - Return Accessibility.Internal - End If - - ' No accessibility modifiers - Select Case container.Kind() - Case SyntaxKind.ClassBlock - ' In a class, fields and shared-constructors are private by default, - ' everything Else Is Public - If node.Kind() = SyntaxKind.FieldDeclaration Then - Return Accessibility.Private - End If - - If node.Kind() = SyntaxKind.SubNewStatement AndAlso - DirectCast(node, SubNewStatementSyntax).Modifiers.Any(SyntaxKind.SharedKeyword) Then - Return Accessibility.Private - End If - - Return Accessibility.Public - Case SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ModuleBlock - ' Everything in a struct/interface/module is public - Return Accessibility.Public - End Select - - ' Otherwise, it's internal - Return Accessibility.Internal - End Function - Private Shared Function GetMethodSuffix(method As MethodStatementSyntax) As String Return GetTypeParameterSuffix(method.TypeParameterList) & GetSuffix(method.ParameterList) End Function diff --git a/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb b/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb index 9061a014285f1..64a3846fe7955 100644 --- a/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Serialization/VisualBasicOptionsSerializationService.vb @@ -23,7 +23,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Serialization Dim vbOptions = DirectCast(options, VisualBasicCompilationOptions) - writer.WriteArray(vbOptions.GlobalImports.SelectAsArray(Function(g) g.Name), Sub(w, n) w.WriteString(n)) + writer.WriteArray(vbOptions.GlobalImports.NullToEmpty().SelectAsArray(Function(g) g?.Name), Sub(w, n) w.WriteString(n)) writer.WriteString(vbOptions.RootNamespace) writer.WriteInt32(vbOptions.OptionStrict) writer.WriteBoolean(vbOptions.OptionInfer) @@ -81,7 +81,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Serialization Dim assemblyIdentityComparer = tuple.assemblyIdentityComparer Dim strongNameProvider = tuple.strongNameProvider - Dim globalImports = GlobalImport.Parse(reader.ReadArray(Function(r) r.ReadString())) + Dim globalImports = GlobalImport.Parse(reader.ReadArray(Function(r) r.ReadString()).WhereNotNull()) Dim rootNamespace = reader.ReadString() Dim optionStrict = CType(reader.ReadInt32(), OptionStrict) Dim optionInfer = reader.ReadBoolean() diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb index 6ce8c2a21f676..caca8e31a6be3 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb @@ -5,7 +5,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Formatting -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb index 5d59bc8eeecd9..f7b6cd943910b 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb @@ -21,12 +21,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Private Sub New() End Sub - Public Overrides Function TrySimplify(expression As ExpressionSyntax, - semanticModel As SemanticModel, - options As VisualBasicSimplifierOptions, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - cancellationToken As CancellationToken) As Boolean + Public Overrides Function TrySimplify( + expression As ExpressionSyntax, + semanticModel As SemanticModel, + options As VisualBasicSimplifierOptions, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + cancellationToken As CancellationToken) As Boolean Dim memberAccessExpression = TryCast(expression, MemberAccessExpressionSyntax) If memberAccessExpression?.Expression?.Kind() = SyntaxKind.MeExpression Then diff --git a/src/Workspaces/VisualBasicTest/Serialization/OptionsSerializationTests.vb b/src/Workspaces/VisualBasicTest/Serialization/OptionsSerializationTests.vb new file mode 100644 index 0000000000000..e90bbb9edcbe0 --- /dev/null +++ b/src/Workspaces/VisualBasicTest/Serialization/OptionsSerializationTests.vb @@ -0,0 +1,41 @@ +' 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. + +Imports System.IO +Imports System.Threading +Imports Microsoft.CodeAnalysis.Serialization +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Roslyn.Test.Utilities +Imports Roslyn.Utilities +Imports Xunit + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Serialization + + Public NotInheritable Class OptionsSerializationTests + + Public Sub TestNullGlobalImport() + Dim options = New VisualBasicCompilationOptions( + OutputKind.ConsoleApplication, + "module", + globalImports:={Nothing}) + + Using workspace = New AdhocWorkspace() + Dim service = workspace.Services.GetLanguageServices(LanguageNames.VisualBasic).GetService(Of IOptionsSerializationService)() + + Using stream = New MemoryStream() + Dim writer = New ObjectWriter(stream, leaveOpen:=True) + service.WriteTo(options, writer, CancellationToken.None) + stream.Position = 0 + + Dim reader = ObjectReader.TryGetReader(stream, leaveOpen:=True) + Dim serializedOptions = DirectCast( + service.ReadCompilationOptionsFrom(reader, CancellationToken.None), + VisualBasicCompilationOptions) + + Assert.Empty(serializedOptions.GlobalImports) + End Using + End Using + End Sub + End Class +End Namespace