diff --git a/BuildAndTest.proj b/BuildAndTest.proj index a52bfaee728fa061ce4e3fa1285eb87a9189888d..b935914f61ba8447b7f4b6ede70f8f09184c0341 100644 --- a/BuildAndTest.proj +++ b/BuildAndTest.proj @@ -105,29 +105,9 @@ $(NuGetPackageRoot)\xunit.runner.console\$(xunitrunnerconsoleVersion)\tools $(RunTestArgs) @(TestAssemblies, ' ') - - - - - - $(OutputDirectory)\ProcessWatchdog\ProcessWatchdog.exe - $(OutputDirectory)\ProcessWatchdogOutput - C:\Sysinternals\Procdump.exe - - - 300 - - - PowerShell.exe -ExecutionPolicy RemoteSigned -NoProfile .\build\scripts\Run-TestsWithProcessWatchdog.ps1 -ProcessWatchDogExe $(ProcessWatchDogExe) -ProcessWatchdogOutputDirectory $(ProcessWatchdogOutputDirectory) -ProcDumpExe $(ProcDumpExe) -CoreRunExe '$(CoreRunExe)' -CoreRunArgs '$(CoreRunArgs)' -RunTestsExe '$(RunTestsExe)' -RunTestsArgs '$(RunTestsArgs)' -BuildStartTime $(BuildStartTime) -BuildTimeLimit $(BuildTimeLimit) -BufferTime $(BufferTime) - + - + diff --git a/Makefile b/Makefile index 5e3621ac05780c63633975dd1865976b64e6b972..d64d2c97505605f678da73bc1aa17e31b19d8a04 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ RESTORE_SEMAPHORE_PATH = $(BINARIES_PATH)/restore.semaphore BOOTSTRAP_PATH = $(BINARIES_PATH)/Bootstrap BUILD_LOG_PATH = HOME_DIR = $(shell cd ~ && pwd) -DOTNET_VERSION = 1.0.0-preview2-002911 +DOTNET_VERSION = 1.0.0-preview2-003131 NUGET_VERSION = 3.5.0-beta2 NUGET_EXE = $(shell pwd)/nuget.exe diff --git a/Restore.cmd b/Restore.cmd index dba478a78eea3909934d133fdde8595d208ba8dd..422c17926f32f7772e9db2504a1ddb8a962089ed 100644 --- a/Restore.cmd +++ b/Restore.cmd @@ -1,4 +1,4 @@ -@echo off +@if not defined EchoOn @echo off @setlocal enabledelayedexpansion set RoslynRoot=%~dp0 diff --git a/SetDevCommandPrompt.cmd b/SetDevCommandPrompt.cmd index ad653379a61b07074ce5ad9bee0f69f319ab7079..b0056e43206aa3ed55406ace7d0ce58088fb430a 100644 --- a/SetDevCommandPrompt.cmd +++ b/SetDevCommandPrompt.cmd @@ -1,4 +1,4 @@ -@echo off +@if not defined EchoOn @echo off :: Prefer building with Dev15 and try the simple route first (we may be running from a DevCmdPrompt already) set CommonToolsDir=%VS150COMNTOOLS% diff --git a/build/MSBuildToolset/project.json b/build/MSBuildToolset/project.json index aa89b1a4ce6af2f7cf7de909eeb83db2299d6497..f21f9380e0c1ccf094ff75913e440da0af7edf6c 100644 --- a/build/MSBuildToolset/project.json +++ b/build/MSBuildToolset/project.json @@ -20,7 +20,7 @@ "Newtonsoft.Json": "8.0.3" }, "frameworks": { - "NETCoreApp1.0": { + "NETCoreApp1.1": { "imports": [ "portable-net452", "dotnet" @@ -29,6 +29,7 @@ }, "runtimes": { "ubuntu.14.04-x64": {}, - "osx.10.11-x64": {} + "osx.10.11-x64": {}, + "osx.10.12-x64": {} } } diff --git a/build/Targets/Dependencies.props b/build/Targets/Dependencies.props index 2a7271b22e7fdfa137d2ffeb0d4be683f1ca411d..46f8c229fb3f8963e6e4d5e078109a4718f43cd3 100644 --- a/build/Targets/Dependencies.props +++ b/build/Targets/Dependencies.props @@ -18,6 +18,7 @@ 10.0.1 0.2.0-beta 0.2.0-beta + 0.2.1-beta 4.1.0 4.0.11 4.0.12 diff --git a/build/Targets/Settings.props b/build/Targets/Settings.props index 168108294b0ac9cb2a9b0e56da4345c111cd939b..7e5e16b3a93509bbb1cf52e5a601a80534cbb66a 100644 --- a/build/Targets/Settings.props +++ b/build/Targets/Settings.props @@ -13,6 +13,8 @@ $(NuGetPackageRoot) Microsoft.Net.Compilers 2.0.0-rc2-61102-09 + RoslynTools.Microsoft.VSIXExpInstaller + 0.2.1-beta 1.2.0-beta2 $(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\$(RoslynDiagnosticsNugetPackageVersion)\build\Microsoft.Net.RoslynDiagnostics.props $(NuGetPackageRoot)\Roslyn.Build.Util\0.9.4-portable\lib\dotnet\Roslyn.MSBuild.Util.dll diff --git a/build/ToolsetPackages/project.json b/build/ToolsetPackages/project.json index a1e0edf193350bbbdcd9033d87902a69392f7cfb..7b523e96e4d81c93a7a17fcc45129bd72d2c3318 100644 --- a/build/ToolsetPackages/project.json +++ b/build/ToolsetPackages/project.json @@ -14,7 +14,8 @@ "Roslyn.Build.Util": "0.9.4-portable", "RoslynDependencies.OptimizationData": "2.0.0-rc-61101-16", "RoslynTools.Microsoft.LocateVS": "0.2.0-beta", - "RoslynTools.Microsoft.SignTool": "0.2.0-beta" + "RoslynTools.Microsoft.SignTool": "0.2.0-beta", + "RoslynTools.Microsoft.VSIXExpInstaller": "0.2.1-beta" }, "frameworks": { "net461": {} diff --git a/cibuild.cmd b/cibuild.cmd index c3e7b9a27127a238dd3b2ad6c2067b24989ec27e..70d174c7cb5428fd71e7cfe0637ba176326dee9c 100644 --- a/cibuild.cmd +++ b/cibuild.cmd @@ -75,11 +75,11 @@ copy "build\bootstrap\*" "%bindir%\Bootstrap" || goto :BuildFailed REM Clean the previous build msbuild %MSBuildAdditionalCommandLineArgs% /t:Clean build/Toolset/Toolset.csproj /p:Configuration=%BuildConfiguration% /fileloggerparameters:LogFile="%bindir%\BootstrapClean.log" || goto :BuildFailed -call :TerminateBuildProcesses +call :TerminateBuildProcesses || goto :BuildFailed if defined TestDeterminism ( powershell -noprofile -executionPolicy RemoteSigned -file "%RoslynRoot%\build\scripts\test-determinism.ps1" "%bindir%\Bootstrap" || goto :BuildFailed - call :TerminateBuildProcesses + call :TerminateBuildProcesses || goto :BuildFailed exit /b 0 ) @@ -110,6 +110,8 @@ if defined TestPerfRun ( ) ) + call :TerminateBuildProcesses || goto :BuildFailed + .\Binaries\%BuildConfiguration%\Exes\Perf.Runner\Roslyn.Test.Performance.Runner.exe --no-trace-upload !EXTRA_PERF_RUNNER_ARGS! || goto :BuildFailed exit /b 0 ) @@ -117,7 +119,7 @@ if defined TestPerfRun ( msbuild %MSBuildAdditionalCommandLineArgs% /p:BootstrapBuildPath="%bindir%\Bootstrap" BuildAndTest.proj /p:Configuration=%BuildConfiguration% /p:Test64=%Test64% /p:TestVsi=%TestVsi% /p:RunProcessWatchdog=%RunProcessWatchdog% /p:BuildStartTime=%BuildStartTime% /p:"ProcDumpExe=%ProcDumpExe%" /p:BuildTimeLimit=%BuildTimeLimit% /p:PathMap="%RoslynRoot%=q:\roslyn" /p:Feature=pdb-path-determinism /fileloggerparameters:LogFile="%bindir%\Build.log";verbosity=diagnostic /p:DeployExtension=false || goto :BuildFailed powershell -noprofile -executionPolicy RemoteSigned -file "%RoslynRoot%\build\scripts\check-msbuild.ps1" "%bindir%\Build.log" || goto :BuildFailed -call :TerminateBuildProcesses +call :TerminateBuildProcesses || goto :BuildFailed REM Ensure caller sees successful exit. exit /b 0 @@ -142,5 +144,12 @@ exit /b 1 @REM Kill any instances of msbuild.exe to ensure that we never reuse nodes (e.g. if a non-roslyn CI run @REM left some floating around). -taskkill /F /IM vbcscompiler.exe 2> nul -taskkill /F /IM msbuild.exe 2> nul +@REM An error-level of 1 means that the process was found, but could not be killed. +echo Killing all build-related processes +taskkill /F /IM msbuild.exe > nul +if %ERRORLEVEL% == 1 exit /b 1 + +taskkill /F /IM vbcscompiler.exe > nul +if %ERRORLEVEL% == 1 exit /b 1 + +exit /b 0 diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 17e604ccec22198721bb18bede9340e22f8cb46e..0bebb66c717f2f833677ad3f6193985d60caf0e4 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4411,7 +4411,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ generation but may be used by analyzers for producing errors or warnings. /embed Embed all source files in the PDB. - /embed:<file list> Embed specfic files the PDB + /embed:<file list> Embed specific files in the PDB - RESOURCES - /win32res:<file> Specify a Win32 resource file (.res) @@ -4975,4 +4975,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A throw expression is not allowed in this context. - \ No newline at end of file + diff --git a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj index 62f69b414d546f6b1bc87e1645add7eaad5c1fb6..c990556b0e9e9dec91ebff1dca03da45d88d4132 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj @@ -111,6 +111,7 @@ + diff --git a/src/EditorFeatures/CSharp/FindReferences/CSharpFindReferencesService.cs b/src/EditorFeatures/CSharp/FindReferences/CSharpFindReferencesService.cs index 0ac6e3c236ca4b754552a9c5fe33a7ceb73c4737..113664360a6925e2b83b52d4dbac08ac3c106604 100644 --- a/src/EditorFeatures/CSharp/FindReferences/CSharpFindReferencesService.cs +++ b/src/EditorFeatures/CSharp/FindReferences/CSharpFindReferencesService.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Composition; +using Microsoft.CodeAnalysis.Editor.FindReferences; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.FindReferences; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Editor.CSharp.FindReferences @@ -14,22 +14,8 @@ internal class CSharpFindReferencesService : AbstractFindReferencesService [ImportingConstructor] public CSharpFindReferencesService( [ImportMany] IEnumerable referencedSymbolsPresenters, - [ImportMany] IEnumerable navigableItemsPresenters, - [ImportMany] IEnumerable externalReferencesProviders) - : base(referencedSymbolsPresenters, navigableItemsPresenters, externalReferencesProviders) - { - } - } - - [ExportLanguageService(typeof(IStreamingFindReferencesService), LanguageNames.CSharp), Shared] - internal class CSharpStreamingFindReferencesService : AbstractFindReferencesService - { - [ImportingConstructor] - public CSharpStreamingFindReferencesService( - [ImportMany] IEnumerable referencedSymbolsPresenters, - [ImportMany] IEnumerable navigableItemsPresenters, - [ImportMany] IEnumerable externalReferencesProviders) - : base(referencedSymbolsPresenters, navigableItemsPresenters, externalReferencesProviders) + [ImportMany] IEnumerable navigableItemsPresenters) + : base(referencedSymbolsPresenters, navigableItemsPresenters) { } } diff --git a/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs new file mode 100644 index 0000000000000000000000000000000000000000..5fb1df0e7dba84723a707c9d3127e2cf982d778c --- /dev/null +++ b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesService.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.FindUsages +{ + [ExportLanguageService(typeof(IFindUsagesService), LanguageNames.CSharp), Shared] + internal class CSharpFindUsagesService : AbstractFindUsagesService + { + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/GoToDefinition/CSharpGoToDefinitionService.cs b/src/EditorFeatures/CSharp/GoToDefinition/CSharpGoToDefinitionService.cs index 8dd47c6be1e497a37eff98214b521e0c33972c16..9366db7bd1422f995db428f66e53650f7ddddcd9 100644 --- a/src/EditorFeatures/CSharp/GoToDefinition/CSharpGoToDefinitionService.cs +++ b/src/EditorFeatures/CSharp/GoToDefinition/CSharpGoToDefinitionService.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Composition; +using Microsoft.CodeAnalysis.Editor.GoToDefinition; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Editor.CSharp.GoToDefinition @@ -15,7 +15,8 @@ internal class CSharpGoToDefinitionService : AbstractGoToDefinitionService [ImportingConstructor] public CSharpGoToDefinitionService( [ImportMany]IEnumerable> presenters, - [ImportMany]IEnumerable> externalDefinitionProviders) : base(presenters, externalDefinitionProviders) + [ImportMany]IEnumerable> streamingPresenters) + : base(presenters, streamingPresenters) { } @@ -24,4 +25,4 @@ protected override ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, C return symbol; } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/GoToImplementation/CSharpGoToImplementationService.cs b/src/EditorFeatures/CSharp/GoToImplementation/CSharpGoToImplementationService.cs index bc30c342b923a5f9ea48d29b48925931def8cd04..76cb1e814ebd37583f007a5d16995d194bf10b79 100644 --- a/src/EditorFeatures/CSharp/GoToImplementation/CSharpGoToImplementationService.cs +++ b/src/EditorFeatures/CSharp/GoToImplementation/CSharpGoToImplementationService.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Composition; +using Microsoft.CodeAnalysis.Editor.GoToImplementation; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Editor.CSharp.GoToImplementation @@ -14,9 +14,9 @@ internal sealed class CSharpGoToImplementationService : AbstractGoToImplementati { [ImportingConstructor] public CSharpGoToImplementationService( - [ImportMany]IEnumerable> presenters, - [ImportMany]IEnumerable> externalDefinitionProviders) : base(presenters, externalDefinitionProviders) + [ImportMany]IEnumerable> presenters) + : base(presenters) { } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs index a3679231f33058ae9ec0b7b79d049dceb5befe2b..ffa11ba4f059bf2985c2b1b4f5f57c8d6acf5272 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CodeRefactorings.ExtractMethod; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Options; using Roslyn.Test.Utilities; using Xunit; @@ -14,9 +17,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.Extrac public class ExtractMethodTests : AbstractCSharpCodeActionTest { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace) - { - return new ExtractMethodCodeRefactoringProvider(); - } + => new ExtractMethodCodeRefactoringProvider(); [WorkItem(540799, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540799")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] @@ -1213,5 +1214,101 @@ private static void NewMethod(int v, CancellationToken ct) } }"); } + + [WorkItem(15219, "https://github.com/dotnet/roslyn/issues/15219")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + public async Task TestUseVar1() + { + await TestAsync( +@"using System; + +class C +{ + void Foo(int i) + { + [|var v = (string)null; + + switch (i) + { + case 0: v = ""0""; break; + case 1: v = ""1""; break; + }|] + + Console.WriteLine(v); + } +}", +@"using System; + +class C +{ + void Foo(int i) + { + var v = {|Rename:NewMethod|}(i); + + Console.WriteLine(v); + } + + private static string NewMethod(int i) + { + var v = (string)null; + + switch (i) + { + case 0: v = ""0""; break; + case 1: v = ""1""; break; + } + + return v; + } +}", options: Option(CSharpCodeStyleOptions.UseImplicitTypeForIntrinsicTypes, CodeStyleOptions.TrueWithSuggestionEnforcement)); + } + + [WorkItem(15219, "https://github.com/dotnet/roslyn/issues/15219")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + public async Task TestUseVar2() + { + await TestAsync( +@"using System; + +class C +{ + void Foo(int i) + { + [|var v = (string)null; + + switch (i) + { + case 0: v = ""0""; break; + case 1: v = ""1""; break; + }|] + + Console.WriteLine(v); + } +}", +@"using System; + +class C +{ + void Foo(int i) + { + string v = {|Rename:NewMethod|}(i); + + Console.WriteLine(v); + } + + private static string NewMethod(int i) + { + var v = (string)null; + + switch (i) + { + case 0: v = ""0""; break; + case 1: v = ""1""; break; + } + + return v; + } +}", options: Option(CSharpCodeStyleOptions.UseImplicitTypeWhereApparent, CodeStyleOptions.TrueWithSuggestionEnforcement)); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs index 3672915a18c87174834d25991ef261bb8603522c..4a973ee88941b97404deae3e7d2a55b1e3161367 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs @@ -14,15 +14,7 @@ public class CompletionServiceTests [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public void AcquireCompletionService() { - var hostServices = MefHostServices.Create( - MefHostServices.DefaultAssemblies.Concat( - new[] - { - typeof(CompletionService).Assembly, - typeof(CSharpCompletionService).Assembly - })); - - var workspace = new AdhocWorkspace(hostServices); + var workspace = new AdhocWorkspace(); var document = workspace .AddProject("TestProject", LanguageNames.CSharp) diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs index dcd7891ba34ecd68c46b2e4648c646a2c5c2fda0..7e81e584ba3eb5cbc9261a58c1c6a84588005611 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ExtractMethod; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; @@ -82,7 +83,9 @@ protected async Task NotSupported_ExtractMethodAsync(string codeWithMarker) var testDocument = workspace.Documents.Single(); var subjectBuffer = testDocument.TextBuffer; - var tree = await ExtractMethodAsync(workspace, testDocument, allowMovingDeclaration: allowMovingDeclaration, dontPutOutOrRefOnStruct: dontPutOutOrRefOnStruct); + var tree = await ExtractMethodAsync( + workspace, testDocument, allowMovingDeclaration: allowMovingDeclaration, + dontPutOutOrRefOnStruct: dontPutOutOrRefOnStruct); using (var edit = subjectBuffer.CreateEdit()) { diff --git a/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs b/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs index adc65ad09faa311bbb7c6bc02f54395c2d0987bd..4e077298cc65631cb60149110e01845f1a549887 100644 --- a/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs +++ b/src/EditorFeatures/CSharpTest/UseCollectionInitializer/UseCollectionInitializerTests.cs @@ -471,7 +471,7 @@ void M() } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] - public async Task TestFixAllInDocument() + public async Task TestFixAllInDocument1() { await TestAsync( @"using System.Collections.Generic; @@ -510,6 +510,78 @@ void M() }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] + public async Task TestFixAllInDocument2() + { + await TestAsync( +@"using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = {|FixAllInDocument:new|} List(() => { + var list2 = new List(); + list2.Add(2); + }); + list1.Add(1); + } +}", +@"using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = new List(() => { + var list2 = new List + { + 2 + }; + }) + { + 1 + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] + public async Task TestFixAllInDocument3() + { + await TestAsync( +@"using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = {|FixAllInDocument:new|} List(); + list1.Add(() => { + var list2 = new List(); + list2.Add(2); + }); + } +}", +@"using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = new List + { + () => { + var list2 = new List + { + 2 + }; + } + }; + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] public async Task TestTrivia1() { diff --git a/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs b/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs index 45da9296ece09f431ba1c0a63c4f0f3b921864fd..e0ba7d7cfa0650fbdfe8e5093ec4989062c0e611 100644 --- a/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs +++ b/src/EditorFeatures/CSharpTest/UseObjectInitializer/UseObjectInitializerTests.cs @@ -305,7 +305,83 @@ void M() } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] - public async Task TestFixAllInDocument() + public async Task TestFixAllInDocument1() + { + await TestAsync( +@"class C +{ + int i; + int j; + + void M() + { + var v = {|FixAllInDocument:new|} C(() => { + var v2 = new C(); + v2.i = 1; + }); + v.j = 2; + } +}", +@"class C +{ + int i; + int j; + + void M() + { + var v = new C(() => { + var v2 = new C() + { + i = 1 + }; + }) + { + j = 2 + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] + public async Task TestFixAllInDocument2() + { + await TestAsync( +@"class C +{ + int i; + int j; + + void M() + { + var v = {|FixAllInDocument:new|} C(); + v.j = () => { + var v2 = new C(); + v2.i = 1; + }; + } +}", +@"class C +{ + int i; + int j; + + void M() + { + var v = new C() + { + j = () => { + var v2 = new C() + { + i = 1 + }; + } + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] + public async Task TestFixAllInDocument3() { await TestAsync( @"class C diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs index 8c112bb88e47fa71315c89ff7df6842c077dfa8e..5a321ba27fb903e43e39b7e5b1362abb2ec5014f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs @@ -128,6 +128,120 @@ public async Task TestNotAfterPartialInClass() class Foo { partial $$ +}"); + } + + [Fact] + [WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + public async Task TestLocalFunction() + { + await VerifyKeywordAsync(@" +class Foo +{ + public void M() + { + $$ + } +}"); + } + + [Fact] + [WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction2() + { + await VerifyKeywordAsync(@" +class Foo +{ + public void M() + { + unsafe $$ + } +}"); + } + + [Fact] + [WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction3() + { + await VerifyKeywordAsync(@" +class Foo +{ + public void M() + { + unsafe $$ void L() { } + } +}"); + } + + [Fact] + [WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction4() + { + await VerifyKeywordAsync(@" +class Foo +{ + public void M() + { + $$ void L() { } + } +}"); + } + + [Fact] + [WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction5() + { + await VerifyKeywordAsync(@" +class Foo +{ + public void M(Action a) + { + M(async () => + { + $$ + }); + } +}"); + } + + [Fact] + [WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction6() + { + await VerifyAbsenceAsync(@" +class Foo +{ + public void M() + { + int $$ + } +}"); + } + + [Fact] + [WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction7() + { + await VerifyAbsenceAsync(@" +class Foo +{ + public void M() + { + static $$ + } }"); } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs index 0f96afeb59ff846d2ef1402d29124e12832d4392..e521a19b61862e5e5bfba557d84859efba54f520 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs @@ -83,13 +83,6 @@ public async Task TestNotInCastType2() @"var str = (($$)items) as string;")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotInEmptyStatement() - { - await VerifyAbsenceAsync(AddInsideMethod( -@"$$")); - } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInTypeOf() { @@ -642,5 +635,123 @@ public async Task TestNotAfterAsyncAsType() { await VerifyAbsenceAsync(@"class c { async async $$ }"); } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction() + { + await VerifyKeywordAsync(@" +class C +{ + void M() + { + $$ + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction2() + { + await VerifyKeywordAsync(@" +class C +{ + void M() + { + async $$ + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction3() + { + await VerifyAbsenceAsync(@" +class C +{ + void M() + { + async async $$ + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction4() + { + await VerifyAbsenceAsync(@" +class C +{ + void M() + { + var async $$ + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction5() + { + await VerifyAbsenceAsync(@" +using System; +class C +{ + void M(Action a) + { + M(async $$ () => + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction6() + { + await VerifyKeywordAsync(@" +class C +{ + void M() + { + unsafe async $$ + } +}"); + } + + [Fact] + [WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")] + [Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)] + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalFunction7() + { + await VerifyKeywordAsync(@" +using System; +class C +{ + void M(Action a) + { + M(async () => + { + async $$ + }) + } +}"); + } } } diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index 9433e6f4c89f6c5ba122575309bf636aba120fa5..207765788c176339ecf7fbc4c3676dde8bdddc7b 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -108,6 +108,12 @@ + + + + + + @@ -254,12 +260,9 @@ - - - - - - + + + @@ -280,7 +283,7 @@ - + @@ -377,9 +380,9 @@ - - - + + + @@ -390,10 +393,10 @@ - - - - + + + + @@ -782,4 +785,4 @@ - + \ No newline at end of file diff --git a/src/EditorFeatures/Core/FindReferences/AbstractFindReferencesService.cs b/src/EditorFeatures/Core/FindReferences/AbstractFindReferencesService.cs new file mode 100644 index 0000000000000000000000000000000000000000..38d6b6d8400d0883f1fb72be3e8b024f862c411f --- /dev/null +++ b/src/EditorFeatures/Core/FindReferences/AbstractFindReferencesService.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.FindReferences +{ + internal abstract partial class AbstractFindReferencesService : + ForegroundThreadAffinitizedObject, IFindReferencesService + { + private readonly IEnumerable _referenceSymbolPresenters; + private readonly IEnumerable _navigableItemPresenters; + + protected AbstractFindReferencesService( + IEnumerable referenceSymbolPresenters, + IEnumerable navigableItemPresenters) + { + _referenceSymbolPresenters = referenceSymbolPresenters; + _navigableItemPresenters = navigableItemPresenters; + } + + private async Task, Solution>> FindReferencedSymbolsAsync( + Document document, int position, IWaitContext waitContext) + { + var cancellationToken = waitContext.CancellationToken; + + var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( + document, position, cancellationToken).ConfigureAwait(false); + if (symbolAndProject == null) + { + return null; + } + + var symbol = symbolAndProject?.symbol; + var project = symbolAndProject?.project; + + var displayName = GetDisplayName(symbol); + + waitContext.Message = string.Format( + EditorFeaturesResources.Finding_references_of_0, displayName); + + var result = await SymbolFinder.FindReferencesAsync(symbol, project.Solution, cancellationToken).ConfigureAwait(false); + + return Tuple.Create(result, project.Solution); + } + + public static string GetDisplayName(ISymbol symbol) + { + return symbol.IsConstructor() ? symbol.ContainingType.Name : symbol.Name; + } + + public bool TryFindReferences(Document document, int position, IWaitContext waitContext) + { + var cancellationToken = waitContext.CancellationToken; + + var result = this.FindReferencedSymbolsAsync(document, position, waitContext).WaitAndGetResult(cancellationToken); + return TryDisplayReferences(result); + } + + private bool TryDisplayReferences(IEnumerable result) + { + if (result != null && result.Any()) + { + var title = result.First().DisplayTaggedParts.JoinText(); + foreach (var presenter in _navigableItemPresenters) + { + presenter.DisplayResult(title, result); + return true; + } + } + + return false; + } + + private bool TryDisplayReferences(Tuple, Solution> result) + { + if (result != null && result.Item1 != null) + { + var solution = result.Item2; + var factory = solution.Workspace.Services.GetService(); + var definitionsAndReferences = factory.CreateDefinitionsAndReferences( + solution, result.Item1); + + foreach (var presenter in _referenceSymbolPresenters) + { + presenter.DisplayResult(definitionsAndReferences); + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesCommandHandler.cs b/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs similarity index 85% rename from src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesCommandHandler.cs rename to src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs index 553f4f93d97a5507084725c03da1995bc6246b9a..917961145c52824ea323a34a21dc4f578c174dd8 100644 --- a/src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesCommandHandler.cs +++ b/src/EditorFeatures/Core/FindReferences/FindReferencesCommandHandler.cs @@ -5,24 +5,26 @@ using System.ComponentModel.Composition; using System.Linq; using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.FindUsages; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences +namespace Microsoft.CodeAnalysis.Editor.FindReferences { [ExportCommandHandler(PredefinedCommandHandlerNames.FindReferences, ContentTypeNames.RoslynContentType)] internal class FindReferencesCommandHandler : ICommandHandler { private readonly IEnumerable _synchronousPresenters; - private readonly IEnumerable> _streamingPresenters; + private readonly IEnumerable> _streamingPresenters; private readonly IWaitIndicator _waitIndicator; private readonly IAsynchronousOperationListener _asyncListener; @@ -31,7 +33,7 @@ internal class FindReferencesCommandHandler : ICommandHandler synchronousPresenters, - [ImportMany] IEnumerable> streamingPresenters, + [ImportMany] IEnumerable> streamingPresenters, [ImportMany] IEnumerable> asyncListeners) { Contract.ThrowIfNull(synchronousPresenters); @@ -72,8 +74,8 @@ public void ExecuteCommand(FindReferencesCommandArgs args, Action nextHandler) private bool TryExecuteCommand(int caretPosition, Document document) { - var streamingService = document.Project.LanguageServices.GetService(); - var synchronousService = document.Project.LanguageServices.GetService(); + var streamingService = document.GetLanguageService(); + var synchronousService = document.GetLanguageService(); var streamingPresenter = GetStreamingPresenter(); @@ -83,7 +85,7 @@ private bool TryExecuteCommand(int caretPosition, Document document) var streamingEnabled = document.Project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.StreamingFindReferences, document.Project.Language); if (streamingEnabled && streamingService != null && streamingPresenter != null) { - StreamingFindReferences(document, streamingService, streamingPresenter, caretPosition); + StreamingFindReferences(document, caretPosition, streamingService, streamingPresenter); return true; } @@ -100,7 +102,7 @@ private bool TryExecuteCommand(int caretPosition, Document document) return false; } - private IStreamingFindReferencesPresenter GetStreamingPresenter() + private IStreamingFindUsagesPresenter GetStreamingPresenter() { try { @@ -113,8 +115,9 @@ private IStreamingFindReferencesPresenter GetStreamingPresenter() } private async void StreamingFindReferences( - Document document, IStreamingFindReferencesService service, - IStreamingFindReferencesPresenter presenter, int caretPosition) + Document document, int caretPosition, + IFindUsagesService findUsagesService, + IStreamingFindUsagesPresenter presenter) { try { @@ -122,8 +125,8 @@ private IStreamingFindReferencesPresenter GetStreamingPresenter() { // Let the presented know we're starging a search. It will give us back // the context object that the FAR service will push results into. - var context = presenter.StartSearch(); - await service.FindReferencesAsync(document, caretPosition, context).ConfigureAwait(false); + var context = presenter.StartSearch(EditorFeaturesResources.Find_References); + await findUsagesService.FindReferencesAsync(document, caretPosition, context).ConfigureAwait(false); // Note: we don't need to put this in a finally. The only time we might not hit // this is if cancellation or another error gets thrown. In the former case, diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesService.cs b/src/EditorFeatures/Core/FindReferences/IFindReferencesService.cs similarity index 64% rename from src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesService.cs rename to src/EditorFeatures/Core/FindReferences/IFindReferencesService.cs index 6d09c840a13207e46ea0c01f9f5c243276eeeff6..9de01830b82b1e3c00a9ab9bc014b4de6d0d8042 100644 --- a/src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesService.cs +++ b/src/EditorFeatures/Core/FindReferences/IFindReferencesService.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Host; @@ -15,13 +14,4 @@ internal interface IFindReferencesService : ILanguageService /// True if finding references of the symbol at the provided position succeeds. False, otherwise. bool TryFindReferences(Document document, int position, IWaitContext waitContext); } - - internal interface IStreamingFindReferencesService : ILanguageService - { - /// - /// Finds the references for the symbol at the specific position in the document, - /// pushing the results into the context instance. - /// - Task FindReferencesAsync(Document document, int position, FindReferencesContext context); - } } \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.ProgressAdapter.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs similarity index 91% rename from src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.ProgressAdapter.cs rename to src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 9b99c044052530336fec5ca160c7660272ad588d..4ba183f5d6c5f494c969c8308300005d4ee62835 100644 --- a/src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.ProgressAdapter.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -4,22 +4,22 @@ using System.Collections.Concurrent; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Navigation; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences +namespace Microsoft.CodeAnalysis.Editor.FindUsages { - internal abstract partial class AbstractFindReferencesService + internal abstract partial class AbstractFindUsagesService { /// - /// Forwards IFindReferencesProgress calls to a FindRefrencesContext instance. + /// Forwards IFindReferencesProgress calls to an IFindUsagesContext instance. /// private class ProgressAdapter : ForegroundThreadAffinitizedObject, IStreamingFindReferencesProgress { private readonly Solution _solution; - private readonly FindReferencesContext _context; + private readonly IFindUsagesContext _context; /// /// We will hear about definition symbols many times while performing FAR. We'll @@ -36,7 +36,7 @@ private class ProgressAdapter : ForegroundThreadAffinitizedObject, IStreamingFin private readonly Func _definitionFactory; - public ProgressAdapter(Solution solution, FindReferencesContext context) + public ProgressAdapter(Solution solution, IFindUsagesContext context) { _solution = solution; _context = context; diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs new file mode 100644 index 0000000000000000000000000000000000000000..e15763a8319014070a3c5c5acfdebe0558eca47d --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindReferences; +using Microsoft.CodeAnalysis.Editor.GoToImplementation; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + internal abstract partial class AbstractFindUsagesService : IFindUsagesService + { + public async Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context) + { + var tuple = await FindUsagesHelpers.FindImplementationsAsync( + document, position, context.CancellationToken).ConfigureAwait(false); + if (tuple == null) + { + context.ReportMessage(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret); + return; + } + + var message = tuple.Value.message; + + if (message != null) + { + context.ReportMessage(message); + return; + } + + var project = tuple.Value.project; + + foreach (var implementation in tuple.Value.implementations) + { + var definitionItem = implementation.ToDefinitionItem(project.Solution); + await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); + } + } + + public async Task FindReferencesAsync( + Document document, int position, IFindUsagesContext context) + { + // NOTE: All ConFigureAwaits in this method need to pass 'true' so that + // we return to the caller's context. that's so the call to + // CallThirdPartyExtensionsAsync will happen on the UI thread. We need + // this to maintain the threading guarantee we had around that method + // from pre-Roslyn days. + var findReferencesProgress = await FindReferencesWorkerAsync( + document, position, context).ConfigureAwait(true); + if (findReferencesProgress == null) + { + return; + } + + // After the FAR engine is done call into any third party extensions to see + // if they want to add results. + await findReferencesProgress.CallThirdPartyExtensionsAsync().ConfigureAwait(true); + } + + private async Task FindReferencesWorkerAsync( + Document document, int position, IFindUsagesContext context) + { + var cancellationToken = context.CancellationToken; + cancellationToken.ThrowIfCancellationRequested(); + + // Find the symbol we want to search and the solution we want to search in. + var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( + document, position, cancellationToken).ConfigureAwait(false); + if (symbolAndProject == null) + { + return null; + } + + var symbol = symbolAndProject?.symbol; + var project = symbolAndProject?.project; + + var displayName = AbstractFindReferencesService.GetDisplayName(symbol); + context.SetSearchLabel(displayName); + + var progressAdapter = new ProgressAdapter(project.Solution, context); + + // Now call into the underlying FAR engine to find reference. The FAR + // engine will push results into the 'progress' instance passed into it. + // We'll take those results, massage them, and forward them along to the + // FindReferencesContext instance we were given. + await SymbolFinder.FindReferencesAsync( + SymbolAndProjectId.Create(symbol, project.Id), + project.Solution, + progressAdapter, + documents: null, + cancellationToken: cancellationToken).ConfigureAwait(false); + + return progressAdapter; + } + } +} diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesContext.cs b/src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs similarity index 77% rename from src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesContext.cs rename to src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs index 5c7f8d2f7799defbd0e2b7f0ee6f0a5ac289d8ed..e64900c8c165d4186540159577427a81766cefd3 100644 --- a/src/EditorFeatures/Core/Implementation/FindReferences/FindReferencesContext.cs +++ b/src/EditorFeatures/Core/FindUsages/FindUsagesContext.cs @@ -1,17 +1,21 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.FindReferences; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor +namespace Microsoft.CodeAnalysis.FindUsages { - internal abstract class FindReferencesContext + internal abstract class FindUsagesContext : IFindUsagesContext { public virtual CancellationToken CancellationToken { get; } - protected FindReferencesContext() + protected FindUsagesContext() + { + } + + public virtual void ReportMessage(string message) { } diff --git a/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs new file mode 100644 index 0000000000000000000000000000000000000000..e28f739a0dbe58a2619d68b4e43751e4674d329e --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.SymbolMapping; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + internal static class FindUsagesHelpers + { + /// + /// Common helper for both the synchronous and streaming versions of FAR. + /// It returns the symbol we want to search for and the solution we should + /// be searching. + /// + /// Note that the returned may absolutely *not* be + /// the same as document.Project.Solution. This is because + /// there may be symbol mapping involved (for example in Metadata-As-Source + /// scenarios). + /// + public static async Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( + Document document, int position, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false); + if (symbol == null) + { + return null; + } + + // If this document is not in the primary workspace, we may want to search for results + // in a solution different from the one we started in. Use the starting workspace's + // ISymbolMappingService to get a context for searching in the proper solution. + var mappingService = document.Project.Solution.Workspace.Services.GetService(); + + var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false); + if (mapping == null) + { + return null; + } + + return (mapping.Symbol, mapping.Project); + } + + public static async Task<(ISymbol symbol, Project project, ImmutableArray implementations, string message)?> FindImplementationsAsync(Document document, int position, CancellationToken cancellationToken) + { + var symbolAndProject = await GetRelevantSymbolAndProjectAtPositionAsync( + document, position, cancellationToken).ConfigureAwait(false); + if (symbolAndProject == null) + { + return null; + } + + return await FindImplementationsAsync( + symbolAndProject?.symbol, symbolAndProject?.project, cancellationToken).ConfigureAwait(false); + } + + private static async Task<(ISymbol symbol, Project project, ImmutableArray implementations, string message)?> FindImplementationsAsync( + ISymbol symbol, Project project, CancellationToken cancellationToken) + { + var implementations = await FindImplementationsWorkerAsync( + symbol, project, cancellationToken).ConfigureAwait(false); + + var filteredSymbols = implementations.WhereAsArray( + s => !s.IsAbstract && s.Locations.Any(l => l.IsInSource)); + + return filteredSymbols.Length == 0 + ? (symbol, project, filteredSymbols, EditorFeaturesResources.The_symbol_has_no_implementations) + : (symbol, project, filteredSymbols, null); + } + + private static async Task> FindImplementationsWorkerAsync( + ISymbol symbol, Project project, CancellationToken cancellationToken) + { + var solution = project.Solution; + if (symbol.IsInterfaceType() || symbol.IsImplementableMember()) + { + var implementations = await SymbolFinder.FindImplementationsAsync( + symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + + // It's important we use a HashSet here -- we may have cases in an inheritence hierarchy where more than one method + // in an overrides chain implements the same interface method, and we want to duplicate those. The easiest way to do it + // is to just use a HashSet. + var implementationsAndOverrides = new HashSet(); + + foreach (var implementation in implementations) + { + implementationsAndOverrides.Add(implementation); + + // FindImplementationsAsync will only return the base virtual/abstract method, not that method and the overrides + // of the method. We should also include those. + if (implementation.IsOverridable()) + { + var overrides = await SymbolFinder.FindOverridesAsync( + implementation, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + implementationsAndOverrides.AddRange(overrides); + } + } + + return implementationsAndOverrides.ToImmutableArray(); + } + else if ((symbol as INamedTypeSymbol)?.TypeKind == TypeKind.Class) + { + var derivedClasses = await SymbolFinder.FindDerivedClassesAsync( + (INamedTypeSymbol)symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + var implementations = derivedClasses.Concat(symbol); + + return implementations.ToImmutableArray(); + } + else if (symbol.IsOverridable()) + { + var overrides = await SymbolFinder.FindOverridesAsync( + symbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + var implementations = overrides.Concat(symbol); + + return implementations.ToImmutableArray(); + } + else + { + // This is something boring like a regular method or type, so we'll just go there directly + return ImmutableArray.Create(symbol); + } + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/FindUsages/IFindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/IFindUsagesContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..35f34ab9c11724e3802d88992fe1c1f4f67db0a4 --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/IFindUsagesContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.FindUsages +{ + internal interface IFindUsagesContext + { + CancellationToken CancellationToken { get; } + + /// + /// Report a message to be displayed to the user. + /// + void ReportMessage(string message); + + /// + /// Set the title of the window that results are displayed in. + /// + void SetSearchLabel(string displayName); + + Task OnDefinitionFoundAsync(DefinitionItem definition); + Task OnReferenceFoundAsync(SourceReferenceItem reference); + + Task ReportProgressAsync(int current, int maximum); + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs new file mode 100644 index 0000000000000000000000000000000000000000..b0dda6dc4af9d00b179de31efcc5c756582cb9bd --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/IFindUsagesService.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + internal interface IFindUsagesService : ILanguageService + { + /// + /// Finds the references for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindReferencesAsync(Document document, int position, IFindUsagesContext context); + + /// + /// Finds the implementationss for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context); + } +} diff --git a/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs b/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..23a183825879bfe0f3a96c97462a327590439d46 --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/SimpleFindUsagesContext.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.FindUsages +{ + /// + /// Simple implementation of a that just aggregates the results + /// for consumers that just want the data once it is finally computed. + /// + internal class SimpleFindUsagesContext : FindUsagesContext + { + private readonly object _gate = new object(); + private readonly ImmutableArray.Builder _definitionItems = + ImmutableArray.CreateBuilder(); + + private readonly ImmutableArray.Builder _referenceItems = + ImmutableArray.CreateBuilder(); + + public override CancellationToken CancellationToken { get; } + + public SimpleFindUsagesContext(CancellationToken cancellationToken) + { + CancellationToken = cancellationToken; + } + + public string Message { get; private set; } + + public override void ReportMessage(string message) + => Message = message; + + public ImmutableArray GetDefinitions() + { + lock (_gate) + { + return _definitionItems.ToImmutable(); + } + } + + public ImmutableArray GetReferences() + { + lock (_gate) + { + return _referenceItems.ToImmutable(); + } + } + + public override Task OnDefinitionFoundAsync(DefinitionItem definition) + { + lock (_gate) + { + _definitionItems.Add(definition); + } + + return SpecializedTasks.EmptyTask; + } + + public override Task OnReferenceFoundAsync(SourceReferenceItem reference) + { + lock (_gate) + { + _referenceItems.Add(reference); + } + + return SpecializedTasks.EmptyTask; + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/GoToDefinition/AbstractGoToDefinitionService.cs b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs similarity index 76% rename from src/EditorFeatures/Core/Implementation/GoToDefinition/AbstractGoToDefinitionService.cs rename to src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs index 888a9e3ca85f1e97d11b0b34a9fbc12e6dfe31c1..06aae0270d341eeb7a98f87ef3eb0be3111cf91d 100644 --- a/src/EditorFeatures/Core/Implementation/GoToDefinition/AbstractGoToDefinitionService.cs +++ b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToDefinitionService.cs @@ -12,19 +12,21 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition +namespace Microsoft.CodeAnalysis.Editor.GoToDefinition { internal abstract class AbstractGoToDefinitionService : IGoToDefinitionService { private readonly IEnumerable> _presenters; - private readonly IEnumerable> _externalDefinitionProviders; + private readonly IEnumerable> _streamingPresenters; protected abstract ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation); - protected AbstractGoToDefinitionService(IEnumerable> presenters, IEnumerable> externalDefinitionProviders) + protected AbstractGoToDefinitionService( + IEnumerable> presenters, + IEnumerable> streamingPresenters) { _presenters = presenters; - _externalDefinitionProviders = externalDefinitionProviders; + _streamingPresenters = streamingPresenters; } private async Task FindSymbolAsync(Document document, int position, CancellationToken cancellationToken) @@ -59,11 +61,6 @@ public async Task> FindDefinitionsAsync(Document doc var items = symbol != null ? NavigableItemFactory.GetItemsFromPreferredSourceLocations(document.Project.Solution, symbol, displayTaggedParts: null) : null; - if (items == null || items.IsEmpty()) - { - // Fallback to asking the navigation definition providers for navigable definition locations. - items = await GoToDefinitionHelpers.FindExternalDefinitionsAsync(document, position, _externalDefinitionProviders, cancellationToken).ConfigureAwait(false); - } // realize the list here so that the consumer await'ing the result doesn't lazily cause // them to be created on an inappropriate thread. @@ -74,21 +71,20 @@ public bool TryGoToDefinition(Document document, int position, CancellationToken { // First try to compute the referenced symbol and attempt to go to definition for the symbol. var symbol = FindSymbolAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken); - if (symbol != null) + if (symbol == null) { - var isThirdPartyNavigationAllowed = IsThirdPartyNavigationAllowed(symbol, position, document, cancellationToken); - - return GoToDefinitionHelpers.TryGoToDefinition(symbol, - document.Project, - _externalDefinitionProviders, - _presenters, - thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed, - throwOnHiddenDefinition: true, - cancellationToken: cancellationToken); + return false; } - // Otherwise, fallback to the external navigation definition providers. - return GoToDefinitionHelpers.TryExternalGoToDefinition(document, position, _externalDefinitionProviders, _presenters, cancellationToken); + var isThirdPartyNavigationAllowed = IsThirdPartyNavigationAllowed(symbol, position, document, cancellationToken); + + return GoToDefinitionHelpers.TryGoToDefinition(symbol, + document.Project, + _presenters, + _streamingPresenters, + thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed, + throwOnHiddenDefinition: true, + cancellationToken: cancellationToken); } private static bool IsThirdPartyNavigationAllowed(ISymbol symbolToNavigateTo, int caretPosition, Document document, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionCommandHandler.cs b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionCommandHandler.cs similarity index 98% rename from src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionCommandHandler.cs rename to src/EditorFeatures/Core/GoToDefinition/GoToDefinitionCommandHandler.cs index 9e4f668006d9a32a124be4f61d8b7fcc58ecc085..43ca3a34f9b61f1f384db67425e8d448a407ab69 100644 --- a/src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionCommandHandler.cs +++ b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionCommandHandler.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; -namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition +namespace Microsoft.CodeAnalysis.Editor.GoToDefinition { [ExportCommandHandler(PredefinedCommandHandlerNames.GoToDefinition, ContentTypeNames.RoslynContentType)] diff --git a/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs new file mode 100644 index 0000000000000000000000000000000000000000..feb070177c1d61085a777c091689ef6148dc5410 --- /dev/null +++ b/src/EditorFeatures/Core/GoToDefinition/GoToDefinitionHelpers.cs @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.FindUsages; + +namespace Microsoft.CodeAnalysis.Editor.GoToDefinition +{ + internal static class GoToDefinitionHelpers + { + public static bool TryGoToDefinition( + ISymbol symbol, + Project project, + IEnumerable> presenters, + IEnumerable> streamingPresenters, + CancellationToken cancellationToken, + bool thirdPartyNavigationAllowed = true, + bool throwOnHiddenDefinition = false) + { + var alias = symbol as IAliasSymbol; + if (alias != null) + { + var ns = alias.Target as INamespaceSymbol; + if (ns != null && ns.IsGlobalNamespace) + { + return false; + } + } + + // VB global import aliases have a synthesized SyntaxTree. + // We can't go to the definition of the alias, so use the target type. + + var solution = project.Solution; + if (symbol is IAliasSymbol && + NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).All(l => project.Solution.GetDocument(l.SourceTree) == null)) + { + symbol = ((IAliasSymbol)symbol).Target; + } + + var definition = SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).WaitAndGetResult(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + symbol = definition ?? symbol; + + if (thirdPartyNavigationAllowed && TryThirdPartyNavigation(symbol, solution)) + { + return true; + } + + // If it is a partial method declaration with no body, choose to go to the implementation + // that has a method body. + if (symbol is IMethodSymbol) + { + symbol = ((IMethodSymbol)symbol).PartialImplementationPart ?? symbol; + } + + var options = project.Solution.Options; + + var preferredSourceLocations = NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).ToArray(); + var displayParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(project, symbol); + var title = displayParts.JoinText(); + + if (preferredSourceLocations.Length == 0) + { + // If there are no visible source locations, then tell the host about the symbol and + // allow it to navigate to it. This will either navigate to any non-visible source + // locations, or it can appropriately deal with metadata symbols for hosts that can go + // to a metadata-as-source view. + + var symbolNavigationService = solution.Workspace.Services.GetService(); + return symbolNavigationService.TryNavigateToSymbol( + symbol, project, + options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true), + cancellationToken: cancellationToken); + } + else if (preferredSourceLocations.Length == 1) + { + var item = NavigableItemFactory.GetItemFromSymbolLocation( + solution, symbol, preferredSourceLocations[0], + displayTaggedParts: null); + return TryGoToSingleLocation(item, options, throwOnHiddenDefinition); + } + else + { + // We have multiple viable source locations, so ask the host what to do. Most hosts + // will simply display the results to the user and allow them to choose where to + // go. + + return TryPresentInFindUsagesPresenter(solution, symbol, streamingPresenters, cancellationToken) || + TryPresentInNavigableItemsPresenter(solution, symbol, presenters, title, preferredSourceLocations); + } + } + + private static bool TryPresentInFindUsagesPresenter( + Solution solution, ISymbol symbol, + IEnumerable> streamingPresenters, + CancellationToken cancellationToken) + { + var presenter = GetFindUsagesPresenter(streamingPresenters); + if (presenter == null) + { + return false; + } + + var definition = symbol.ToDefinitionItem(solution); + var context = presenter.StartSearch(EditorFeaturesResources.Go_to_Definition); + try + { + context.OnDefinitionFoundAsync(definition).Wait(cancellationToken); + } + finally + { + context.OnCompletedAsync().Wait(cancellationToken); + } + + return true; + } + + private static IStreamingFindUsagesPresenter GetFindUsagesPresenter( + IEnumerable> streamingPresenters) + { + try + { + return streamingPresenters.FirstOrDefault()?.Value; + } + catch + { + return null; + } + } + + private static bool TryPresentInNavigableItemsPresenter( + Solution solution, ISymbol symbol, + IEnumerable> presenters, + string title, Location[] locations) + { + var presenter = presenters.FirstOrDefault(); + if (presenter != null) + { + var navigableItems = locations.Select(location => + NavigableItemFactory.GetItemFromSymbolLocation( + solution, symbol, location, + displayTaggedParts: null)).ToImmutableArray(); + + presenter.Value.DisplayResult(title, navigableItems); + return true; + } + + return false; + } + + private static bool TryGoToSingleLocation( + INavigableItem navigableItem, OptionSet options, bool throwOnHiddenDefinition) + { + var firstItem = navigableItem; + var workspace = firstItem.Document.Project.Solution.Workspace; + var navigationService = workspace.Services.GetService(); + + if (navigationService.CanNavigateToSpan(workspace, firstItem.Document.Id, firstItem.SourceSpan)) + { + return navigationService.TryNavigateToSpan( + workspace, + documentId: firstItem.Document.Id, + textSpan: firstItem.SourceSpan, + options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true)); + } + else + { + if (throwOnHiddenDefinition) + { + const int E_FAIL = -2147467259; + throw new COMException(EditorFeaturesResources.The_definition_of_the_object_is_hidden, E_FAIL); + } + else + { + return false; + } + } + } + + private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution) + { + var symbolNavigationService = solution.Workspace.Services.GetService(); + + // Notify of navigation so third parties can intercept the navigation + return symbolNavigationService.TrySymbolNavigationNotify(symbol, solution); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/GoToDefinition/IGoToDefinitionService.cs b/src/EditorFeatures/Core/GoToDefinition/IGoToDefinitionService.cs similarity index 100% rename from src/EditorFeatures/Core/Implementation/GoToDefinition/IGoToDefinitionService.cs rename to src/EditorFeatures/Core/GoToDefinition/IGoToDefinitionService.cs diff --git a/src/EditorFeatures/Core/GoToImplementation/AbstractGoToImplementationService.cs b/src/EditorFeatures/Core/GoToImplementation/AbstractGoToImplementationService.cs new file mode 100644 index 0000000000000000000000000000000000000000..5ec0bf6bcd338bb2db92804bcbaf029a85c7a0c7 --- /dev/null +++ b/src/EditorFeatures/Core/GoToImplementation/AbstractGoToImplementationService.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.Editor.GoToDefinition; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.GoToImplementation +{ + internal abstract class AbstractGoToImplementationService : IGoToImplementationService + { + private readonly IEnumerable> _navigableItemPresenters; + + public AbstractGoToImplementationService( + IEnumerable> navigableItemPresenters) + { + _navigableItemPresenters = navigableItemPresenters; + } + + public bool TryGoToImplementation(Document document, int position, CancellationToken cancellationToken, out string message) + { + var result = FindUsagesHelpers.FindImplementationsAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken); + if (result == null) + { + message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret; + return false; + } + + if (result.Value.message != null) + { + message = result.Value.message; + return false; + } + + return TryGoToImplementations( + result.Value.symbol, result.Value.project, + result.Value.implementations, cancellationToken, out message); + } + + private bool TryGoToImplementations( + ISymbol symbol, Project project, ImmutableArray implementations, CancellationToken cancellationToken, out string message) + { + if (implementations.Length == 0) + { + message = EditorFeaturesResources.The_symbol_has_no_implementations; + return false; + } + else if (implementations.Length == 1) + { + GoToDefinitionHelpers.TryGoToDefinition( + implementations.Single(), project, _navigableItemPresenters, + SpecializedCollections.EmptyEnumerable>(), + cancellationToken); + message = null; + return true; + } + else + { + return TryPresentInNavigableItemsPresenter(symbol, project, implementations, out message); + } + } + + private bool TryPresentInNavigableItemsPresenter( + ISymbol symbol, Project project, ImmutableArray implementations, out string message) + { + // We have multiple symbols, so we'll build a list of all preferred locations for all the symbols + var navigableItems = implementations.SelectMany( + implementation => CreateItemsForImplementation(implementation, project.Solution)); + + var presenter = _navigableItemPresenters.First(); + + var taggedParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(project, symbol); + + presenter.Value.DisplayResult(taggedParts.JoinText(), navigableItems); + message = null; + return true; + } + + private static IEnumerable CreateItemsForImplementation(ISymbol implementation, Solution solution) + { + var symbolDisplayService = solution.Workspace.Services.GetLanguageServices(implementation.Language).GetRequiredService(); + + return NavigableItemFactory.GetItemsFromPreferredSourceLocations( + solution, + implementation, + displayTaggedParts: symbolDisplayService.ToDisplayParts(implementation).ToTaggedText()); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs b/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..28611f7a0f28be45ab7952f1a0888cdaaa16a05e --- /dev/null +++ b/src/EditorFeatures/Core/GoToImplementation/GoToImplementationCommandHandler.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Editor.GoToImplementation +{ + [ExportCommandHandler(PredefinedCommandHandlerNames.GoToImplementation, + ContentTypeNames.RoslynContentType)] + internal partial class GoToImplementationCommandHandler : ICommandHandler + { + private readonly IWaitIndicator _waitIndicator; + private readonly IEnumerable> _streamingPresenters; + + [ImportingConstructor] + public GoToImplementationCommandHandler( + IWaitIndicator waitIndicator, + [ImportMany] IEnumerable> streamingPresenters) + { + _waitIndicator = waitIndicator; + _streamingPresenters = streamingPresenters; + } + + public CommandState GetCommandState(GoToImplementationCommandArgs args, Func nextHandler) + { + // Because this is expensive to compute, we just always say yes + return CommandState.Available; + } + + public void ExecuteCommand(GoToImplementationCommandArgs args, Action nextHandler) + { + var caret = args.TextView.GetCaretPoint(args.SubjectBuffer); + + if (caret.HasValue) + { + var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document != null) + { + ExecuteCommand(document, caret.Value); + return; + } + } + + nextHandler(); + } + + private void ExecuteCommand(Document document, int caretPosition) + { + var streamingService = document.GetLanguageService(); + var synchronousService = document.GetLanguageService(); + + var streamingPresenter = GetStreamingPresenter(); + + // 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. + var streamingEnabled = document.Project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.StreamingGoToImplementation, document.Project.Language); + var canUseStreamingWindow = streamingEnabled && streamingService != null && streamingPresenter != null; + var canUseSynchronousWindow = synchronousService != null; + + if (canUseStreamingWindow || canUseSynchronousWindow) + { + // We have all the cheap stuff, so let's do expensive stuff now + string messageToShow = null; + _waitIndicator.Wait( + EditorFeaturesResources.Go_To_Implementation, + EditorFeaturesResources.Locating_implementations, + allowCancel: true, + action: context => + { + if (canUseStreamingWindow) + { + StreamingGoToImplementation( + document, caretPosition, + streamingService, streamingPresenter, + context.CancellationToken, out messageToShow); + } + else + { + synchronousService.TryGoToImplementation( + document, caretPosition, context.CancellationToken, out messageToShow); + } + }); + + if (messageToShow != null) + { + var notificationService = document.Project.Solution.Workspace.Services.GetService(); + notificationService.SendNotification(messageToShow, + title: EditorFeaturesResources.Go_To_Implementation, + severity: NotificationSeverity.Information); + } + } + } + + private void StreamingGoToImplementation( + Document document, int caretPosition, + IFindUsagesService findUsagesService, + IStreamingFindUsagesPresenter streamingPresenter, + CancellationToken cancellationToken, + out string messageToShow) + { + // We create our own context object, simply to capture all the definitions reported by + // the individual IFindUsagesService. Once we get the results back we'll then decide + // what to do with them. If we get only a single result back, then we'll just go + // directly to it. Otherwise, we'll present the results in the IStreamingFindUsagesPresenter. + var goToImplContext = new SimpleFindUsagesContext(cancellationToken); + findUsagesService.FindImplementationsAsync(document, caretPosition, goToImplContext).Wait(cancellationToken); + + // If finding implementations reported a message, then just stop and show that + // message to the user. + messageToShow = goToImplContext.Message; + if (messageToShow != null) + { + return; + } + + var definitionItems = goToImplContext.GetDefinitions(); + + streamingPresenter.NavigateToOrPresentItemsAsync( + EditorFeaturesResources.Go_To_Implementation, definitionItems).Wait(cancellationToken); + } + + private IStreamingFindUsagesPresenter GetStreamingPresenter() + { + try + { + return _streamingPresenters.FirstOrDefault()?.Value; + } + catch + { + return null; + } + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/GoToImplementation/IGoToImplementationService.cs b/src/EditorFeatures/Core/GoToImplementation/IGoToImplementationService.cs similarity index 100% rename from src/EditorFeatures/Core/Implementation/GoToImplementation/IGoToImplementationService.cs rename to src/EditorFeatures/Core/GoToImplementation/IGoToImplementationService.cs diff --git a/src/EditorFeatures/Core/Host/IDefinitionsAndReferencesPresenter.cs b/src/EditorFeatures/Core/Host/IDefinitionsAndReferencesPresenter.cs index 47fba57ae8e4cf4a3a616e16ed28e3a9e2970f0b..70256bae4736e07cdb7f5d91c6f713bc0d916ca9 100644 --- a/src/EditorFeatures/Core/Host/IDefinitionsAndReferencesPresenter.cs +++ b/src/EditorFeatures/Core/Host/IDefinitionsAndReferencesPresenter.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; namespace Microsoft.CodeAnalysis.Editor.Host { diff --git a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs index 186d4d96c66b8ec6f59f99b2a2e27b6b8c141637..4c98d241aa257fef01bf1c8ed24cf7ca94573b6b 100644 --- a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs +++ b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs @@ -1,21 +1,82 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; + namespace Microsoft.CodeAnalysis.Editor.Host { /// - /// API for hosts to provide if they can present FindReferences results in a streaming manner. + /// API for hosts to provide if they can present FindUsages results in a streaming manner. /// i.e. if they support showing results as they are found instead of after all of the results /// are found. /// - internal interface IStreamingFindReferencesPresenter + internal interface IStreamingFindUsagesPresenter { /// - /// Tells the presenter that a search is starting. The returned + /// Tells the presenter that a search is starting. The returned /// is used to push information about the search into. i.e. when a reference is found - /// should be called. When the - /// search completes should be called. + /// should be called. When the + /// search completes should be called. /// etc. etc. /// - FindReferencesContext StartSearch(); + FindUsagesContext StartSearch(string title); + } + + internal static class IStreamingFindUsagesPresenterExtensions + { + /// + /// If there's only a single item, navigates to it. Otherwise, presents all the + /// items to the user. + /// + public static async Task NavigateToOrPresentItemsAsync( + this IStreamingFindUsagesPresenter presenter, + string title, ImmutableArray items) + { + // Ignore any definitions that we can't navigate to. + var definitions = items.WhereAsArray(d => d.CanNavigateTo()); + + // See if there's a third party external item we can navigate to. If so, defer + // to that item and finish. + var externalItems = definitions.WhereAsArray(d => d.IsExternal); + foreach (var item in externalItems) + { + if (item.TryNavigateTo()) + { + return; + } + } + + var nonExternalItems = definitions.WhereAsArray(d => !d.IsExternal); + if (nonExternalItems.Length == 0) + { + return; + } + + if (nonExternalItems.Length == 1 && + nonExternalItems[0].SourceSpans.Length <= 1) + { + // There was only one location to navigate to. Just directly go to that location. + nonExternalItems[0].TryNavigateTo(); + return; + } + + // We have multiple definitions, or we have definitions with multiple locations. + // Present this to the user so they can decide where they want to go to. + + var context = presenter.StartSearch(title); + foreach (var definition in nonExternalItems) + { + await context.OnDefinitionFoundAsync(definition).ConfigureAwait(false); + } + + // Note: we don't need to put this in a finally. The only time we might not hit + // this is if cancellation or another error gets thrown. In the former case, + // that means that a new search has started. We don't care about telling the + // context it has completed. In the latter case somethign wrong has happened + // and we don't want to run any more code code in this particular context. + await context.OnCompletedAsync().ConfigureAwait(false); + } } } \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.cs b/src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.cs deleted file mode 100644 index c661febe8b19f03bbeaba896db1b8665bd20f782..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/FindReferences/AbstractFindReferencesService.cs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.SymbolMapping; -using Microsoft.CodeAnalysis.FindReferences; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.FindReferences -{ - internal abstract partial class AbstractFindReferencesService : - ForegroundThreadAffinitizedObject, IFindReferencesService, IStreamingFindReferencesService - { - private readonly IEnumerable _referenceSymbolPresenters; - private readonly IEnumerable _navigableItemPresenters; - private readonly IEnumerable _externalReferencesProviders; - - protected AbstractFindReferencesService( - IEnumerable referenceSymbolPresenters, - IEnumerable navigableItemPresenters, - IEnumerable externalReferencesProviders) - { - _referenceSymbolPresenters = referenceSymbolPresenters; - _navigableItemPresenters = navigableItemPresenters; - _externalReferencesProviders = externalReferencesProviders; - } - - /// - /// Common helper for both the synchronous and streaming versions of FAR. - /// It returns the symbol we want to search for and the solution we should - /// be searching. - /// - /// Note that the returned may absolutely *not* be - /// the same as document.Project.Solution. This is because - /// there may be symbol mapping involved (for example in Metadata-As-Source - /// scenarios). - /// - private async Task> GetRelevantSymbolAndProjectAtPositionAsync( - Document document, int position, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false); - if (symbol == null) - { - return null; - } - - // If this document is not in the primary workspace, we may want to search for results - // in a solution different from the one we started in. Use the starting workspace's - // ISymbolMappingService to get a context for searching in the proper solution. - var mappingService = document.Project.Solution.Workspace.Services.GetService(); - - var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false); - if (mapping == null) - { - return null; - } - - return Tuple.Create(mapping.Symbol, mapping.Project); - } - - /// - /// Finds references using the externally defined s. - /// - private async Task AddExternalReferencesAsync(Document document, int position, ArrayBuilder builder, CancellationToken cancellationToken) - { - // CONSIDER: Do the computation in parallel. - foreach (var provider in _externalReferencesProviders) - { - var references = await provider.FindReferencesAsync(document, position, cancellationToken).ConfigureAwait(false); - if (references != null) - { - builder.AddRange(references.WhereNotNull()); - } - } - } - - private async Task, Solution>> FindReferencedSymbolsAsync( - Document document, int position, IWaitContext waitContext) - { - var cancellationToken = waitContext.CancellationToken; - - var symbolAndProject = await GetRelevantSymbolAndProjectAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndProject == null) - { - return null; - } - - var symbol = symbolAndProject.Item1; - var project = symbolAndProject.Item2; - - var displayName = GetDisplayName(symbol); - - waitContext.Message = string.Format( - EditorFeaturesResources.Finding_references_of_0, displayName); - - var result = await SymbolFinder.FindReferencesAsync(symbol, project.Solution, cancellationToken).ConfigureAwait(false); - - return Tuple.Create(result, project.Solution); - } - - private static string GetDisplayName(ISymbol symbol) - { - return symbol.IsConstructor() ? symbol.ContainingType.Name : symbol.Name; - } - - public bool TryFindReferences(Document document, int position, IWaitContext waitContext) - { - var cancellationToken = waitContext.CancellationToken; - var workspace = document.Project.Solution.Workspace; - - // First see if we have any external navigable item references. - // If so, we display the results as navigable items. - var succeeded = TryFindAndDisplayNavigableItemsReferencesAsync(document, position, waitContext).WaitAndGetResult(cancellationToken); - if (succeeded) - { - return true; - } - - // Otherwise, fall back to displaying SymbolFinder based references. - var result = this.FindReferencedSymbolsAsync(document, position, waitContext).WaitAndGetResult(cancellationToken); - return TryDisplayReferences(result); - } - - /// - /// Attempts to find and display navigable item references, including the references provided by external providers. - /// - /// False if there are no external references or display was not successful. - private async Task TryFindAndDisplayNavigableItemsReferencesAsync(Document document, int position, IWaitContext waitContext) - { - var foundReferences = false; - if (_externalReferencesProviders.Any()) - { - var cancellationToken = waitContext.CancellationToken; - var builder = ArrayBuilder.GetInstance(); - await AddExternalReferencesAsync(document, position, builder, cancellationToken).ConfigureAwait(false); - - // TODO: Merging references from SymbolFinder and external providers might lead to duplicate or counter-intuitive results. - // TODO: For now, we avoid merging and just display the results either from SymbolFinder or the external result providers but not both. - if (builder.Count > 0 && TryDisplayReferences(builder)) - { - foundReferences = true; - } - - builder.Free(); - } - - return foundReferences; - } - - private bool TryDisplayReferences(IEnumerable result) - { - if (result != null && result.Any()) - { - var title = result.First().DisplayTaggedParts.JoinText(); - foreach (var presenter in _navigableItemPresenters) - { - presenter.DisplayResult(title, result); - return true; - } - } - - return false; - } - - private bool TryDisplayReferences(Tuple, Solution> result) - { - if (result != null && result.Item1 != null) - { - var solution = result.Item2; - var factory = solution.Workspace.Services.GetService(); - var definitionsAndReferences = factory.CreateDefinitionsAndReferences( - solution, result.Item1); - - foreach (var presenter in _referenceSymbolPresenters) - { - presenter.DisplayResult(definitionsAndReferences); - return true; - } - } - - return false; - } - - public async Task FindReferencesAsync( - Document document, int position, FindReferencesContext context) - { - // NOTE: All ConFigureAwaits in this method need to pass 'true' so that - // we return to the caller's context. that's so the call to - // CallThirdPartyExtensionsAsync will happen on the UI thread. We need - // this to maintain the threading guarantee we had around that method - // from pre-Roslyn days. - var findReferencesProgress = await FindReferencesWorkerAsync( - document, position, context).ConfigureAwait(true); - if (findReferencesProgress == null) - { - return; - } - - // After the FAR engine is done call into any third party extensions to see - // if they want to add results. - await findReferencesProgress.CallThirdPartyExtensionsAsync().ConfigureAwait(true); - } - - private async Task FindReferencesWorkerAsync( - Document document, int position, FindReferencesContext context) - { - var cancellationToken = context.CancellationToken; - cancellationToken.ThrowIfCancellationRequested(); - - // Find the symbol we want to search and the solution we want to search in. - var symbolAndProject = await GetRelevantSymbolAndProjectAtPositionAsync( - document, position, cancellationToken).ConfigureAwait(false); - if (symbolAndProject == null) - { - return null; - } - - var symbol = symbolAndProject.Item1; - var project = symbolAndProject.Item2; - - var displayName = GetDisplayName(symbol); - context.SetSearchLabel(displayName); - - var progressAdapter = new ProgressAdapter(project.Solution, context); - - // Now call into the underlying FAR engine to find reference. The FAR - // engine will push results into the 'progress' instance passed into it. - // We'll take those results, massage them, and forward them along to the - // FindReferencesContext instance we were given. - await SymbolFinder.FindReferencesAsync( - SymbolAndProjectId.Create(symbol, project.Id), - project.Solution, - progressAdapter, - documents: null, - cancellationToken: cancellationToken).ConfigureAwait(false); - - return progressAdapter; - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesNavigableItemResultProvider.cs b/src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesNavigableItemResultProvider.cs deleted file mode 100644 index 25c3863f1b3cf4dd9547741b486ffc3f3761e5b4..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/FindReferences/IFindReferencesNavigableItemResultProvider.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Navigation; - -namespace Microsoft.CodeAnalysis.Editor -{ - internal interface IFindReferencesResultProvider - { - /// - /// Compute navigable reference items for the symbol referenced or defined at the given position in the given document. - /// - Task> FindReferencesAsync(Document document, int position, CancellationToken cancellationToken); - } -} diff --git a/src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionHelpers.cs b/src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionHelpers.cs deleted file mode 100644 index 7e405419560bf4e403fd3d9f6b4210c4e1e502a9..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/GoToDefinition/GoToDefinitionHelpers.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition -{ - internal static class GoToDefinitionHelpers - { - public static bool TryGoToDefinition( - ISymbol symbol, - Project project, - IEnumerable> externalDefinitionProviders, - IEnumerable> presenters, - CancellationToken cancellationToken, - bool thirdPartyNavigationAllowed = true, - bool throwOnHiddenDefinition = false) - { - var alias = symbol as IAliasSymbol; - if (alias != null) - { - var ns = alias.Target as INamespaceSymbol; - if (ns != null && ns.IsGlobalNamespace) - { - return false; - } - } - - // VB global import aliases have a synthesized SyntaxTree. - // We can't go to the definition of the alias, so use the target type. - - var solution = project.Solution; - if (symbol is IAliasSymbol && - NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).All(l => project.Solution.GetDocument(l.SourceTree) == null)) - { - symbol = ((IAliasSymbol)symbol).Target; - } - - var definition = SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).WaitAndGetResult(cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - - symbol = definition ?? symbol; - - if (thirdPartyNavigationAllowed && TryThirdPartyNavigation(symbol, solution)) - { - return true; - } - - // If it is a partial method declaration with no body, choose to go to the implementation - // that has a method body. - if (symbol is IMethodSymbol) - { - symbol = ((IMethodSymbol)symbol).PartialImplementationPart ?? symbol; - } - - var options = project.Solution.Options; - - var preferredSourceLocations = NavigableItemFactory.GetPreferredSourceLocations(solution, symbol).ToArray(); - var displayParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(project, symbol); - var title = displayParts.JoinText(); - - if (!preferredSourceLocations.Any()) - { - // Attempt to find source locations from external definition providers. - if (thirdPartyNavigationAllowed && externalDefinitionProviders.Any()) - { - var externalSourceDefinitions = FindExternalDefinitionsAsync(symbol, project, externalDefinitionProviders, cancellationToken).WaitAndGetResult(cancellationToken).ToImmutableArray(); - if (externalSourceDefinitions.Length > 0) - { - return TryGoToDefinition(externalSourceDefinitions, title, options, presenters, throwOnHiddenDefinition); - } - } - - // If there are no visible source locations, then tell the host about the symbol and - // allow it to navigate to it. This will either navigate to any non-visible source - // locations, or it can appropriately deal with metadata symbols for hosts that can go - // to a metadata-as-source view. - - var symbolNavigationService = solution.Workspace.Services.GetService(); - return symbolNavigationService.TryNavigateToSymbol( - symbol, project, - options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true), - cancellationToken: cancellationToken); - } - - var navigableItems = preferredSourceLocations.Select(location => - NavigableItemFactory.GetItemFromSymbolLocation( - solution, symbol, location, - displayTaggedParts: null)).ToImmutableArray(); - return TryGoToDefinition(navigableItems, title, options, presenters, throwOnHiddenDefinition); - } - - private static bool TryGoToDefinition( - ImmutableArray navigableItems, - string title, - OptionSet options, - IEnumerable> presenters, - bool throwOnHiddenDefinition) - { - Contract.ThrowIfNull(options); - - // If we have a single location, then just navigate to it. - if (navigableItems.Length == 1 && navigableItems[0].Document != null) - { - var firstItem = navigableItems[0]; - var workspace = firstItem.Document.Project.Solution.Workspace; - var navigationService = workspace.Services.GetService(); - - if (navigationService.CanNavigateToSpan(workspace, firstItem.Document.Id, firstItem.SourceSpan)) - { - return navigationService.TryNavigateToSpan( - workspace, - documentId: firstItem.Document.Id, - textSpan: firstItem.SourceSpan, - options: options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true)); - } - else - { - if (throwOnHiddenDefinition) - { - const int E_FAIL = -2147467259; - throw new COMException(EditorFeaturesResources.The_definition_of_the_object_is_hidden, E_FAIL); - } - else - { - return false; - } - } - } - else - { - // We have multiple viable source locations, so ask the host what to do. Most hosts - // will simply display the results to the user and allow them to choose where to - // go. - - if (presenters.Any()) - { - presenters.First().Value.DisplayResult(title, navigableItems); - return true; - } - - return false; - } - } - - public static async Task> FindExternalDefinitionsAsync(Document document, int position, IEnumerable> externalDefinitionProviders, CancellationToken cancellationToken) - { - foreach (var definitionProvider in externalDefinitionProviders) - { - var definitions = await definitionProvider.Value.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); - if (definitions != null && definitions.Any()) - { - var preferredDefinitions = NavigableItemFactory.GetPreferredNavigableItems(document.Project.Solution, definitions); - if (preferredDefinitions.Any()) - { - return preferredDefinitions; - } - } - } - - return SpecializedCollections.EmptyEnumerable(); - } - - public static async Task> FindExternalDefinitionsAsync(ISymbol symbol, Project project, IEnumerable> externalDefinitionProviders, CancellationToken cancellationToken) - { - foreach (var definitionProvider in externalDefinitionProviders) - { - var definitions = await definitionProvider.Value.FindDefinitionsAsync(project, symbol, cancellationToken).ConfigureAwait(false); - if (definitions != null && definitions.Any()) - { - var preferredDefinitions = NavigableItemFactory.GetPreferredNavigableItems(project.Solution, definitions); - if (preferredDefinitions.Any()) - { - return preferredDefinitions; - } - } - } - - return SpecializedCollections.EmptyEnumerable(); - } - - public static bool TryExternalGoToDefinition(Document document, int position, IEnumerable> externalDefinitionProviders, IEnumerable> presenters, CancellationToken cancellationToken) - { - var externalDefinitions = FindExternalDefinitionsAsync(document, position, externalDefinitionProviders, cancellationToken).WaitAndGetResult(cancellationToken); - if (externalDefinitions != null && externalDefinitions.Any()) - { - var itemsArray = externalDefinitions.ToImmutableArrayOrEmpty(); - var title = itemsArray[0].DisplayTaggedParts.JoinText(); - return TryGoToDefinition(externalDefinitions.ToImmutableArrayOrEmpty(), title, document.Project.Solution.Workspace.Options, presenters, throwOnHiddenDefinition: true); - } - - return false; - } - - private static bool TryThirdPartyNavigation(ISymbol symbol, Solution solution) - { - var symbolNavigationService = solution.Workspace.Services.GetService(); - - // Notify of navigation so third parties can intercept the navigation - return symbolNavigationService.TrySymbolNavigationNotify(symbol, solution); - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/GoToDefinition/INavigableDefinitionProvider.cs b/src/EditorFeatures/Core/Implementation/GoToDefinition/INavigableDefinitionProvider.cs deleted file mode 100644 index e615bd96e7eda7472c6b30a04f5d569125586b33..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/GoToDefinition/INavigableDefinitionProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Navigation; - -namespace Microsoft.CodeAnalysis.Editor -{ - internal interface INavigableDefinitionProvider - { - /// - /// Find definitions for the symbol referenced or defined at the given position in the given document. - /// - Task> FindDefinitionsAsync(Document document, int position, CancellationToken cancellationToken); - - /// - /// Find definitions for the given symbol in the context of the given project. - /// - Task> FindDefinitionsAsync(Project project, ISymbol symbol, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/GoToImplementation/AbstractGoToImplementationService.cs b/src/EditorFeatures/Core/Implementation/GoToImplementation/AbstractGoToImplementationService.cs deleted file mode 100644 index f5b1c666dd0e52347a1169d4aa92dfae18fe649e..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/GoToImplementation/AbstractGoToImplementationService.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.SymbolMapping; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation -{ - internal abstract class AbstractGoToImplementationService : IGoToImplementationService - { - private readonly IEnumerable> _navigableItemPresenters; - private readonly IEnumerable> _externalDefinitionProviders; - - public AbstractGoToImplementationService( - IEnumerable> navigableItemPresenters, - IEnumerable> externalDefinitionProviders) - { - _navigableItemPresenters = navigableItemPresenters; - _externalDefinitionProviders = externalDefinitionProviders; - } - - public bool TryGoToImplementation(Document document, int position, CancellationToken cancellationToken, out string message) - { - var symbol = SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken); - if (symbol != null) - { - // Map the symbol if necessary back to the originating workspace if we're invoking from something - // like metadata as source - var mappingService = document.Project.Solution.Workspace.Services.GetRequiredService(); - var mapping = mappingService.MapSymbolAsync(document, symbol, cancellationToken).WaitAndGetResult(cancellationToken); - - if (mapping != null) - { - return TryGoToImplementationOnMappedSymbol(mapping, cancellationToken, out message); - } - } - else - { - return TryExternalGotoDefinition(document, position, cancellationToken, out message); - } - - message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret; - return false; - } - - private bool TryGoToImplementationOnMappedSymbol(SymbolMappingResult mapping, CancellationToken cancellationToken, out string message) - { - if (mapping.Symbol.IsInterfaceType() || mapping.Symbol.IsImplementableMember()) - { - var implementations = - SymbolFinder.FindImplementationsAsync(mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken) - .WaitAndGetResult(cancellationToken); - - // It's important we use a HashSet here -- we may have cases in an inheritence hierarchy where more than one method - // in an overrides chain implements the same interface method, and we want to duplicate those. The easiest way to do it - // is to just use a HashSet. - var implementationsAndOverrides = new HashSet(); - - foreach (var implementation in implementations) - { - implementationsAndOverrides.Add(implementation); - - // FindImplementationsAsync will only return the base virtual/abstract method, not that method and the overrides - // of the method. We should also include those. - if (implementation.IsOverridable()) - { - implementationsAndOverrides.AddRange( - SymbolFinder.FindOverridesAsync(implementation, mapping.Solution, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken)); - } - } - - return TryGoToImplementations(implementationsAndOverrides, mapping, cancellationToken, out message); - } - else if ((mapping.Symbol as INamedTypeSymbol)?.TypeKind == TypeKind.Class) - { - var implementations = - SymbolFinder.FindDerivedClassesAsync((INamedTypeSymbol)mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken) - .WaitAndGetResult(cancellationToken) - .Concat(mapping.Symbol); - - return TryGoToImplementations(implementations, mapping, cancellationToken, out message); - } - else if (mapping.Symbol.IsOverridable()) - { - var implementations = - SymbolFinder.FindOverridesAsync(mapping.Symbol, mapping.Solution, cancellationToken: cancellationToken) - .WaitAndGetResult(cancellationToken) - .Concat(mapping.Symbol); - - return TryGoToImplementations(implementations, mapping, cancellationToken, out message); - } - else - { - // This is something boring like a regular method or type, so we'll just go there directly - if (GoToDefinition.GoToDefinitionHelpers.TryGoToDefinition(mapping.Symbol, mapping.Project, _externalDefinitionProviders, _navigableItemPresenters, cancellationToken)) - { - message = null; - return true; - } - else - { - message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret; - return false; - } - } - } - - private bool TryGoToImplementations(IEnumerable candidateImplementations, SymbolMappingResult mapping, CancellationToken cancellationToken, out string message) - { - var implementations = candidateImplementations - .Where(s => !s.IsAbstract && s.Locations.Any(l => l.IsInSource)) - .ToList(); - - if (implementations.Count == 0) - { - message = EditorFeaturesResources.The_symbol_has_no_implementations; - return false; - } - else if (implementations.Count == 1) - { - GoToDefinition.GoToDefinitionHelpers.TryGoToDefinition(implementations.Single(), mapping.Project, _externalDefinitionProviders, _navigableItemPresenters, cancellationToken); - message = null; - return true; - } - else - { - // We have multiple symbols, so we'll build a list of all preferred locations for all the symbols - var navigableItems = implementations.SelectMany( - implementation => CreateItemsForImplementation(implementation, mapping.Solution)); - - var presenter = _navigableItemPresenters.First(); - - var taggedParts = NavigableItemFactory.GetSymbolDisplayTaggedParts(mapping.Project, mapping.Symbol); - - presenter.Value.DisplayResult(taggedParts.JoinText(), navigableItems); - message = null; - return true; - } - } - - private static IEnumerable CreateItemsForImplementation(ISymbol implementation, Solution solution) - { - var symbolDisplayService = solution.Workspace.Services.GetLanguageServices(implementation.Language).GetRequiredService(); - - return NavigableItemFactory.GetItemsFromPreferredSourceLocations( - solution, - implementation, - displayTaggedParts: symbolDisplayService.ToDisplayParts(implementation).ToTaggedText()); - } - - private bool TryExternalGotoDefinition(Document document, int position, CancellationToken cancellationToken, out string message) - { - if (GoToDefinition.GoToDefinitionHelpers.TryExternalGoToDefinition(document, position, _externalDefinitionProviders, _navigableItemPresenters, cancellationToken)) - { - message = null; - return true; - } - else - { - message = EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret; - return false; - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/GoToImplementation/GoToImplementationCommandHandler.cs b/src/EditorFeatures/Core/Implementation/GoToImplementation/GoToImplementationCommandHandler.cs deleted file mode 100644 index cb90c5479943160c137e67cf36854c6ed74fd046..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/GoToImplementation/GoToImplementationCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.Composition; -using Microsoft.CodeAnalysis.Editor.Commands; -using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation -{ - [ExportCommandHandler(PredefinedCommandHandlerNames.GoToImplementation, - ContentTypeNames.RoslynContentType)] - internal sealed class GoToImplementationCommandHandler : ICommandHandler - { - private readonly IWaitIndicator _waitIndicator; - - [ImportingConstructor] - public GoToImplementationCommandHandler( - IWaitIndicator waitIndicator) - { - _waitIndicator = waitIndicator; - } - - public CommandState GetCommandState(GoToImplementationCommandArgs args, Func nextHandler) - { - // Because this is expensive to compute, we just always say yes - return CommandState.Available; - } - - public void ExecuteCommand(GoToImplementationCommandArgs args, Action nextHandler) - { - var caret = args.TextView.GetCaretPoint(args.SubjectBuffer); - - if (caret.HasValue) - { - var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document != null) - { - var service = document.Project.LanguageServices.GetService(); - - if (service != null) - { - // We have all the cheap stuff, so let's do expensive stuff now - string messageToShow = null; - bool succeeded = false; - _waitIndicator.Wait( - EditorFeaturesResources.Go_To_Implementation, - EditorFeaturesResources.Locating_implementations, - allowCancel: true, - action: context => succeeded = service.TryGoToImplementation(document, caret.Value, context.CancellationToken, out messageToShow)); - - if (messageToShow != null) - { - var notificationService = document.Project.Solution.Workspace.Services.GetService(); - notificationService.SendNotification(messageToShow, - title: EditorFeaturesResources.Go_To_Implementation, - severity: NotificationSeverity.Information); - } - } - - return; - } - } - - nextHandler(); - } - } -} diff --git a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs index 65f134c0b46f188383c56a5aafef0b3895a7eba6..72cb18a4a0c0fd218e4b961fc52055ca58981560 100644 --- a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs +++ b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs @@ -87,9 +87,15 @@ internal static class FeatureOnOffOptions /// implemented this feature yet. /// [ExportOption] - public static readonly PerLanguageOption RefactoringVerification = new PerLanguageOption(nameof(FeatureOnOffOptions), nameof(RefactoringVerification), defaultValue: false); + public static readonly PerLanguageOption RefactoringVerification = new PerLanguageOption( + nameof(FeatureOnOffOptions), nameof(RefactoringVerification), defaultValue: false); [ExportOption] - public static readonly PerLanguageOption StreamingFindReferences = new PerLanguageOption(nameof(FeatureOnOffOptions), nameof(StreamingFindReferences), defaultValue: true); + public static readonly PerLanguageOption StreamingFindReferences = new PerLanguageOption( + nameof(FeatureOnOffOptions), nameof(StreamingFindReferences), defaultValue: true); + + [ExportOption] + public static readonly PerLanguageOption StreamingGoToImplementation = new PerLanguageOption( + nameof(FeatureOnOffOptions), nameof(StreamingGoToImplementation), defaultValue: true); } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Test/EditorServicesTest.csproj b/src/EditorFeatures/Test/EditorServicesTest.csproj index faaa7b0c7663b56ec6ea79110be891843edf6cc5..58cf8a38071f6351ab6740984210837c2ee22aa4 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -249,6 +249,7 @@ + diff --git a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs index 49f480e0202a2d629abab76c318621cbbe2fe455..78f745a22a5cbff531691fb01428ee698ed117f6 100644 --- a/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs +++ b/src/EditorFeatures/Test/FindReferences/FindReferencesCommandHandlerTests.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.FindReferences; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.FindReferences; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Roslyn.Test.Utilities; @@ -29,7 +29,7 @@ public async Task TestFindReferencesSynchronousCall() var handler = new FindReferencesCommandHandler( TestWaitIndicator.Default, SpecializedCollections.SingletonEnumerable(findReferencesPresenter), - SpecializedCollections.EmptyEnumerable>(), + SpecializedCollections.EmptyEnumerable>(), workspace.ExportProvider.GetExports()); var textView = workspace.Documents[0].GetTextView(); diff --git a/src/EditorFeatures/Test/FindReferences/MockDefinitionsAndReferencesPresenter.cs b/src/EditorFeatures/Test/FindReferences/MockDefinitionsAndReferencesPresenter.cs index 7cb45cb0b2a38a11990e3d2a066ff6bd6dffb7bb..3b9f9ed7997734f9bcd63aef03c945097574ec6e 100644 --- a/src/EditorFeatures/Test/FindReferences/MockDefinitionsAndReferencesPresenter.cs +++ b/src/EditorFeatures/Test/FindReferences/MockDefinitionsAndReferencesPresenter.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences { diff --git a/src/EditorFeatures/Test/Workspaces/WorkspaceTests.cs b/src/EditorFeatures/Test/Workspaces/WorkspaceTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..f08b6b5a5eccf36d22d441fe9e2b7b9c2d0f0604 --- /dev/null +++ b/src/EditorFeatures/Test/Workspaces/WorkspaceTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editor.Implementation.Workspaces; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +{ + public class WorkspaceTests + { + [Fact] + public void TestDefaultCompositionIncludesFeaturesLayer() + { + var ws = new AdhocWorkspace(); + + var csservice = ws.Services.GetLanguageServices(LanguageNames.CSharp).GetService(); + Assert.NotNull(csservice); + + var vbservice = ws.Services.GetLanguageServices(LanguageNames.VisualBasic).GetService(); + Assert.NotNull(vbservice); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index e2bddc15afcb0273dcfc71672989dd916b626fd3..7f7189b3e5ff9b55e31c081ec317e6fd16d32e72 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -4,11 +4,12 @@ Imports System.Collections.Immutable Imports System.Threading.Tasks Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.FindReferences +Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Text Imports Roslyn.Utilities Imports Xunit.Abstractions +Imports Microsoft.CodeAnalysis.Editor.FindUsages Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Partial Public Class FindReferencesTests @@ -38,7 +39,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Dim startDocument = workspace.CurrentSolution.GetDocument(cursorDocument.Id) Assert.NotNull(startDocument) - Dim findRefsService = startDocument.GetLanguageService(Of IStreamingFindReferencesService) + Dim findRefsService = startDocument.GetLanguageService(Of IFindUsagesService) Dim context = New TestContext() Await findRefsService.FindReferencesAsync(startDocument, cursorPosition, context) @@ -109,7 +110,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences End Structure Private Class TestContext - Inherits FindReferencesContext + Inherits FindUsagesContext Private ReadOnly gate As Object = New Object() diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb index c573ef303ca8dfce9e294c7d214184fdd7cc9bb5..0201a0de36de462ee9a4a2b2b72408fc40a3b96a 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionCommandHandlerTests.vb @@ -3,7 +3,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.CSharp.GoToDefinition Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition +Imports Microsoft.CodeAnalysis.Editor.GoToDefinition Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces diff --git a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb index 6960169ebb422790836879e091d6d0d7837dfc5e..023a56338fa4521776e411871873ff6565a61939 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/GoToDefinitionTests.vb @@ -11,10 +11,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition Public Class GoToDefinitionTests Private Function TestAsync(workspaceDefinition As XElement, Optional expectedResult As Boolean = True) As Tasks.Task Return GoToTestHelpers.TestAsync(workspaceDefinition, expectedResult, - Function(document As Document, cursorPosition As Integer, presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter)), externalDefinitionProviders As IEnumerable(Of Lazy(Of INavigableDefinitionProvider))) + Function(document As Document, cursorPosition As Integer, presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter))) Dim goToDefService = If(document.Project.Language = LanguageNames.CSharp, - DirectCast(New CSharpGoToDefinitionService(presenters, externalDefinitionProviders), IGoToDefinitionService), - New VisualBasicGoToDefinitionService(presenters, externalDefinitionProviders)) + DirectCast(New CSharpGoToDefinitionService(presenters, {}), IGoToDefinitionService), + New VisualBasicGoToDefinitionService(presenters, {})) Return goToDefService.TryGoToDefinition(document, cursorPosition, CancellationToken.None) End Function) diff --git a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb index 1823986f919ab0bb8a07adad1f4d8aebd6e69926..5171e7916671174754f45daad5f66c447d84e6fd 100644 --- a/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb +++ b/src/EditorFeatures/Test2/GoToImplementation/GoToImplementationTests.vb @@ -11,10 +11,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToImplementation Public Class GoToImplementationTests Private Function TestAsync(workspaceDefinition As XElement, Optional shouldSucceed As Boolean = True) As Tasks.Task Return GoToTestHelpers.TestAsync(workspaceDefinition, shouldSucceed, - Function(document As Document, cursorPosition As Integer, presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter)), externalDefinitionProviders As IEnumerable(Of Lazy(Of INavigableDefinitionProvider))) + Function(document As Document, cursorPosition As Integer, presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter))) Dim service = If(document.Project.Language = LanguageNames.CSharp, - DirectCast(New CSharpGoToImplementationService(presenters, externalDefinitionProviders), IGoToImplementationService), - New VisualBasicGoToImplementationService(presenters, externalDefinitionProviders)) + DirectCast(New CSharpGoToImplementationService(presenters), IGoToImplementationService), + New VisualBasicGoToImplementationService(presenters)) Dim message As String = Nothing Return service.TryGoToImplementation(document, cursorPosition, CancellationToken.None, message) diff --git a/src/EditorFeatures/TestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs b/src/EditorFeatures/TestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs index c9728bc3d93ea3ffac0d6a182223c8e5fc582942..7f80d9b0f32dc7eb24afdf266dddca3978a4b717 100644 --- a/src/EditorFeatures/TestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs +++ b/src/EditorFeatures/TestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs @@ -530,10 +530,13 @@ protected virtual IList MassageActions(IList actions) protected static IList FlattenActions(IEnumerable codeActions) { return codeActions?.SelectMany(a => a.NestedCodeActions.Length > 0 - ? a.NestedCodeActions.ToArray() + ? a.NestedCodeActions.ToArray() : new[] { a }).ToList(); } + protected (OptionKey, object) SingleOption(Option option, bool enabled) + => (new OptionKey(option), enabled); + protected (OptionKey, object) SingleOption(Option> option, bool enabled, NotificationOption notification) => SingleOption(option, new CodeStyleOption(enabled, notification)); diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/GoToTestHelpers.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/GoToTestHelpers.vb index 0ddd4f430ab0fca4d750d73ce2ecb9b7be243706..98c4a88813562a0ba78146831f5095de15b87b5a 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/GoToTestHelpers.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/GoToTestHelpers.vb @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Public ReadOnly ExportProvider As ExportProvider = MinimalTestExportProvider.CreateExportProvider(Catalog) - Public Async Function TestAsync(workspaceDefinition As XElement, expectedResult As Boolean, executeOnDocument As Func(Of Document, Integer, IEnumerable(Of Lazy(Of INavigableItemsPresenter)), IEnumerable(Of Lazy(Of INavigableDefinitionProvider)), Boolean)) As System.Threading.Tasks.Task + Public Async Function TestAsync(workspaceDefinition As XElement, expectedResult As Boolean, executeOnDocument As Func(Of Document, Integer, IEnumerable(Of Lazy(Of INavigableItemsPresenter)), Boolean)) As System.Threading.Tasks.Task Using workspace = Await TestWorkspace.CreateAsync(workspaceDefinition, exportProvider:=ExportProvider) Dim solution = workspace.CurrentSolution Dim cursorDocument = workspace.Documents.First(Function(d) d.CursorPosition.HasValue) @@ -41,7 +41,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Dim presenter = New MockNavigableItemsPresenter(Sub(i) items = i) Dim presenters = {New Lazy(Of INavigableItemsPresenter)(Function() presenter)} - Dim actualResult = executeOnDocument(document, cursorPosition, presenters, {}) + Dim actualResult = executeOnDocument(document, cursorPosition, presenters) Assert.Equal(expectedResult, actualResult) diff --git a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj index e864ee5d5bffaa32eb73346e4325144cc7c947fb..52beba08ac7efbe6323a07b6eea3a546479ed10f 100644 --- a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj @@ -100,6 +100,7 @@ + diff --git a/src/EditorFeatures/VisualBasic/FindReferences/VisualBasicFindReferencesService.vb b/src/EditorFeatures/VisualBasic/FindReferences/VisualBasicFindReferencesService.vb index b61586da6d6cef57333bb7aef74cd38523b01ae7..1ac2e725b8f0a8b245f0662d60741789aa2bb968 100644 --- a/src/EditorFeatures/VisualBasic/FindReferences/VisualBasicFindReferencesService.vb +++ b/src/EditorFeatures/VisualBasic/FindReferences/VisualBasicFindReferencesService.vb @@ -1,8 +1,8 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Composition +Imports Microsoft.CodeAnalysis.Editor.FindReferences Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Implementation.FindReferences Imports Microsoft.CodeAnalysis.Host.Mef Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.FindReferences @@ -12,21 +12,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.FindReferences Protected Sub New( referencedSymbolsPresenters As IEnumerable(Of IDefinitionsAndReferencesPresenter), - navigableItemsPresenters As IEnumerable(Of INavigableItemsPresenter), - externalReferencesProviders As IEnumerable(Of IFindReferencesResultProvider)) - MyBase.New(referencedSymbolsPresenters, navigableItemsPresenters, externalReferencesProviders) - End Sub - End Class - - - Friend Class VisualBasicStreamingFindReferencesService - Inherits AbstractFindReferencesService - - - Protected Sub New( referencedSymbolsPresenters As IEnumerable(Of IDefinitionsAndReferencesPresenter), - navigableItemsPresenters As IEnumerable(Of INavigableItemsPresenter), - externalReferencesProviders As IEnumerable(Of IFindReferencesResultProvider)) - MyBase.New(referencedSymbolsPresenters, navigableItemsPresenters, externalReferencesProviders) + navigableItemsPresenters As IEnumerable(Of INavigableItemsPresenter)) + MyBase.New(referencedSymbolsPresenters, navigableItemsPresenters) End Sub End Class End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb new file mode 100644 index 0000000000000000000000000000000000000000..217963bf5dcd2badc1c6b4574d5b766303631dd7 --- /dev/null +++ b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesService.vb @@ -0,0 +1,12 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.Editor.FindUsages +Imports Microsoft.CodeAnalysis.Host.Mef + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.FindUsages + + Friend Class VisualBasicFindUsagesService + Inherits AbstractFindUsagesService + End Class +End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/VisualBasic/GoToDefinition/VisualBasicGoToDefinitionService.vb b/src/EditorFeatures/VisualBasic/GoToDefinition/VisualBasicGoToDefinitionService.vb index 54462222f1c2e32ae182944ddc03f40b63836570..3807bca6b8b658b8b3dcc6b3e267f1a6bf67d53e 100644 --- a/src/EditorFeatures/VisualBasic/GoToDefinition/VisualBasicGoToDefinitionService.vb +++ b/src/EditorFeatures/VisualBasic/GoToDefinition/VisualBasicGoToDefinitionService.vb @@ -1,8 +1,8 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Composition +Imports Microsoft.CodeAnalysis.Editor.GoToDefinition Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.VisualBasic.Utilities @@ -12,12 +12,13 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.GoToDefinition Inherits AbstractGoToDefinitionService - Public Sub New( presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter)), externalDefinitionProviders As IEnumerable(Of Lazy(Of INavigableDefinitionProvider))) - MyBase.New(presenters, externalDefinitionProviders) + Public Sub New( presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter)), + streamingPresenters As IEnumerable(Of Lazy(Of IStreamingFindUsagesPresenter))) + MyBase.New(presenters, streamingPresenters) End Sub Protected Overrides Function FindRelatedExplicitlyDeclaredSymbol(symbol As ISymbol, compilation As Compilation) As ISymbol Return symbol.FindRelatedExplicitlyDeclaredSymbol(compilation) End Function End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/VisualBasic/GoToImplementation/VisualBasicGoToImplementationService.vb b/src/EditorFeatures/VisualBasic/GoToImplementation/VisualBasicGoToImplementationService.vb index 23b4964fc1f6a323c5491459e4c5863138c81f9d..122cecd148fb4b19d23de96721de2703af8038f8 100644 --- a/src/EditorFeatures/VisualBasic/GoToImplementation/VisualBasicGoToImplementationService.vb +++ b/src/EditorFeatures/VisualBasic/GoToImplementation/VisualBasicGoToImplementationService.vb @@ -1,8 +1,8 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Composition +Imports Microsoft.CodeAnalysis.Editor.GoToImplementation Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Implementation.GoToImplementation Imports Microsoft.CodeAnalysis.Host.Mef Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.GoToImplementation @@ -11,8 +11,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.GoToImplementation Inherits AbstractGoToImplementationService - Public Sub New( presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter)), externalDefinitionProviders As IEnumerable(Of Lazy(Of INavigableDefinitionProvider))) - MyBase.New(presenters, externalDefinitionProviders) + Public Sub New( presenters As IEnumerable(Of Lazy(Of INavigableItemsPresenter))) + MyBase.New(presenters) End Sub End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionServiceTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionServiceTests.vb index 832e5f97463511e110587cc1e6e75a8412bf9d74..f204c7e1f02ab38deecc71b2a118b1a078ea3a9d 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionServiceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionServiceTests.vb @@ -8,12 +8,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion Public Class CompletionServiceTests Public Sub AcquireCompletionService() - Dim hostServices = MefHostServices.Create( - MefHostServices.DefaultAssemblies.Concat({ - GetType(CompletionService).Assembly, - GetType(VisualBasicCompletionService).Assembly})) - - Dim workspace = New AdhocWorkspace(hostServices) + Dim workspace = New AdhocWorkspace() Dim document = workspace _ .AddProject("TestProject", LanguageNames.VisualBasic) _ diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AsyncKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AsyncKeywordRecommender.cs index 7b20dcdf7e9aec116327803b010517ad3a2cac6f..cd4179ae33f2af2b5807837c94175b1c21462aae 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AsyncKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AsyncKeywordRecommender.cs @@ -21,8 +21,13 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return true; } - return !context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword) - && InMemberDeclarationContext(position, context, cancellationToken); + if (context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword)) + { + return false; + } + + return InMemberDeclarationContext(position, context, cancellationToken) + || context.SyntaxTree.IsLocalFunctionDeclarationContext(position, cancellationToken); } private static bool InMemberDeclarationContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/VoidKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/VoidKeywordRecommender.cs index a78636245a1044ce3465fe4c24d41dc7a6c83bcc..079c0cd9ffcca43dc328b85303961d544eb0608d 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/VoidKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/VoidKeywordRecommender.cs @@ -45,7 +45,8 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context IsUnsafeParameterTypeContext(context) || IsUnsafeCastTypeContext(context) || IsUnsafeDefaultExpressionContext(context, cancellationToken) || - context.IsFixedVariableDeclarationContext; + context.IsFixedVariableDeclarationContext || + context.SyntaxTree.IsLocalFunctionDeclarationContext(position, cancellationToken); } private bool IsUnsafeDefaultExpressionContext(CSharpSyntaxContext context, CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.State.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.State.cs index 6b4bb285e62d65ee7bf5ccd4b5bb87519483fa68..d8803c8e221924034ffdbd483160c04c9dc7ea9b 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.State.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.State.cs @@ -97,12 +97,11 @@ private bool IsPredefinedTypeInDeclaration(SyntaxNode declarationStatement) private bool IsInferredPredefinedType(SyntaxNode declarationStatement, SemanticModel semanticModel, CancellationToken cancellationToken) { - TypeSyntax typeSyntax = GetTypeSyntaxFromDeclaration(declarationStatement); + var typeSyntax = GetTypeSyntaxFromDeclaration(declarationStatement); - return typeSyntax != null - ? typeSyntax.IsTypeInferred(semanticModel) && - semanticModel.GetTypeInfo(typeSyntax).Type?.IsSpecialType() == true - : false; + return typeSyntax != null && + typeSyntax.IsTypeInferred(semanticModel) && + semanticModel.GetTypeInfo(typeSyntax).Type?.IsSpecialType() == true; } private TypeSyntax GetTypeSyntaxFromDeclaration(SyntaxNode declarationStatement) diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index bf9a7da9cc010e77e3074828f607af64fbc6aee6..f82bdda31da4253f657dfa0ea63d516b2c2dee16 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; @@ -381,13 +382,12 @@ private OperationStatus CheckActiveStatements(IEnumerable state // return survived var decls if (list.Count > 0) { - yield return - SyntaxFactory.LocalDeclarationStatement( - declarationStatement.Modifiers, - SyntaxFactory.VariableDeclaration( - declarationStatement.Declaration.Type, - SyntaxFactory.SeparatedList(list)), - declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); + yield return SyntaxFactory.LocalDeclarationStatement( + declarationStatement.Modifiers, + SyntaxFactory.VariableDeclaration( + declarationStatement.Declaration.Type, + SyntaxFactory.SeparatedList(list)), + declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList)); triviaList.Clear(); } @@ -603,11 +603,19 @@ protected override StatementSyntax CreateAssignmentExpressionStatement(SyntaxTok protected override StatementSyntax CreateDeclarationStatement( VariableInfo variable, - CancellationToken cancellationToken, - ExpressionSyntax initialValue = null) + ExpressionSyntax initialValue, + CancellationToken cancellationToken) { + // Convert to 'var' if appropriate. Note: because we're extracting out + // to a method, the initialValue will be a method-call. Types are not + // apperant with method calls (i.e. as opposed to a 'new XXX()' expression, + // where the type is apperant). var type = variable.GetVariableType(this.SemanticDocument); - var typeNode = type.GenerateTypeSyntax(); + var typeNode = initialValue == null + ? type.GenerateTypeSyntax() + : type.GenerateTypeSyntaxOrVar( + this.SemanticDocument.Document.Project.Solution.Options, + typeIsApperant: false); var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); diff --git a/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs b/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs index c729a17c3f3bb500bfd92592b81202c0cf90ccb8..774ada6019056cf416720f7aaaa1b5f4b67f3a4f 100644 --- a/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -47,22 +48,21 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) // Note: if using 'var' would cause a problem, we will use the actual type // of hte local. This is necessary in some cases (for example, when the // type of the out-var-decl affects overload resolution or generic instantiation). - var useVarWhenDeclaringLocals = options.GetOption(CSharpCodeStyleOptions.UseVarWhenDeclaringLocals); - var useImplicitTypeForIntrinsicTypes = options.GetOption(CSharpCodeStyleOptions.UseImplicitTypeForIntrinsicTypes).Value; foreach (var diagnostic in diagnostics) { cancellationToken.ThrowIfCancellationRequested(); - await AddEditsAsync(document, editor, diagnostic, - useVarWhenDeclaringLocals, useImplicitTypeForIntrinsicTypes, - cancellationToken).ConfigureAwait(false); + await AddEditsAsync( + document, editor, diagnostic, + options, cancellationToken).ConfigureAwait(false); } } private async Task AddEditsAsync( Document document, SyntaxEditor editor, Diagnostic diagnostic, - bool useVarWhenDeclaringLocals, bool useImplicitTypeForIntrinsicTypes, CancellationToken cancellationToken) + OptionSet options, CancellationToken cancellationToken) { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // Recover the nodes we care about. @@ -121,8 +121,15 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) } // get the type that we want to put in the out-var-decl based on the user's options. - // i.e. prefer 'out var' if that is what the user wants. - var newType = this.GetDeclarationType(declaration.Type, useVarWhenDeclaringLocals, useImplicitTypeForIntrinsicTypes); + // i.e. prefer 'out var' if that is what the user wants. Note: if we have: + // + // Method(out var x) + // + // Then the type is not-apperant, and we shoudl not use var if the user only wants + // it for apperant types + + var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator); + var newType = local.Type.GenerateTypeSyntaxOrVar(options, typeIsApperant: false); var declarationExpression = GetDeclarationExpression( sourceText, identifier, newType, singleDeclarator ? null : declarator); @@ -131,11 +138,9 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) var semanticsChanged = await SemanticsChangedAsync( document, declaration, invocationOrCreation, newType, identifier, declarationExpression, cancellationToken).ConfigureAwait(false); - if (semanticsChanged) + if (semanticsChanged && newType.IsVar) { // Switching to 'var' changed semantics. Just use the original type of the local. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator); // If the user originally wrote it something other than 'var', then use what they // wrote. Otherwise, synthesize the actual type of the local. diff --git a/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs b/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs index b4fddb5cfd78765a9173f5ca564f0e6682983914..6519a90b1696fc7b1aeee9406eff72de5cf67d7a 100644 --- a/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCollectionInitializer), Shared] internal class CSharpUseCollectionInitializerCodeFixProvider : AbstractUseCollectionInitializerCodeFixProvider< + SyntaxKind, ExpressionSyntax, StatementSyntax, ObjectCreationExpressionSyntax, diff --git a/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs index 8da1bbc875fa8df5be6f62a91b25cabfd509935b..17e2af307102979fefc40eb887770ffe26f33976 100644 --- a/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs index 3d0cb459a92c9600cd2ec6ed2a1ac5a2da777b26..5fc0c7acc2e1757a2d0c2282b55a94d9c4dbdea7 100644 --- a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs @@ -5,8 +5,6 @@ using System.Composition; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.UseObjectInitializer; namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer @@ -14,6 +12,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseObjectInitializer), Shared] internal class CSharpUseObjectInitializerCodeFixProvider : AbstractUseObjectInitializerCodeFixProvider< + SyntaxKind, ExpressionSyntax, StatementSyntax, ObjectCreationExpressionSyntax, @@ -23,7 +22,7 @@ internal class CSharpUseObjectInitializerCodeFixProvider : { protected override ObjectCreationExpressionSyntax GetNewObjectCreation( ObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches) + ImmutableArray> matches) { var openBrace = SyntaxFactory.Token(SyntaxKind.OpenBraceToken) .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); @@ -35,7 +34,7 @@ internal class CSharpUseObjectInitializerCodeFixProvider : } private SeparatedSyntaxList CreateExpressions( - ImmutableArray> matches) + ImmutableArray> matches) { var nodesAndTokens = new List(); for (int i = 0; i < matches.Length; i++) diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs index 0826cb3b849914813be99a64303ee7f61d1187df..b261e4dbd8199030c4f2f71cea5f28ab85232d21 100644 --- a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; diff --git a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs index 2d942b51447900f8e324d4a19e8d2016ce337aa2..0e09bbdb673d60653db2fad7598ddc7accea22e2 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -212,7 +213,7 @@ internal protected CompletionProvider GetProvider(CompletionItem item) var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var defaultItemSpan = this.GetDefaultCompletionListSpan(text, caretPosition); - options = options ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);; + options = options ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var providers = GetFilteredProviders(roles, trigger, options); var completionProviderToIndex = GetCompletionProviderToIndex(providers); @@ -220,38 +221,43 @@ internal protected CompletionProvider GetProvider(CompletionItem item) var triggeredProviders = ImmutableArray.Empty; switch (trigger.Kind) { - case CompletionTriggerKind.Insertion: - case CompletionTriggerKind.Deletion: - if (this.ShouldTriggerCompletion(text, caretPosition, trigger, roles, options)) - { - triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty(); - if (triggeredProviders.Length == 0) + case CompletionTriggerKind.Insertion: + case CompletionTriggerKind.Deletion: + if (this.ShouldTriggerCompletion(text, caretPosition, trigger, roles, options)) { - triggeredProviders = providers; + triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty(); + if (triggeredProviders.Length == 0) + { + triggeredProviders = providers; + } } - } - break; - default: - triggeredProviders = providers; - break; + break; + default: + triggeredProviders = providers; + break; } - // Now, ask all the triggered providers if they can provide a group. - var completionContexts = new List(); - foreach (var provider in triggeredProviders) + // Now, ask all the triggered providers, in parallel, to populate a completion context. + // Note: we keep any context with items *or* with a suggested item. + var triggeredCompletionContexts = await ComputeNonEmptyCompletionContextsAsync( + document, caretPosition, trigger, options, + defaultItemSpan, triggeredProviders, + cancellationToken).ConfigureAwait(false); + + // If we didn't even get any back with items, then there's nothing to do. + // i.e. if only got items back that had only suggestion items, then we don't + // want to show any completion. + if (!triggeredCompletionContexts.Any(cc => cc.Items.Count > 0)) { - var completionContext = await GetContextAsync( - provider, document, caretPosition, trigger, - options, defaultItemSpan, cancellationToken).ConfigureAwait(false); - if (completionContext != null) - { - completionContexts.Add(completionContext); - } + return null; } - // See if there was a group provided that was exclusive and had items in it. If so, then + // All the contexts should be non-empty or have a suggestion item. + Debug.Assert(triggeredCompletionContexts.All(HasAnyItems)); + + // See if there was a completion context provided that was exclusive. If so, then // that's all we'll return. - var firstExclusiveContext = completionContexts.FirstOrDefault(t => t.IsExclusive && t.Items.Any()); + var firstExclusiveContext = triggeredCompletionContexts.FirstOrDefault(t => t.IsExclusive); if (firstExclusiveContext != null) { @@ -261,41 +267,51 @@ internal protected CompletionProvider GetProvider(CompletionItem item) isExclusive: true); } - // If no exclusive providers provided anything, then go through the remaining - // triggered list and see if any provide items. - var nonExclusiveLists = completionContexts.Where(t => !t.IsExclusive).ToList(); + // Shouldn't be any exclusive completion contexts at this point. + Debug.Assert(triggeredCompletionContexts.All(cc => !cc.IsExclusive)); - // If we still don't have any items, then we're definitely done. - if (!nonExclusiveLists.Any(g => g.Items.Any())) - { - return null; - } + // Great! We had some items. Now we want to see if any of the other providers + // would like to augment the completion list. For example, we might trigger + // enum-completion on space. If enum completion results in any items, then + // we'll want to augment the list with all the regular symbol completion items. + var augmentingProviders = providers.Except(triggeredProviders).ToImmutableArray(); - // If we do have items, then ask all the other (non exclusive providers) if they - // want to augment the items. - var usedProviders = nonExclusiveLists.Select(g => g.Provider); - var nonUsedProviders = providers.Except(usedProviders); - var nonUsedNonExclusiveLists = new List(); - foreach (var provider in nonUsedProviders) - { - var completionList = await GetContextAsync(provider, document, caretPosition, trigger, options, defaultItemSpan, cancellationToken).ConfigureAwait(false); - if (completionList != null && !completionList.IsExclusive) - { - nonUsedNonExclusiveLists.Add(completionList); - } - } + var augmentingCompletionContexts = await ComputeNonEmptyCompletionContextsAsync( + document, caretPosition, trigger, options, defaultItemSpan, + augmentingProviders, cancellationToken).ConfigureAwait(false); - var allProvidersAndLists = nonExclusiveLists.Concat(nonUsedNonExclusiveLists).ToList(); - if (allProvidersAndLists.Count == 0) - { - return null; - } + var allContexts = triggeredCompletionContexts.Concat(augmentingCompletionContexts); + Debug.Assert(allContexts.Length > 0); // Providers are ordered, but we processed them in our own order. Ensure that the // groups are properly ordered based on the original providers. - allProvidersAndLists.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]); + allContexts = allContexts.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]); + + return MergeAndPruneCompletionLists(allContexts, defaultItemSpan, isExclusive: false); + } + + private static bool HasAnyItems(CompletionContext cc) + { + return cc.Items.Count > 0 || cc.SuggestionModeItem != null; + } + + private async Task> ComputeNonEmptyCompletionContextsAsync( + Document document, int caretPosition, CompletionTrigger trigger, + OptionSet options, TextSpan defaultItemSpan, + ImmutableArray providers, + CancellationToken cancellationToken) + { + var completionContextTasks = new List>(); + foreach (var provider in providers) + { + completionContextTasks.Add(GetContextAsync( + provider, document, caretPosition, trigger, + options, defaultItemSpan, cancellationToken)); + } - return MergeAndPruneCompletionLists(allProvidersAndLists, defaultItemSpan, isExclusive: false); + var completionContexts = await Task.WhenAll(completionContextTasks).ConfigureAwait(false); + var nonEmptyContexts = completionContexts.Where(HasAnyItems).ToImmutableArray(); + return nonEmptyContexts; } private CompletionList MergeAndPruneCompletionLists( @@ -520,4 +536,4 @@ int IEqualityComparer>.GetHashCode(ImmutableHashSet GetAnalysisDataAsync(Project project } } - if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, s_addOthers, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. @@ -182,7 +182,7 @@ public async Task GetProjectAnalysisDataAsync(Project var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(project.Id, lastResult.Version); - if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, s_addOthers, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. @@ -301,7 +301,7 @@ private async Task LoadInitialAnalysisDataAsync(Projec } } - if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, s_addOthers, builder, cancellationToken).ConfigureAwait(false)) { return new DiagnosticAnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true, fromBuild: false); } @@ -333,7 +333,7 @@ private async Task LoadInitialProjectAnalysisDataAsync var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); var builder = new Builder(project.Id, version); - if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, s_addOthers, builder, cancellationToken).ConfigureAwait(false)) { return new DiagnosticAnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true, fromBuild: false); } @@ -355,22 +355,28 @@ private async Task SerializeAsync(DiagnosticDataSerializer serializer, object do InMemoryStorage.Cache(_owner.Analyzer, ValueTuple.Create(key, stateKey), new CacheEntry(serializer.Version, diagnostics)); } + private static Action> s_addSyntaxLocals = (b, id, locals) => b.AddSyntaxLocals(id, locals); + private static Action> s_addSemanticLocals = (b, id, locals) => b.AddSemanticLocals(id, locals); + private static Action> s_addNonLocals = (b, id, locals) => b.AddNonLocals(id, locals); + private static Action> s_addOthers = (b, id, locals) => b.AddOthers(id, locals); + private async Task TryDeserializeDocumentAsync(DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) { var result = true; - result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, builder.AddSyntaxLocals, cancellationToken).ConfigureAwait(false); - result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, builder.AddSemanticLocals, cancellationToken).ConfigureAwait(false); - result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, builder.AddNonLocals, cancellationToken).ConfigureAwait(false); + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, s_addSyntaxLocals, builder, cancellationToken).ConfigureAwait(false); + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, s_addSemanticLocals, builder, cancellationToken).ConfigureAwait(false); + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, s_addNonLocals, builder, cancellationToken).ConfigureAwait(false); return result; } - private async Task TryDeserializeAsync( + private async Task TryDeserializeAsync( DiagnosticDataSerializer serializer, - object documentOrProject, T key, string stateKey, - Action> add, - CancellationToken cancellationToken) where T : class + object documentOrProject, TKey key, string stateKey, + Action> add, + TArg arg, + CancellationToken cancellationToken) where TKey : class { var diagnostics = await DeserializeAsync(serializer, documentOrProject, key, stateKey, cancellationToken).ConfigureAwait(false); if (diagnostics == null) @@ -378,7 +384,7 @@ private async Task TryDeserializeDocumentAsync(DiagnosticDataSerializer se return false; } - add(key, diagnostics.Value); + add(arg, key, diagnostics.Value); return true; } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index a8554079323e52c0ee11e390e984151221a83f4a..3b0e183ac79f72de0f49f1b617150e43b5d42a25 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; @@ -61,7 +62,7 @@ protected CodeGenerator(InsertionPoint insertionPoint, SelectionResult selection protected abstract Task GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(SyntaxAnnotation callsiteAnnotation, CancellationToken cancellationToken); protected abstract TExpression CreateCallSignature(); - protected abstract TStatement CreateDeclarationStatement(VariableInfo variable, CancellationToken cancellationToken, TExpression initialValue = null); + protected abstract TStatement CreateDeclarationStatement(VariableInfo variable, TExpression initialValue, CancellationToken cancellationToken); protected abstract TStatement CreateAssignmentExpressionStatement(SyntaxToken identifier, TExpression rvalue); protected abstract TStatement CreateReturnStatement(string identifierName = null); @@ -188,8 +189,10 @@ protected VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(Cancellati // there must be one decl behavior when there is "return value and initialize" variable Contract.ThrowIfFalse(this.AnalyzerResult.GetVariablesToSplitOrMoveOutToCallSite(cancellationToken).Single(v => v.ReturnBehavior == ReturnBehavior.Initialization) != null); - return statements.Concat( - CreateDeclarationStatement(variable, cancellationToken, CreateCallSignature()).WithAdditionalAnnotations(this.CallSiteAnnotation)); + var declarationStatement = CreateDeclarationStatement( + variable, CreateCallSignature(), cancellationToken).WithAdditionalAnnotations(this.CallSiteAnnotation); + + return statements.Concat(declarationStatement); } Contract.ThrowIfFalse(variable.ReturnBehavior == ReturnBehavior.Assignment); @@ -197,19 +200,22 @@ protected VariableInfo GetOutermostVariableToMoveIntoMethodDefinition(Cancellati CreateAssignmentExpressionStatement(CreateIdentifier(variable.Name), CreateCallSignature()).WithAdditionalAnnotations(this.CallSiteAnnotation)); } - protected IEnumerable CreateDeclarationStatements(IEnumerable variables, CancellationToken cancellationToken) + protected IEnumerable CreateDeclarationStatements( + IEnumerable variables, CancellationToken cancellationToken) { var list = new List(); foreach (var variable in variables) { - list.Add(CreateDeclarationStatement(variable, cancellationToken)); + list.Add(CreateDeclarationStatement( + variable, initialValue: null, cancellationToken: cancellationToken)); } return list; } - protected IEnumerable AddSplitOrMoveDeclarationOutStatementsToCallSite(IEnumerable statements, CancellationToken cancellationToken) + protected IEnumerable AddSplitOrMoveDeclarationOutStatementsToCallSite( + IEnumerable statements, CancellationToken cancellationToken) { var list = new List(); @@ -220,7 +226,8 @@ protected IEnumerable AddSplitOrMoveDeclarationOutStatementsToCallSi continue; } - list.Add(CreateDeclarationStatement(variable, cancellationToken)); + list.Add(CreateDeclarationStatement( + variable, initialValue: null, cancellationToken: cancellationToken)); } return list; diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 4a93ce908d0ef3220cb0e9d9eb40e03c179d6071..17dee38125cfeaeff935046b5bacc01984954349 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -140,9 +140,11 @@ + + @@ -306,13 +308,13 @@ - - - - - - - + + + + + + + diff --git a/src/Features/Core/Portable/FindReferences/DefinitionItem.DocumentLocationDefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.DocumentLocationDefinitionItem.cs similarity index 96% rename from src/Features/Core/Portable/FindReferences/DefinitionItem.DocumentLocationDefinitionItem.cs rename to src/Features/Core/Portable/FindUsages/DefinitionItem.DocumentLocationDefinitionItem.cs index 87e7c42f5eb1dbc5961f2890ba024172b9e76aea..876bd3ed5bfcb99ff29da2836fbe7d51e590c73e 100644 --- a/src/Features/Core/Portable/FindReferences/DefinitionItem.DocumentLocationDefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.DocumentLocationDefinitionItem.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Immutable; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { internal partial class DefinitionItem { diff --git a/src/Features/Core/Portable/FindReferences/DefinitionItem.NonNavigatingDefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.NonNavigatingDefinitionItem.cs similarity index 96% rename from src/Features/Core/Portable/FindReferences/DefinitionItem.NonNavigatingDefinitionItem.cs rename to src/Features/Core/Portable/FindUsages/DefinitionItem.NonNavigatingDefinitionItem.cs index b5966f1298e34c9f717b99b69399222092b0c63f..1dd644c86b5e239f986c9a9e4a894b16565aef75 100644 --- a/src/Features/Core/Portable/FindReferences/DefinitionItem.NonNavigatingDefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.NonNavigatingDefinitionItem.cs @@ -2,7 +2,7 @@ using System.Collections.Immutable; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { internal partial class DefinitionItem { diff --git a/src/Features/Core/Portable/FindReferences/DefinitionItem.SymbolDefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.SymbolDefinitionItem.cs similarity index 98% rename from src/Features/Core/Portable/FindReferences/DefinitionItem.SymbolDefinitionItem.cs rename to src/Features/Core/Portable/FindUsages/DefinitionItem.SymbolDefinitionItem.cs index e9dd74d3c90415b3c2108ee89406dc6b5c1c3a46..48911343ffff2b4fc6cb3c807ec4523674866029 100644 --- a/src/Features/Core/Portable/FindReferences/DefinitionItem.SymbolDefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.SymbolDefinitionItem.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.Navigation; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { internal partial class DefinitionItem { diff --git a/src/Features/Core/Portable/FindReferences/DefinitionItem.cs b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs similarity index 99% rename from src/Features/Core/Portable/FindReferences/DefinitionItem.cs rename to src/Features/Core/Portable/FindUsages/DefinitionItem.cs index 87b94a12c78f1220f15861be9e108fd093f60a9d..7b11774123a5f1dbd85be7a7379b3fe6544a72d4 100644 --- a/src/Features/Core/Portable/FindReferences/DefinitionItem.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItem.cs @@ -4,7 +4,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Completion; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { /// /// Information about a symbol's definition that can be displayed in an editor diff --git a/src/Features/Core/Portable/FindReferences/DefinitionLocation.DocumentDefinitionLocation.cs b/src/Features/Core/Portable/FindUsages/DefinitionLocation.DocumentDefinitionLocation.cs similarity index 100% rename from src/Features/Core/Portable/FindReferences/DefinitionLocation.DocumentDefinitionLocation.cs rename to src/Features/Core/Portable/FindUsages/DefinitionLocation.DocumentDefinitionLocation.cs diff --git a/src/Features/Core/Portable/FindReferences/DefinitionsAndReferences.cs b/src/Features/Core/Portable/FindUsages/DefinitionsAndReferences.cs similarity index 97% rename from src/Features/Core/Portable/FindReferences/DefinitionsAndReferences.cs rename to src/Features/Core/Portable/FindUsages/DefinitionsAndReferences.cs index f4ad08d3d5c8bbaff1bcaa19d3555d9f1cdacade..8dc54612490a49f33045f791e727976d094fd377 100644 --- a/src/Features/Core/Portable/FindReferences/DefinitionsAndReferences.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionsAndReferences.cs @@ -4,7 +4,7 @@ using System.Collections.Immutable; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { /// /// A collection of s and s diff --git a/src/Features/Core/Portable/FindReferences/IDefinitionsAndReferencesFactory.cs b/src/Features/Core/Portable/FindUsages/IDefinitionsAndReferencesFactory.cs similarity index 99% rename from src/Features/Core/Portable/FindReferences/IDefinitionsAndReferencesFactory.cs rename to src/Features/Core/Portable/FindUsages/IDefinitionsAndReferencesFactory.cs index 915e9c47ef38c7f9a5bdd64a70819cb523395d9e..80a23337a6acc086618c8b018a38d12cec8bdb60 100644 --- a/src/Features/Core/Portable/FindReferences/IDefinitionsAndReferencesFactory.cs +++ b/src/Features/Core/Portable/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -12,7 +12,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { internal interface IDefinitionsAndReferencesFactory : IWorkspaceService { diff --git a/src/Features/Core/Portable/FindReferences/SourceReferenceItem.cs b/src/Features/Core/Portable/FindUsages/SourceReferenceItem.cs similarity index 94% rename from src/Features/Core/Portable/FindReferences/SourceReferenceItem.cs rename to src/Features/Core/Portable/FindUsages/SourceReferenceItem.cs index d16b9f8a3379128a51018366ed2002c476da9513..a9ed3a84fc5763095bc3ec1c61d7b12e409ee44a 100644 --- a/src/Features/Core/Portable/FindReferences/SourceReferenceItem.cs +++ b/src/Features/Core/Portable/FindUsages/SourceReferenceItem.cs @@ -2,7 +2,7 @@ using System; -namespace Microsoft.CodeAnalysis.FindReferences +namespace Microsoft.CodeAnalysis.FindUsages { /// /// Information about a symbol's reference that can be used for diplay and diff --git a/src/Features/Core/Portable/IncrementalCaches/SyntaxTreeInfoIncrementalAnalyzerProvider.cs b/src/Features/Core/Portable/IncrementalCaches/SyntaxTreeInfoIncrementalAnalyzerProvider.cs index b88d84c26cadac99fb1558e43ff55e4831c1d257..293ea99f6f970293c501ab422859577a01bfcf2a 100644 --- a/src/Features/Core/Portable/IncrementalCaches/SyntaxTreeInfoIncrementalAnalyzerProvider.cs +++ b/src/Features/Core/Portable/IncrementalCaches/SyntaxTreeInfoIncrementalAnalyzerProvider.cs @@ -20,8 +20,8 @@ private class IncrementalAnalyzer : IncrementalAnalyzerBase { public override Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) { - return SyntaxTreeInfo.PrecalculateAsync(document, cancellationToken); + return SyntaxTreeIndex.PrecalculateAsync(document, cancellationToken); } } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 3b84603f7afe6e3905792332b1470bdfefee7608..8fb4c1feae1d50f503ce0baf04341910b0a9829e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -45,7 +45,7 @@ internal abstract partial class AbstractNavigateToSearchService } cancellationToken.ThrowIfCancellationRequested(); - var declarationInfo = await document.GetDeclarationInfoAsync(cancellationToken).ConfigureAwait(false); + var declarationInfo = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false); foreach (var declaredSymbolInfo in declarationInfo.DeclaredSymbolInfos) { diff --git a/src/Features/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs b/src/Features/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs index 6d87b91cb7e3112ed0e628f3b7b7bcd59c1042e3..1b4ecebf36ce774a8159a7300ea410ec72abc663 100644 --- a/src/Features/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs +++ b/src/Features/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs @@ -9,15 +9,20 @@ internal partial class FeatureAttribute public const string BraceHighlighting = nameof(BraceHighlighting); public const string CallHierarchy = nameof(CallHierarchy); public const string Classification = nameof(Classification); + public const string CodeModel = nameof(CodeModel); public const string CompletionSet = nameof(CompletionSet); public const string DesignerAttribute = nameof(DesignerAttribute); - public const string ErrorSquiggles = nameof(ErrorSquiggles); + public const string DiagnosticService = nameof(DiagnosticService); public const string ErrorList = nameof(ErrorList); - public const string FindReferences = nameof(FindReferences); - public const string TodoCommentList = nameof(TodoCommentList); + public const string ErrorSquiggles = nameof(ErrorSquiggles); public const string EventHookup = nameof(EventHookup); + public const string FindReferences = nameof(FindReferences); + public const string GlobalOperation = nameof(GlobalOperation); + public const string GoToImplementation = nameof(GoToImplementation); public const string GraphProvider = nameof(GraphProvider); + public const string InfoBar = nameof(InfoBar); public const string KeywordHighlighting = nameof(KeywordHighlighting); + public const string LightBulb = nameof(LightBulb); public const string LineSeparators = nameof(LineSeparators); public const string NavigateTo = nameof(NavigateTo); public const string NavigationBar = nameof(NavigationBar); @@ -26,15 +31,11 @@ internal partial class FeatureAttribute public const string ReferenceHighlighting = nameof(ReferenceHighlighting); public const string Rename = nameof(Rename); public const string RenameTracking = nameof(RenameTracking); + public const string RuleSetEditor = nameof(RuleSetEditor); public const string SignatureHelp = nameof(SignatureHelp); public const string Snippets = nameof(Snippets); public const string SolutionCrawler = nameof(SolutionCrawler); + public const string TodoCommentList = nameof(TodoCommentList); public const string Workspace = nameof(Workspace); - public const string LightBulb = nameof(LightBulb); - public const string CodeModel = nameof(CodeModel); - public const string GlobalOperation = nameof(GlobalOperation); - public const string DiagnosticService = nameof(DiagnosticService); - public const string RuleSetEditor = nameof(RuleSetEditor); - public const string InfoBar = nameof(InfoBar); } } diff --git a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs index 5e991a182122416fb6032c05e3b2b2726ce68bf3..212d1c63d4f3f37cbfe186a77c1b6f3788c3e168 100644 --- a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -17,27 +18,30 @@ namespace Microsoft.CodeAnalysis.UseCollectionInitializer { internal abstract class AbstractUseCollectionInitializerCodeFixProvider< + TSyntaxKind, TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, - TVariableDeclarator> - : CodeFixProvider + TVariableDeclaratorSyntax> + : SyntaxEditorBasedCodeFixProvider + where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode where TStatementSyntax : SyntaxNode where TObjectCreationExpressionSyntax : TExpressionSyntax where TMemberAccessExpressionSyntax : TExpressionSyntax where TInvocationExpressionSyntax : TExpressionSyntax where TExpressionStatementSyntax : TStatementSyntax - where TVariableDeclarator : SyntaxNode + where TVariableDeclaratorSyntax : SyntaxNode { - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.UseCollectionInitializerDiagnosticId); + protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic) + => !diagnostic.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary); + public override Task RegisterCodeFixesAsync(CodeFixContext context) { context.RegisterCodeFix( @@ -46,32 +50,65 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) return SpecializedTasks.EmptyTask; } - private async Task FixAsync( - Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + protected override Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var objectCreation = (TObjectCreationExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); - + // Fix-All for this feature is somewhat complicated. As Collection-Initializers + // could be arbitrarily nested, we have to make sure that any edits we make + // to one Collection-Initializer are seen by any higher ones. In order to do this + // we actually process each object-creation-node, one at a time, rewriting + // the tree for each node. In order to do this effectively, we use the '.TrackNodes' + // feature to keep track of all the object creation nodes as we make edits to + // the tree. If we didn't do this, then we wouldn't be able to find the + // second object-creation-node after we make the edit for the first one. + var workspace = document.Project.Solution.Workspace; var syntaxFacts = document.GetLanguageService(); - var analyzer = new Analyzer( - syntaxFacts, objectCreation); - var matches = analyzer.Analyze(); + var originalRoot = editor.OriginalRoot; - var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); + var originalObjectCreationNodes = new Stack(); + foreach (var diagnostic in diagnostics) + { + var objectCreation = (TObjectCreationExpressionSyntax)originalRoot.FindNode( + diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); + originalObjectCreationNodes.Push(objectCreation); + } - var statement = objectCreation.FirstAncestorOrSelf(); - var newStatement = statement.ReplaceNode( - objectCreation, - GetNewObjectCreation(objectCreation, matches)).WithAdditionalAnnotations(Formatter.Annotation); + // We're going to be continually editing this tree. Track all the nodes we + // care about so we can find them across each edit. + var currentRoot = originalRoot.TrackNodes(originalObjectCreationNodes); - editor.ReplaceNode(statement, newStatement); - foreach (var match in matches) + while (originalObjectCreationNodes.Count > 0) { - editor.RemoveNode(match); + var originalObjectCreation = originalObjectCreationNodes.Pop(); + var objectCreation = currentRoot.GetCurrentNodes(originalObjectCreation).Single(); + + var analyzer = new ObjectCreationExpressionAnalyzer( + syntaxFacts, objectCreation); + var matches = analyzer.Analyze(); + if (matches == null || matches.Value.Length == 0) + { + continue; + } + + var statement = objectCreation.FirstAncestorOrSelf(); + var newStatement = statement.ReplaceNode( + objectCreation, + GetNewObjectCreation(objectCreation, matches.Value)).WithAdditionalAnnotations(Formatter.Annotation); + + var subEditor = new SyntaxEditor(currentRoot, workspace); + + subEditor.ReplaceNode(statement, newStatement); + foreach (var match in matches) + { + subEditor.RemoveNode(match); + } + + currentRoot = subEditor.GetChangedRoot(); } - var newRoot = editor.GetChangedRoot(); - return document.WithSyntaxRoot(newRoot); + editor.ReplaceNode(originalRoot, currentRoot); + return SpecializedTasks.EmptyTask; } protected abstract TObjectCreationExpressionSyntax GetNewObjectCreation( diff --git a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index 72639e90af2533c12edb57374be015c27ce37b3e..71a6aab1203675b5d5b8a95c90e1dbbef253c617 100644 --- a/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -1,16 +1,23 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.UseCollectionInitializer { - internal abstract class AbstractUseCollectionInitializerDiagnosticAnalyzer< + internal abstract partial class AbstractUseCollectionInitializerDiagnosticAnalyzer< TSyntaxKind, TExpressionSyntax, TStatementSyntax, @@ -18,7 +25,7 @@ internal abstract class AbstractUseCollectionInitializerDiagnosticAnalyzer< TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, - TVariableDeclarator> + TVariableDeclaratorSyntax> : AbstractCodeStyleDiagnosticAnalyzer where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode @@ -27,11 +34,11 @@ internal abstract class AbstractUseCollectionInitializerDiagnosticAnalyzer< where TMemberAccessExpressionSyntax : TExpressionSyntax where TInvocationExpressionSyntax : TExpressionSyntax where TExpressionStatementSyntax : TStatementSyntax - where TVariableDeclarator : SyntaxNode + where TVariableDeclaratorSyntax : SyntaxNode { public bool OpenFileOnly(Workspace workspace) => false; - protected AbstractUseCollectionInitializerDiagnosticAnalyzer() + protected AbstractUseCollectionInitializerDiagnosticAnalyzer() : base(IDEDiagnosticIds.UseCollectionInitializerDiagnosticId, new LocalizableResourceString(nameof(FeaturesResources.Simplify_collection_initialization), FeaturesResources.ResourceManager, typeof(FeaturesResources)), new LocalizableResourceString(nameof(FeaturesResources.Collection_initialization_can_be_simplified), FeaturesResources.ResourceManager, typeof(FeaturesResources))) @@ -82,13 +89,10 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien return; } - var syntaxFacts = GetSyntaxFactsService(); - var analyzer = new Analyzer< - TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, - TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, - TExpressionStatementSyntax, TVariableDeclarator>(syntaxFacts, objectCreationExpression); + var analyzer = new ObjectCreationExpressionAnalyzer( + GetSyntaxFactsService(), objectCreationExpression); var matches = analyzer.Analyze(); - if (matches.Length == 0) + if (matches == null || matches.Value.Length == 0) { return; } @@ -101,7 +105,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien objectCreationExpression.GetLocation(), additionalLocations: locations)); - FadeOutCode(context, optionSet, matches, locations); + FadeOutCode(context, optionSet, matches.Value, locations); } private void FadeOutCode( @@ -146,256 +150,4 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien protected abstract ISyntaxFactsService GetSyntaxFactsService(); } - - internal struct Analyzer< - TExpressionSyntax, - TStatementSyntax, - TObjectCreationExpressionSyntax, - TMemberAccessExpressionSyntax, - TInvocationExpressionSyntax, - TExpressionStatementSyntax, - TVariableDeclaratorSyntax> - where TExpressionSyntax : SyntaxNode - where TStatementSyntax : SyntaxNode - where TObjectCreationExpressionSyntax : TExpressionSyntax - where TMemberAccessExpressionSyntax : TExpressionSyntax - where TInvocationExpressionSyntax : TExpressionSyntax - where TExpressionStatementSyntax : TStatementSyntax - where TVariableDeclaratorSyntax : SyntaxNode - { - private readonly ISyntaxFactsService _syntaxFacts; - private readonly TObjectCreationExpressionSyntax _objectCreationExpression; - - private TStatementSyntax _containingStatement; - private SyntaxNodeOrToken _valuePattern; - - public Analyzer( - ISyntaxFactsService syntaxFacts, - TObjectCreationExpressionSyntax objectCreationExpression) : this() - { - _syntaxFacts = syntaxFacts; - _objectCreationExpression = objectCreationExpression; - } - - internal ImmutableArray Analyze() - { - if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null) - { - // Don't bother if this already has an initializer. - return ImmutableArray.Empty; - } - - _containingStatement = _objectCreationExpression.FirstAncestorOrSelf(); - if (_containingStatement == null) - { - return ImmutableArray.Empty; - } - - if (!TryInitializeVariableDeclarationCase() && - !TryInitializeAssignmentCase()) - { - return ImmutableArray.Empty; - } - - var matches = ArrayBuilder.GetInstance(); - AddMatches(matches); - return matches.ToImmutableAndFree(); ; - } - - private void AddMatches(ArrayBuilder matches) - { - var containingBlock = _containingStatement.Parent; - var foundStatement = false; - - var seenInvocation = false; - var seenIndexAssignment = false; - - foreach (var child in containingBlock.ChildNodesAndTokens()) - { - if (!foundStatement) - { - if (child == _containingStatement) - { - foundStatement = true; - } - - continue; - } - - if (child.IsToken) - { - return; - } - - var statement = child.AsNode() as TExpressionStatementSyntax; - if (statement == null) - { - return; - } - - SyntaxNode instance = null; - if (!seenIndexAssignment) - { - if (TryAnalyzeAddInvocation(statement, out instance)) - { - seenInvocation = true; - } - } - - if (!seenInvocation) - { - if (TryAnalyzeIndexAssignment(statement, out instance)) - { - seenIndexAssignment = true; - } - } - - if (instance == null) - { - return; - } - - if (!ValuePatternMatches((TExpressionSyntax)instance)) - { - return; - } - - matches.Add(statement); - } - } - - private bool TryAnalyzeIndexAssignment( - TExpressionStatementSyntax statement, - out SyntaxNode instance) - { - instance = null; - if (!_syntaxFacts.SupportsIndexingInitializer(statement.SyntaxTree.Options)) - { - return false; - } - - if (!_syntaxFacts.IsSimpleAssignmentStatement(statement)) - { - return false; - } - - SyntaxNode left, right; - _syntaxFacts.GetPartsOfAssignmentStatement(statement, out left, out right); - - if (!_syntaxFacts.IsElementAccessExpression(left)) - { - return false; - } - - instance = _syntaxFacts.GetExpressionOfElementAccessExpression(left); - return true; - } - - private bool TryAnalyzeAddInvocation( - TExpressionStatementSyntax statement, - out SyntaxNode instance) - { - instance = null; - var invocationExpression = _syntaxFacts.GetExpressionOfExpressionStatement(statement) as TInvocationExpressionSyntax; - if (invocationExpression == null) - { - return false; - } - - var arguments = _syntaxFacts.GetArgumentsOfInvocationExpression(invocationExpression); - if (arguments.Count < 1) - { - return false; - } - - foreach (var argument in arguments) - { - if (!_syntaxFacts.IsSimpleArgument(argument)) - { - return false; - } - } - - var memberAccess = _syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression) as TMemberAccessExpressionSyntax; - if (memberAccess == null) - { - return false; - } - - if (!_syntaxFacts.IsSimpleMemberAccessExpression(memberAccess)) - { - return false; - } - - SyntaxNode memberName; - _syntaxFacts.GetPartsOfMemberAccessExpression(memberAccess, out instance, out memberName); - - string name; - int arity; - _syntaxFacts.GetNameAndArityOfSimpleName(memberName, out name, out arity); - - if (arity != 0 || !name.Equals(nameof(IList.Add))) - { - return false; - } - - return true; - } - - private bool ValuePatternMatches(TExpressionSyntax expression) - { - if (_valuePattern.IsToken) - { - return _syntaxFacts.IsIdentifierName(expression) && - _syntaxFacts.AreEquivalent( - _valuePattern.AsToken(), - _syntaxFacts.GetIdentifierOfSimpleName(expression)); - } - else - { - return _syntaxFacts.AreEquivalent( - _valuePattern.AsNode(), expression); - } - } - - private bool TryInitializeAssignmentCase() - { - if (!_syntaxFacts.IsSimpleAssignmentStatement(_containingStatement)) - { - return false; - } - - SyntaxNode left, right; - _syntaxFacts.GetPartsOfAssignmentStatement(_containingStatement, out left, out right); - if (right != _objectCreationExpression) - { - return false; - } - - _valuePattern = left; - return true; - } - - private bool TryInitializeVariableDeclarationCase() - { - if (!_syntaxFacts.IsLocalDeclarationStatement(_containingStatement)) - { - return false; - } - - var containingDeclarator = _objectCreationExpression.FirstAncestorOrSelf(); - if (containingDeclarator == null) - { - return false; - } - - if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement)) - { - return false; - } - - _valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator); - return true; - } - } } \ No newline at end of file diff --git a/src/Features/Core/Portable/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs b/src/Features/Core/Portable/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..e7b272b202377aab798bbc8d7c7d2a38edf6d6ee --- /dev/null +++ b/src/Features/Core/Portable/UseCollectionInitializer/ObjectCreationExpressionAnalyzer.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.LanguageServices; + +namespace Microsoft.CodeAnalysis.UseCollectionInitializer +{ + internal struct ObjectCreationExpressionAnalyzer< + TExpressionSyntax, + TStatementSyntax, + TObjectCreationExpressionSyntax, + TMemberAccessExpressionSyntax, + TInvocationExpressionSyntax, + TExpressionStatementSyntax, + TVariableDeclaratorSyntax> + where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TInvocationExpressionSyntax : TExpressionSyntax + where TExpressionStatementSyntax : TStatementSyntax + where TVariableDeclaratorSyntax : SyntaxNode + { + private readonly ISyntaxFactsService _syntaxFacts; + private readonly TObjectCreationExpressionSyntax _objectCreationExpression; + + private TStatementSyntax _containingStatement; + private SyntaxNodeOrToken _valuePattern; + + public ObjectCreationExpressionAnalyzer( + ISyntaxFactsService syntaxFacts, + TObjectCreationExpressionSyntax objectCreationExpression) : this() + { + _syntaxFacts = syntaxFacts; + _objectCreationExpression = objectCreationExpression; + } + + internal ImmutableArray? Analyze() + { + if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null) + { + // Don't bother if this already has an initializer. + return null; + } + + _containingStatement = _objectCreationExpression.FirstAncestorOrSelf(); + if (_containingStatement == null) + { + return null; + } + + if (!TryInitializeVariableDeclarationCase() && + !TryInitializeAssignmentCase()) + { + return null; + } + + var matches = ArrayBuilder.GetInstance(); + AddMatches(matches); + return matches.ToImmutableAndFree(); + } + + private void AddMatches(ArrayBuilder matches) + { + var containingBlock = _containingStatement.Parent; + var foundStatement = false; + + var seenInvocation = false; + var seenIndexAssignment = false; + + foreach (var child in containingBlock.ChildNodesAndTokens()) + { + if (!foundStatement) + { + if (child == _containingStatement) + { + foundStatement = true; + } + + continue; + } + + if (child.IsToken) + { + return; + } + + var statement = child.AsNode() as TExpressionStatementSyntax; + if (statement == null) + { + return; + } + + SyntaxNode instance = null; + if (!seenIndexAssignment) + { + if (TryAnalyzeAddInvocation(statement, out instance)) + { + seenInvocation = true; + } + } + + if (!seenInvocation) + { + if (TryAnalyzeIndexAssignment(statement, out instance)) + { + seenIndexAssignment = true; + } + } + + if (instance == null) + { + return; + } + + if (!ValuePatternMatches((TExpressionSyntax)instance)) + { + return; + } + + matches.Add(statement); + } + } + + private bool TryAnalyzeIndexAssignment( + TExpressionStatementSyntax statement, + out SyntaxNode instance) + { + instance = null; + if (!_syntaxFacts.SupportsIndexingInitializer(statement.SyntaxTree.Options)) + { + return false; + } + + if (!_syntaxFacts.IsSimpleAssignmentStatement(statement)) + { + return false; + } + + _syntaxFacts.GetPartsOfAssignmentStatement(statement, + out var left, out var right); + + if (!_syntaxFacts.IsElementAccessExpression(left)) + { + return false; + } + + instance = _syntaxFacts.GetExpressionOfElementAccessExpression(left); + return true; + } + + private bool TryAnalyzeAddInvocation( + TExpressionStatementSyntax statement, + out SyntaxNode instance) + { + instance = null; + var invocationExpression = _syntaxFacts.GetExpressionOfExpressionStatement(statement) as TInvocationExpressionSyntax; + if (invocationExpression == null) + { + return false; + } + + var arguments = _syntaxFacts.GetArgumentsOfInvocationExpression(invocationExpression); + if (arguments.Count < 1) + { + return false; + } + + foreach (var argument in arguments) + { + if (!_syntaxFacts.IsSimpleArgument(argument)) + { + return false; + } + } + + var memberAccess = _syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression) as TMemberAccessExpressionSyntax; + if (memberAccess == null) + { + return false; + } + + if (!_syntaxFacts.IsSimpleMemberAccessExpression(memberAccess)) + { + return false; + } + + _syntaxFacts.GetPartsOfMemberAccessExpression(memberAccess, out instance, out var memberName); + _syntaxFacts.GetNameAndArityOfSimpleName(memberName, out var name, out var arity); + + if (arity != 0 || !name.Equals(nameof(IList.Add))) + { + return false; + } + + return true; + } + + private bool ValuePatternMatches(TExpressionSyntax expression) + { + if (_valuePattern.IsToken) + { + return _syntaxFacts.IsIdentifierName(expression) && + _syntaxFacts.AreEquivalent( + _valuePattern.AsToken(), + _syntaxFacts.GetIdentifierOfSimpleName(expression)); + } + else + { + return _syntaxFacts.AreEquivalent( + _valuePattern.AsNode(), expression); + } + } + + private bool TryInitializeAssignmentCase() + { + if (!_syntaxFacts.IsSimpleAssignmentStatement(_containingStatement)) + { + return false; + } + + _syntaxFacts.GetPartsOfAssignmentStatement(_containingStatement, + out var left, out var right); + if (right != _objectCreationExpression) + { + return false; + } + + _valuePattern = left; + return true; + } + + private bool TryInitializeVariableDeclarationCase() + { + if (!_syntaxFacts.IsLocalDeclarationStatement(_containingStatement)) + { + return false; + } + + var containingDeclarator = _objectCreationExpression.FirstAncestorOrSelf(); + if (containingDeclarator == null) + { + return false; + } + + if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement)) + { + return false; + } + + _valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator); + return true; + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs index dc0ae7953d3771304800d4dc6cd3c8f00641fe86..e8963f0aaf221b4d4b5ef2074e0c99eda3060e25 100644 --- a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -17,25 +18,28 @@ namespace Microsoft.CodeAnalysis.UseObjectInitializer { internal abstract class AbstractUseObjectInitializerCodeFixProvider< + TSyntaxKind, TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, - TVariableDeclarator> - : CodeFixProvider + TVariableDeclaratorSyntax> + : SyntaxEditorBasedCodeFixProvider + where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode where TStatementSyntax : SyntaxNode where TObjectCreationExpressionSyntax : TExpressionSyntax where TMemberAccessExpressionSyntax : TExpressionSyntax where TAssignmentStatementSyntax : TStatementSyntax - where TVariableDeclarator : SyntaxNode + where TVariableDeclaratorSyntax : SyntaxNode { - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.UseObjectInitializerDiagnosticId); + protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic) + => !diagnostic.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary); + public override Task RegisterCodeFixesAsync(CodeFixContext context) { context.RegisterCodeFix( @@ -44,37 +48,71 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) return SpecializedTasks.EmptyTask; } - private async Task FixAsync( - Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + protected override Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var objectCreation = (TObjectCreationExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); - + // Fix-All for this feature is somewhat complicated. As Object-Initializers + // could be arbitrarily nested, we have to make sure that any edits we make + // to one Object-Initializer are seen by any higher ones. In order to do this + // we actually process each object-creation-node, one at a time, rewriting + // the tree for each node. In order to do this effectively, we use the '.TrackNodes' + // feature to keep track of all the object creation nodes as we make edits to + // the tree. If we didn't do this, then we wouldn't be able to find the + // second object-creation-node after we make the edit for the first one. + var workspace = document.Project.Solution.Workspace; var syntaxFacts = document.GetLanguageService(); - var analyzer = new Analyzer( - syntaxFacts, objectCreation); - var matches = analyzer.Analyze(); - var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); + var originalRoot = editor.OriginalRoot; + var originalObjectCreationNodes = new Stack(); + foreach (var diagnostic in diagnostics) + { + var objectCreation = (TObjectCreationExpressionSyntax)originalRoot.FindNode( + diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); + originalObjectCreationNodes.Push(objectCreation); + } - var statement = objectCreation.FirstAncestorOrSelf(); - var newStatement = statement.ReplaceNode( - objectCreation, - GetNewObjectCreation(objectCreation, matches)).WithAdditionalAnnotations(Formatter.Annotation); + // We're going to be continually editing this tree. Track all the nodes we + // care about so we can find them across each edit. + var currentRoot = originalRoot.TrackNodes(originalObjectCreationNodes); - editor.ReplaceNode(statement, newStatement); - foreach (var match in matches) + while (originalObjectCreationNodes.Count > 0) { - editor.RemoveNode(match.Statement); + var originalObjectCreation = originalObjectCreationNodes.Pop(); + var objectCreation = currentRoot.GetCurrentNodes(originalObjectCreation).Single(); + + var analyzer = new ObjectCreationExpressionAnalyzer( + syntaxFacts, objectCreation); + var matches = analyzer.Analyze(); + + if (matches == null || matches.Value.Length == 0) + { + continue; + } + + var statement = objectCreation.FirstAncestorOrSelf(); + var newStatement = statement.ReplaceNode( + objectCreation, + GetNewObjectCreation(objectCreation, matches.Value)).WithAdditionalAnnotations(Formatter.Annotation); + + var subEditor = new SyntaxEditor(currentRoot, workspace); + + subEditor.ReplaceNode(statement, newStatement); + foreach (var match in matches) + { + subEditor.RemoveNode(match.Statement); + } + + currentRoot = subEditor.GetChangedRoot(); } - var newRoot = editor.GetChangedRoot(); - return document.WithSyntaxRoot(newRoot); + editor.ReplaceNode(editor.OriginalRoot, currentRoot); + return SpecializedTasks.EmptyTask; } protected abstract TObjectCreationExpressionSyntax GetNewObjectCreation( TObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches); + ImmutableArray> matches); private class MyCodeAction : CodeAction.DocumentChangeAction { diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index 0206522973561e241806e8a4165b5f75141c18b4..c79daccaf4ca97d112ca11642792a1458af9d548 100644 --- a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -1,25 +1,22 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Options; -using System; -using System.Linq; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.UseObjectInitializer { - internal abstract class AbstractUseObjectInitializerDiagnosticAnalyzer< + internal abstract partial class AbstractUseObjectInitializerDiagnosticAnalyzer< TSyntaxKind, TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, - TVariableDeclarator> + TVariableDeclaratorSyntax> : AbstractCodeStyleDiagnosticAnalyzer, IBuiltInAnalyzer where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode @@ -27,13 +24,13 @@ internal abstract class AbstractUseObjectInitializerDiagnosticAnalyzer< where TObjectCreationExpressionSyntax : TExpressionSyntax where TMemberAccessExpressionSyntax : TExpressionSyntax where TAssignmentStatementSyntax : TStatementSyntax - where TVariableDeclarator : SyntaxNode + where TVariableDeclaratorSyntax : SyntaxNode { protected abstract bool FadeOutOperatorToken { get; } public bool OpenFileOnly(Workspace workspace) => false; - protected AbstractUseObjectInitializerDiagnosticAnalyzer() + protected AbstractUseObjectInitializerDiagnosticAnalyzer() : base(IDEDiagnosticIds.UseObjectInitializerDiagnosticId, new LocalizableResourceString(nameof(FeaturesResources.Simplify_object_initialization), FeaturesResources.ResourceManager, typeof(FeaturesResources)), new LocalizableResourceString(nameof(FeaturesResources.Object_initialization_can_be_simplified), FeaturesResources.ResourceManager, typeof(FeaturesResources))) @@ -66,11 +63,11 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) } var syntaxFacts = GetSyntaxFactsService(); - var analyzer = new Analyzer( - syntaxFacts, - objectCreationExpression); - var matches = analyzer.Analyze(); - if (matches.Length == 0) + var analyzer = new ObjectCreationExpressionAnalyzer( + syntaxFacts, objectCreationExpression); + var result = analyzer.Analyze(); + + if (result == null || result.Value.Length == 0) { return; } @@ -83,13 +80,13 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) objectCreationExpression.GetLocation(), additionalLocations: locations)); - FadeOutCode(context, optionSet, matches, locations); + FadeOutCode(context, optionSet, result.Value, locations); } private void FadeOutCode( SyntaxNodeAnalysisContext context, OptionSet optionSet, - ImmutableArray> matches, + ImmutableArray> matches, ImmutableArray locations) { var syntaxTree = context.Node.SyntaxTree; @@ -134,280 +131,4 @@ public DiagnosticAnalyzerCategory GetAnalyzerCategory() return DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; } } - - internal struct Match - where TExpressionSyntax : SyntaxNode - where TMemberAccessExpressionSyntax : TExpressionSyntax - where TAssignmentStatementSyntax : SyntaxNode - { - public readonly TAssignmentStatementSyntax Statement; - public readonly TMemberAccessExpressionSyntax MemberAccessExpression; - public readonly TExpressionSyntax Initializer; - - public Match( - TAssignmentStatementSyntax statement, - TMemberAccessExpressionSyntax memberAccessExpression, - TExpressionSyntax initializer) - { - Statement = statement; - MemberAccessExpression = memberAccessExpression; - Initializer = initializer; - } - } - - internal struct Analyzer< - TExpressionSyntax, - TStatementSyntax, - TObjectCreationExpressionSyntax, - TMemberAccessExpressionSyntax, - TAssignmentStatementSyntax, - TVariableDeclaratorSyntax> - where TExpressionSyntax : SyntaxNode - where TStatementSyntax : SyntaxNode - where TObjectCreationExpressionSyntax : TExpressionSyntax - where TMemberAccessExpressionSyntax : TExpressionSyntax - where TAssignmentStatementSyntax : TStatementSyntax - where TVariableDeclaratorSyntax : SyntaxNode - { - private readonly ISyntaxFactsService _syntaxFacts; - private readonly TObjectCreationExpressionSyntax _objectCreationExpression; - - private TStatementSyntax _containingStatement; - private SyntaxNodeOrToken _valuePattern; - - public Analyzer( - ISyntaxFactsService syntaxFacts, - TObjectCreationExpressionSyntax objectCreationExpression) : this() - { - _syntaxFacts = syntaxFacts; - _objectCreationExpression = objectCreationExpression; - } - - internal ImmutableArray> Analyze() - { - if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null) - { - // Don't bother if this already has an initializer. - return ImmutableArray>.Empty; - } - - _containingStatement = _objectCreationExpression.FirstAncestorOrSelf(); - if (_containingStatement == null) - { - return ImmutableArray>.Empty; - } - - if (!TryInitializeVariableDeclarationCase() && - !TryInitializeAssignmentCase()) - { - return ImmutableArray>.Empty; - } - - var containingBlock = _containingStatement.Parent; - var foundStatement = false; - - var matches = ArrayBuilder>.GetInstance(); - HashSet seenNames = null; - - foreach (var child in containingBlock.ChildNodesAndTokens()) - { - if (!foundStatement) - { - if (child == _containingStatement) - { - foundStatement = true; - } - - continue; - } - - if (child.IsToken) - { - break; - } - - var statement = child.AsNode() as TAssignmentStatementSyntax; - if (statement == null) - { - break; - } - - if (!_syntaxFacts.IsSimpleAssignmentStatement(statement)) - { - break; - } - - _syntaxFacts.GetPartsOfAssignmentStatement( - statement, out var left, out var right); - - var rightExpression = right as TExpressionSyntax; - var leftMemberAccess = left as TMemberAccessExpressionSyntax; - - if (!_syntaxFacts.IsSimpleMemberAccessExpression(leftMemberAccess)) - { - break; - } - - var expression = (TExpressionSyntax)_syntaxFacts.GetExpressionOfMemberAccessExpression(leftMemberAccess); - if (!ValuePatternMatches(expression)) - { - break; - } - - // Don't offer this fix if the value we're initializing is itself referenced - // on the RHS of the assignment. For example: - // - // var v = new X(); - // v.Prop = v.Prop.WithSomething(); - // - // Or with - // - // v = new X(); - // v.Prop = v.Prop.WithSomething(); - // - // In the first case, 'v' is being initialized, and so will not be available - // in the object initializer we create. - // - // In the second case we'd change semantics because we'd access the old value - // before the new value got written. - if (ExpressionContainsValuePattern(rightExpression)) - { - break; - } - - // If we have code like "x.v = .Length.ToString()" - // then we don't want to change this into: - // - // var x = new Whatever() With { .v = .Length.ToString() } - // - // The problem here is that .Length will change it's meaning to now refer to the - // object that we're creating in our object-creation expression. - if (ImplicitMemberAccessWouldBeAffected(rightExpression)) - { - break; - } - - // found a match! - seenNames = seenNames ?? new HashSet(); - - // If we see an assignment to the same property/field, we can't convert it - // to an initializer. - var name = _syntaxFacts.GetNameOfMemberAccessExpression(leftMemberAccess); - var identifier = _syntaxFacts.GetIdentifierOfSimpleName(name); - if (!seenNames.Add(identifier.ValueText)) - { - break; - } - - matches.Add(new Match( - statement, leftMemberAccess, rightExpression)); - } - - return matches.ToImmutableAndFree(); - } - - private bool ImplicitMemberAccessWouldBeAffected(SyntaxNode node) - { - if (node != null) - { - foreach (var child in node.ChildNodesAndTokens()) - { - if (child.IsNode) - { - if (ImplicitMemberAccessWouldBeAffected(child.AsNode())) - { - return true; - } - } - } - - if (_syntaxFacts.IsSimpleMemberAccessExpression(node)) - { - var expression = _syntaxFacts.GetExpressionOfMemberAccessExpression( - node, allowImplicitTarget: true); - - // If we're implicitly referencing some target that is before the - // object creation expression, then our semantics will change. - if (expression != null && expression.SpanStart < _objectCreationExpression.SpanStart) - { - return true; - } - } - } - - return false; - } - - private bool ExpressionContainsValuePattern(TExpressionSyntax expression) - { - foreach (var subExpression in expression.DescendantNodesAndSelf().OfType()) - { - if (!_syntaxFacts.IsNameOfMemberAccessExpression(subExpression)) - { - if (ValuePatternMatches(subExpression)) - { - return true; - } - } - } - - return false; - } - - private bool ValuePatternMatches(TExpressionSyntax expression) - { - if (_valuePattern.IsToken) - { - return _syntaxFacts.IsIdentifierName(expression) && - _syntaxFacts.AreEquivalent( - _valuePattern.AsToken(), - _syntaxFacts.GetIdentifierOfSimpleName(expression)); - } - else - { - return _syntaxFacts.AreEquivalent( - _valuePattern.AsNode(), expression); - } - } - - private bool TryInitializeAssignmentCase() - { - if (!_syntaxFacts.IsSimpleAssignmentStatement(_containingStatement)) - { - return false; - } - - _syntaxFacts.GetPartsOfAssignmentStatement( - _containingStatement, out var left, out var right); - if (right != _objectCreationExpression) - { - return false; - } - - _valuePattern = left; - return true; - } - - private bool TryInitializeVariableDeclarationCase() - { - if (!_syntaxFacts.IsLocalDeclarationStatement(_containingStatement)) - { - return false; - } - - var containingDeclarator = _objectCreationExpression.FirstAncestorOrSelf(); - if (containingDeclarator == null) - { - return false; - } - - if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement)) - { - return false; - } - - _valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator); - return true; - } - } } \ No newline at end of file diff --git a/src/Features/Core/Portable/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs b/src/Features/Core/Portable/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..406790df6b9ca61e9b22d2d2c40c388b4ee5aaf2 --- /dev/null +++ b/src/Features/Core/Portable/UseObjectInitializer/ObjectCreationExpressionAnalyzer.cs @@ -0,0 +1,291 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.LanguageServices; + +namespace Microsoft.CodeAnalysis.UseObjectInitializer +{ + internal struct ObjectCreationExpressionAnalyzer< + TExpressionSyntax, + TStatementSyntax, + TObjectCreationExpressionSyntax, + TMemberAccessExpressionSyntax, + TAssignmentStatementSyntax, + TVariableDeclaratorSyntax> + where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TAssignmentStatementSyntax : TStatementSyntax + where TVariableDeclaratorSyntax : SyntaxNode + { + private readonly ISyntaxFactsService _syntaxFacts; + private readonly TObjectCreationExpressionSyntax _objectCreationExpression; + + private TStatementSyntax _containingStatement; + private SyntaxNodeOrToken _valuePattern; + + public ObjectCreationExpressionAnalyzer( + ISyntaxFactsService syntaxFacts, + TObjectCreationExpressionSyntax objectCreationExpression) : this() + { + _syntaxFacts = syntaxFacts; + _objectCreationExpression = objectCreationExpression; + } + + internal ImmutableArray>? Analyze() + { + if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null) + { + // Don't bother if this already has an initializer. + return null; + } + + _containingStatement = _objectCreationExpression.FirstAncestorOrSelf(); + if (_containingStatement == null) + { + return null; + } + + if (!TryInitializeVariableDeclarationCase() && + !TryInitializeAssignmentCase()) + { + return null; + } + + var containingBlock = _containingStatement.Parent; + var foundStatement = false; + + var matches = ArrayBuilder>.GetInstance(); + HashSet seenNames = null; + + foreach (var child in containingBlock.ChildNodesAndTokens()) + { + if (!foundStatement) + { + if (child == _containingStatement) + { + foundStatement = true; + continue; + } + + continue; + } + + if (child.IsToken) + { + break; + } + + var statement = child.AsNode() as TAssignmentStatementSyntax; + if (statement == null) + { + break; + } + + if (!_syntaxFacts.IsSimpleAssignmentStatement(statement)) + { + break; + } + + _syntaxFacts.GetPartsOfAssignmentStatement( + statement, out var left, out var right); + + var rightExpression = right as TExpressionSyntax; + var leftMemberAccess = left as TMemberAccessExpressionSyntax; + + if (!_syntaxFacts.IsSimpleMemberAccessExpression(leftMemberAccess)) + { + break; + } + + var expression = (TExpressionSyntax)_syntaxFacts.GetExpressionOfMemberAccessExpression(leftMemberAccess); + if (!ValuePatternMatches(expression)) + { + break; + } + + // Don't offer this fix if the value we're initializing is itself referenced + // on the RHS of the assignment. For example: + // + // var v = new X(); + // v.Prop = v.Prop.WithSomething(); + // + // Or with + // + // v = new X(); + // v.Prop = v.Prop.WithSomething(); + // + // In the first case, 'v' is being initialized, and so will not be available + // in the object initializer we create. + // + // In the second case we'd change semantics because we'd access the old value + // before the new value got written. + if (ExpressionContainsValuePattern(rightExpression)) + { + break; + } + + // If we have code like "x.v = .Length.ToString()" + // then we don't want to change this into: + // + // var x = new Whatever() With { .v = .Length.ToString() } + // + // The problem here is that .Length will change it's meaning to now refer to the + // object that we're creating in our object-creation expression. + if (ImplicitMemberAccessWouldBeAffected(rightExpression)) + { + break; + } + + // found a match! + seenNames = seenNames ?? new HashSet(); + + // If we see an assignment to the same property/field, we can't convert it + // to an initializer. + var name = _syntaxFacts.GetNameOfMemberAccessExpression(leftMemberAccess); + var identifier = _syntaxFacts.GetIdentifierOfSimpleName(name); + if (!seenNames.Add(identifier.ValueText)) + { + break; + } + + matches.Add(new Match( + statement, leftMemberAccess, rightExpression)); + } + + return matches.ToImmutableAndFree(); + } + + private bool ImplicitMemberAccessWouldBeAffected(SyntaxNode node) + { + if (node != null) + { + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + { + if (ImplicitMemberAccessWouldBeAffected(child.AsNode())) + { + return true; + } + } + } + + if (_syntaxFacts.IsSimpleMemberAccessExpression(node)) + { + var expression = _syntaxFacts.GetExpressionOfMemberAccessExpression( + node, allowImplicitTarget: true); + + // If we're implicitly referencing some target that is before the + // object creation expression, then our semantics will change. + if (expression != null && expression.SpanStart < _objectCreationExpression.SpanStart) + { + return true; + } + } + } + + return false; + } + + private bool ExpressionContainsValuePattern(TExpressionSyntax expression) + { + foreach (var subExpression in expression.DescendantNodesAndSelf().OfType()) + { + if (!_syntaxFacts.IsNameOfMemberAccessExpression(subExpression)) + { + if (ValuePatternMatches(subExpression)) + { + return true; + } + } + } + + return false; + } + + private bool ValuePatternMatches(TExpressionSyntax expression) + { + if (_valuePattern.IsToken) + { + return _syntaxFacts.IsIdentifierName(expression) && + _syntaxFacts.AreEquivalent( + _valuePattern.AsToken(), + _syntaxFacts.GetIdentifierOfSimpleName(expression)); + } + else + { + return _syntaxFacts.AreEquivalent( + _valuePattern.AsNode(), expression); + } + } + + private bool TryInitializeAssignmentCase() + { + if (!_syntaxFacts.IsSimpleAssignmentStatement(_containingStatement)) + { + return false; + } + + _syntaxFacts.GetPartsOfAssignmentStatement( + _containingStatement, out var left, out var right); + if (right != _objectCreationExpression) + { + return false; + } + + _valuePattern = left; + return true; + } + + private bool TryInitializeVariableDeclarationCase() + { + if (!_syntaxFacts.IsLocalDeclarationStatement(_containingStatement)) + { + return false; + } + + var containingDeclarator = _objectCreationExpression.FirstAncestorOrSelf(); + if (containingDeclarator == null) + { + return false; + } + + if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement)) + { + return false; + } + + _valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator); + return true; + } + } + + internal struct Match< + TExpressionSyntax, + TStatementSyntax, + TMemberAccessExpressionSyntax, + TAssignmentStatementSyntax> + where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TAssignmentStatementSyntax : TStatementSyntax + { + public readonly TAssignmentStatementSyntax Statement; + public readonly TMemberAccessExpressionSyntax MemberAccessExpression; + public readonly TExpressionSyntax Initializer; + + public Match( + TAssignmentStatementSyntax statement, + TMemberAccessExpressionSyntax memberAccessExpression, + TExpressionSyntax initializer) + { + Statement = statement; + MemberAccessExpression = memberAccessExpression; + Initializer = initializer; + } + } +} \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index 1b263144dcb0f7beede3eb5de5ed7a039498ac17..32f45dc5d4fc40de5dddbf6c85f1b65f9d3a1844 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.Options Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Partial Friend Class VisualBasicMethodExtractor @@ -349,9 +350,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Return identifier.CreateAssignmentExpressionStatementWithValue(rvalue) End Function - Protected Overrides Function CreateDeclarationStatement(variable As VariableInfo, - cancellationToken As CancellationToken, - Optional givenInitializer As ExpressionSyntax = Nothing) As StatementSyntax + Protected Overrides Function CreateDeclarationStatement( + variable As VariableInfo, + givenInitializer As ExpressionSyntax, + cancellationToken As CancellationToken) As StatementSyntax Dim shouldInitializeWithNothing = (variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.MoveOut OrElse variable.GetDeclarationBehavior(cancellationToken) = DeclarationBehavior.SplitOut) AndAlso (variable.ParameterModifier = ParameterBehavior.Out) diff --git a/src/Features/VisualBasic/Portable/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb index a231cd9c3862abbda67b668134b3d8b19990623b..de18e1c6b8e9f51c5f689a8fd87e5798def3d58a 100644 --- a/src/Features/VisualBasic/Portable/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb @@ -10,6 +10,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer Friend Class VisualBasicUseCollectionInitializerCodeFixProvider Inherits AbstractUseCollectionInitializerCodeFixProvider(Of + SyntaxKind, ExpressionSyntax, StatementSyntax, ObjectCreationExpressionSyntax, diff --git a/src/Features/VisualBasic/Portable/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb index 8972658051ae56ff87b1a5bf60cc01f7a0274c19..bdbf4fbee3f78537e913f5f7259617024d0a75f5 100644 --- a/src/Features/VisualBasic/Portable/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb @@ -10,6 +10,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseObjectInitializer Friend Class VisualBasicUseObjectInitializerCodeFixProvider Inherits AbstractUseObjectInitializerCodeFixProvider(Of + SyntaxKind, ExpressionSyntax, StatementSyntax, ObjectCreationExpressionSyntax, @@ -19,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseObjectInitializer Protected Overrides Function GetNewObjectCreation( objectCreation As ObjectCreationExpressionSyntax, - matches As ImmutableArray(Of Match(Of AssignmentStatementSyntax, MemberAccessExpressionSyntax, ExpressionSyntax))) As ObjectCreationExpressionSyntax + matches As ImmutableArray(Of Match(Of ExpressionSyntax, StatementSyntax, MemberAccessExpressionSyntax, AssignmentStatementSyntax))) As ObjectCreationExpressionSyntax Dim initializer = SyntaxFactory.ObjectMemberInitializer( CreateFieldInitializers(matches)) @@ -30,7 +31,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseObjectInitializer End Function Private Function CreateFieldInitializers( - matches As ImmutableArray(Of Match(Of AssignmentStatementSyntax, MemberAccessExpressionSyntax, ExpressionSyntax))) As SeparatedSyntaxList(Of FieldInitializerSyntax) + matches As ImmutableArray(Of Match(Of ExpressionSyntax, StatementSyntax, MemberAccessExpressionSyntax, AssignmentStatementSyntax))) As SeparatedSyntaxList(Of FieldInitializerSyntax) Dim nodesAndTokens = New List(Of SyntaxNodeOrToken) For i = 0 To matches.Length - 1 diff --git a/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec index eac10fcd6f28fe76904c72ee36cdb9310b10f5d5..596e7f707d23dfca4e4d42e8dee2bf48f620ca40 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec @@ -43,6 +43,7 @@ + diff --git a/src/NuGet/Microsoft.Net.Compilers.nuspec b/src/NuGet/Microsoft.Net.Compilers.nuspec index 8a125e3043035cc7a0b7f81c3cacbed12eaf8233..2c5342e3563941815c6e5493d02d3e0588c5dabd 100644 --- a/src/NuGet/Microsoft.Net.Compilers.nuspec +++ b/src/NuGet/Microsoft.Net.Compilers.nuspec @@ -70,9 +70,9 @@ Supported Platforms: + - diff --git a/src/Tools/Github/GitMergeBot/GitHubRepository.cs b/src/Tools/Github/GitMergeBot/GitHubRepository.cs new file mode 100644 index 0000000000000000000000000000000000000000..c0fd18fa86054db23cb652d69b0d57ad2f67845a --- /dev/null +++ b/src/Tools/Github/GitMergeBot/GitHubRepository.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octokit; + +namespace GitMergeBot +{ + internal sealed class GitHubRepository : RepositoryBase + { + private GitHubClient _client; + + public GitHubRepository(string path, string repoName, string userName, string authToken) + : base(path, repoName, userName, authToken) + { + _client = new GitHubClient(new ProductHeaderValue(userName)) + { + Credentials = new Credentials(authToken) + }; + } + + public override async Task ShouldMakePullRequestAsync(string title) + { + return (await GetExistingMergePrsAsync(title)).Count == 0; + } + + public override async Task CreatePullRequestAsync(string title, string destinationOwner, string pullRequestBranch, string prBranchSourceRemote, string sourceBranch, string destinationBranch) + { + var remoteName = $"{UserName}-{RepositoryName}"; + var prMessage = $@" +This is an automatically generated pull request from {sourceBranch} into {destinationBranch}. + +@dotnet/roslyn-infrastructure: + +``` bash +git remote add {remoteName} ""https://github.com/{UserName}/{RepositoryName}.git"" +git fetch {remoteName} +git fetch {prBranchSourceRemote} +git checkout {pullRequestBranch} +git reset --hard {prBranchSourceRemote}/{destinationBranch} +git merge {prBranchSourceRemote}/{sourceBranch} +# Fix merge conflicts +git commit +git push {remoteName} {pullRequestBranch} --force +``` + +Once all conflicts are resolved and all the tests pass, you are free to merge the pull request. +".Trim(); + + try + { + var pullRequest = await _client.PullRequest.Create( + owner: destinationOwner, + name: RepositoryName, + newPullRequest: new NewPullRequest( + title: title, + head: $"{UserName}:{pullRequestBranch}", + baseRef: destinationBranch) + { + Body = prMessage + } + ); + + // The reason for this delay is twofold: + // + // * Github has a bug in which it can "create" a pull request without that pull request + // being able to be commented on for a short period of time. + // * The Jenkins "comment watcher" has a bug whereby any comment posted shortly after + // pull-request creation is ignored. + // + // Thus, this delay sidesteps both of those bugs by asking for a VSI test 30 seconds after + // the creation of the PR. Ugly, yes; but the only *real* way to sidestep this would be to + // 1) Fix github, 2) Fix jenkins, and while those might be lofty goals, they are not in the + // scope of this PR. + await Task.Delay(TimeSpan.FromSeconds(30.0)); + + await _client.Issue.Comment.Create(destinationOwner, RepositoryName, pullRequest.Number, "@dotnet-bot test vsi please"); + } + catch (Exception ex) when (DidPullRequestFailDueToNoChanges(ex)) + { + Console.WriteLine("There were no commits between the specified branchs. Pull request not created."); + } + } + + /// The existing open merge PRs. + private async Task> GetExistingMergePrsAsync(string newBranchPrefix) + { + var allPullRequests = await _client.PullRequest.GetAllForRepository(UserName, RepositoryName); + var openPrs = allPullRequests.Where(pr => pr.Head.Ref.StartsWith(newBranchPrefix) && pr.User.Login == UserName).ToList(); + + Console.WriteLine($"Found {openPrs.Count} existing open merge pull requests."); + foreach (var pr in openPrs) + { + Console.WriteLine($" Open PR: {pr.HtmlUrl}"); + } + + return openPrs; + } + + /// + /// The Octokit API fails on pull request creation if the PR would have been empty, but there is no way + /// to know that ahead of time. + /// + /// The fall-back is to check for a very specific "failure". + /// + private static bool DidPullRequestFailDueToNoChanges(Exception ex) + { + if (!(ex is ApiValidationException apiException)) + { + return false; + } + + if (apiException.ApiError.Errors.Count != 1) + { + return false; + } + + var error = apiException.ApiError.Errors.Single(); + return error.Message.StartsWith("No commits between"); + } + } +} diff --git a/src/Tools/Github/GitMergeBot/GitMergeBot.csproj b/src/Tools/Github/GitMergeBot/GitMergeBot.csproj index c28180f3eb82101eeacc64abc57ddd1c0af4e18a..9120455213f44d93687084349e83de9c2cf0280b 100644 --- a/src/Tools/Github/GitMergeBot/GitMergeBot.csproj +++ b/src/Tools/Github/GitMergeBot/GitMergeBot.csproj @@ -1,5 +1,6 @@  + Debug @@ -11,6 +12,8 @@ GitMergeBot v4.6 true + + AnyCPU @@ -32,12 +35,24 @@ 4 + + ..\..\..\..\..\packages\LibGit2Sharp.0.22.0\lib\net40\LibGit2Sharp.dll + True + + + ..\..\..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + ..\..\..\..\..\packages\Octokit.0.17.0\lib\net45\Octokit.dll True + + ..\..\..\..\..\packages\System.ValueTuple.4.3.0-preview1-24530-04\lib\netstandard1.0\System.ValueTuple.dll + True + @@ -46,16 +61,26 @@ + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + - + \ No newline at end of file diff --git a/src/Tools/Github/GitMergeBot/Options.cs b/src/Tools/Github/GitMergeBot/Options.cs index fce4100e20768280078e2cd3be108e819cbb5f12..e380fa577759675d28f8cd54b677949ed71f671a 100644 --- a/src/Tools/Github/GitMergeBot/Options.cs +++ b/src/Tools/Github/GitMergeBot/Options.cs @@ -6,16 +6,88 @@ namespace GitMergeBot { internal sealed class Options { - public string AuthToken { get; set; } - public string RepoName { get; set; } - public string SourceBranch { get; set; } - public string DestinationBranch { get; set; } - public string SourceUser { get; set; } - public string DestinationUser { get; set; } public bool Force { get; set; } public bool Debug { get; set; } - public bool ShowHelp { get; set; } + public string RepositoryPath { get; set; } + public RepositoryType SourceRepoType { get; set; } + public string SourceRepoName { get; set; } + public string SourceProject { get; set; } + public string SourceUserId { get; set; } + public string SourceUserName { get; set; } + public string SourcePassword { get; set; } + public string SourceRemoteName { get; set; } + public string SourceBranchName { get; set; } - public bool AreValid => new[] { AuthToken, RepoName, SourceBranch, DestinationBranch, SourceUser, DestinationUser }.All(s => s != null); + public bool PushBranchToDestination { get; set; } + + private string _prBranchSourceRemote; + private RepositoryType? _destinationRepoType; + private string _destinationRepoOwner; + private string _destinationRepoName; + private string _destinationUserName; + private string _destinationPassword; + private string _destinationRemoteName; + private string _destinationBranchName; + + public string DestinationProject { get; set; } + public string DestinationUserId { get; set; } + + public string PullRequestBranchSourceRemote + { + get { return _prBranchSourceRemote ?? SourceRemoteName; } + set { _prBranchSourceRemote = value; } + } + + public RepositoryType DestinationRepoType + { + get { return _destinationRepoType ?? SourceRepoType; } + set { _destinationRepoType = value; } + } + + public string DestinationRepoOwner + { + get { return _destinationRepoOwner ?? DestinationUserName; } + set { _destinationRepoOwner = value; } + } + + public string DestinationRepoName + { + get { return _destinationRepoName ?? SourceRepoName; } + set { _destinationRepoName = value; } + } + + public string DestinationUserName + { + get { return _destinationUserName ?? SourceUserName; } + set { _destinationUserName = value; } + } + + public string DestinationPassword + { + get { return _destinationPassword ?? SourcePassword; } + set { _destinationPassword = value; } + } + + public string DestinationRemoteName + { + get { return _destinationRemoteName ?? SourceRemoteName; } + set { _destinationRemoteName = value; } + } + + public string DestinationBranchName + { + get { return _destinationBranchName ?? SourceBranchName; } + set { _destinationBranchName = value; } + } + + public bool IsValid => new[] + { + RepositoryPath, + SourceRepoName, + SourceUserName, + SourcePassword, + SourceRemoteName, + SourceBranchName + }.All(s => s != null); } } diff --git a/src/Tools/Github/GitMergeBot/Program.cs b/src/Tools/Github/GitMergeBot/Program.cs index 62f79a56dd97052a89b3f9b96b228da4d671d78f..62972265fb9910567f972d6802fab70ffad16166 100644 --- a/src/Tools/Github/GitMergeBot/Program.cs +++ b/src/Tools/Github/GitMergeBot/Program.cs @@ -1,215 +1,152 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; using System.Reflection; using System.Threading.Tasks; +using LibGit2Sharp; using Mono.Options; -using Octokit; + +using static System.Console; namespace GitMergeBot { - class Program + internal sealed class Program { static int Main(string[] args) { var exeName = Assembly.GetExecutingAssembly().GetName().Name; + var showHelp = false; var options = new Options(); - - // default to using an environment variable, but allow an explicitly provided value to override - options.AuthToken = Environment.GetEnvironmentVariable("AUTH_CODE"); var parameters = new OptionSet() { $"Usage: {exeName} [options]", "Create a pull request from the specified user and branch to another specified user and branch.", "", "Options:", - { "a|auth=", "The GitHub authentication token.", value => options.AuthToken = value }, - { "r|repo=", "The name of the remote repository.", value => options.RepoName = value }, - { "s|source=", "The source branch of the merge operation.", value => options.SourceBranch = value }, - { "d|dest=", "The destination branch of the merge operation.", value => options.DestinationBranch = value }, - { "su|sourceuser=", "The user hosting the source branch of the merge operation.", value => options.SourceUser = value }, - { "du|destuser=", "The user hosting the destination branch of the merge operation.", value => options.DestinationUser = value }, + { "repopath=", "The local path to the repository.", value => options.RepositoryPath = value }, + { "sourcetype=", "The source repository type. Valid values are 'GitHub' and 'VisualStudioOnline'.", value => options.SourceRepoType = (RepositoryType)Enum.Parse(typeof(RepositoryType), value) }, + { "sourcereponame=", "The name of the source repository.", value => options.SourceRepoName = value }, + { "sourceproject=", "The name of the source project. Only needed for VisualStudioOnline repos.", value => options.SourceProject = value }, + { "sourceuserid=", "The source user ID. Only needed for VisualStudioOnline repos.", value => options.SourceUserId = value }, + { "sourceuser=", "The source user name.", value => options.SourceUserName = value }, + { "sourcepassword=", "The source password.", value => options.SourcePassword = value }, + { "sourceremote=", "The source remote name.", value => options.SourceRemoteName = value }, + { "sourcebranch=", "The source branch name.", value => options.SourceBranchName = value }, + { "pushtodestination=", "If true the PR branch will be pushed to the destination repository; if false the PR branch will be pushed to the source.", value => options.PushBranchToDestination = value != null }, + { "prbranchsourceremote=", "The name of the remote the PR should initiate from. Defaults to `sourceremote` parameter.", value => options.PullRequestBranchSourceRemote = value }, + { "destinationtype=", "The destination repository type. Valid values are 'GitHub' and 'VisualStudioOnline'. Defaults to `sourcetype` parameter.", value => options.DestinationRepoType = (RepositoryType)Enum.Parse(typeof(RepositoryType), value) }, + { "destinationrepoowner=", "", value => options.DestinationRepoOwner = value }, + { "destinationreponame=", "The name of the destination repository. Defaults to `sourcereponame` parameter.", value => options.DestinationRepoName = value }, + { "destinationproject=", "The name of the destination project. Only needed for VisualStudioOnline repos.", value => options.DestinationProject = value }, + { "destinationuserid=", "The destination user ID. Only needed for VisualStudioOnline repos.", value => options.DestinationUserId = value }, + { "destinationuser=", "The destination user name. Defaults to `sourceuser` parameter.", value => options.DestinationUserName = value }, + { "destinationpassword=", "The destination password. Defaults to `sourcepassword` parameter.", value => options.DestinationPassword = value }, + { "destinationremote=", "The destination remote name. Defaults to `sourceremote` parameter.", value => options.DestinationRemoteName = value }, + { "destinationbranch=", "The destination branch name. Defaults to `sourcebranch` parameter.", value => options.DestinationBranchName = value }, { "f|force", "Force the creation of the PR even if an open PR already exists.", value => options.Force = value != null }, { "debug", "Print debugging information about the merge but don't actually create the pull request.", value => options.Debug = value != null }, - { "h|help", "Show this message and exit.", value => options.ShowHelp = value != null } + { "h|?|help", "Show this message and exit.", value => showHelp = value != null } }; try { parameters.Parse(args); + if (showHelp || !options.IsValid) + { + parameters.WriteOptionDescriptions(Out); + return options.IsValid ? 0 : 1; + } + + var sourceRepository = RepositoryBase.Create(options.SourceRepoType, options.RepositoryPath, options.SourceRepoName, options.SourceProject, options.SourceUserId, options.SourceUserName, options.SourcePassword, options.SourceRemoteName); + var destRepository = RepositoryBase.Create(options.DestinationRepoType, options.RepositoryPath, options.DestinationRepoName, options.DestinationProject, options.DestinationUserId, options.DestinationUserName, options.DestinationPassword, options.DestinationRemoteName); + new Program(sourceRepository, destRepository, options).RunAsync().GetAwaiter().GetResult(); + return 0; } catch (OptionException e) { - Console.WriteLine($"{exeName}: {e.Message}"); - Console.WriteLine($"Try `{exeName} --help` for more information."); + WriteLine($"{exeName}: {e.Message}"); + WriteLine($"Try `{exeName} --help` for more information."); return 1; } - - if (options.ShowHelp || !options.AreValid) - { - parameters.WriteOptionDescriptions(Console.Out); - return options.AreValid ? 0 : 1; - } - else - { - var github = new GitHubClient(new ProductHeaderValue(options.SourceUser)); - github.Credentials = new Credentials(options.AuthToken); - new Program(options).MakePullRequest().GetAwaiter().GetResult(); - return 0; - } } + private RepositoryBase _sourceRepo; + private RepositoryBase _destRepo; private Options _options; - private GitHubClient _client; - private Program(Options options) + private Program(RepositoryBase sourceRepository, RepositoryBase destinationRepository, Options options) { + _sourceRepo = sourceRepository; + _destRepo = destinationRepository; _options = options; } - public async Task MakePullRequest() + public async Task RunAsync() { - _client = new GitHubClient(new ProductHeaderValue(_options.SourceUser)); - _client.Credentials = new Credentials(_options.AuthToken); - var remoteIntoBranch = await GetShaFromBranch(_options.DestinationUser, _options.RepoName, _options.SourceBranch); - var newBranchPrefix = $"merge-{_options.SourceBranch}-into-{_options.DestinationBranch}"; - if (!_options.Force && await DoesOpenPrAlreadyExist(newBranchPrefix)) - { - Console.WriteLine("Existing merge PRs exist; aboring creation. Use `--force` option to override."); - return; - } - - var newBranchName = await MakePrBranch(_options.SourceUser, _options.RepoName, remoteIntoBranch, newBranchPrefix); - var pullRequest = await SubmitPullRequest(newBranchName); - - // The reason for this delay is twofold: - // - // * Github has a bug in which it can "create" a pull request without that pull request - // being able to be commented on for a short period of time. - // * The Jenkins "comment watcher" has a bug whereby any comment posted shortly after - // pull-request creation is ignored. - // - // Thus, this delay sidesteps both of those bugs by asking for a VSI test 30 seconds after - // the creation of the PR. Ugly, yes; but the only *real* way to sidestep this would be to - // 1) Fix github, 2) Fix jenkins, and while those might be lofty goals, they are not in the - // scope of this PR. - await Task.Delay(TimeSpan.FromSeconds(30.0)); - - await _client.Issue.Comment.Create(_options.DestinationUser, _options.RepoName, pullRequest.Number, "@dotnet-bot test vsi please"); - } - - /// The SHA at the tip of `branchName` in the repository `user/repo` - private async Task GetShaFromBranch(string user, string repo, string branchName) - { - var refs = await _client.GitDatabase.Reference.Get(user, repo, $"heads/{branchName}"); - return refs.Object.Sha; - } + await _sourceRepo.Initialize(); + await _destRepo.Initialize(); - /// True if an existing auto merge PR is still open. - private async Task DoesOpenPrAlreadyExist(string newBranchPrefix) - { - return (await GetExistingMergePrs(newBranchPrefix)).Count > 0; - } + // fetch latest sources + WriteLine("Fetching."); + _sourceRepo.Fetch(_options.PullRequestBranchSourceRemote); - /// The existing open merge PRs. - private async Task> GetExistingMergePrs(string newBranchPrefix) - { - var allPullRequests = await _client.PullRequest.GetAllForRepository(_options.DestinationUser, _options.RepoName); - var openPrs = allPullRequests.Where(pr => pr.Head.Ref.StartsWith(newBranchPrefix) && pr.User.Login == _options.SourceUser).ToList(); + var (prRepo, prRemoteName, prUserName, prPassword) = _options.PushBranchToDestination + ? (_destRepo, _options.DestinationRemoteName, _options.DestinationUserName, _options.DestinationPassword) + : (_sourceRepo, _options.SourceRemoteName, _options.SourceUserName, _options.SourcePassword); - Console.WriteLine($"Found {openPrs.Count} existing open merge pull requests."); - foreach (var pr in openPrs) + // branch from the source + var title = $"Merge {_options.SourceBranchName} into {_options.DestinationBranchName}"; + if (_options.Force || await prRepo.ShouldMakePullRequestAsync(title)) { - Console.WriteLine($" Open PR: {pr.HtmlUrl}"); + } + else + { + WriteLine("Existing merge PRs exist; aboring creation."); + return; } - return openPrs; - } - - /// - /// Creates a PR branch on the bot account with the branch head at `sha`. - /// - /// The name of the branch that was created - private async Task MakePrBranch(string user, string repo, string sha, string branchNamePrefix) - { - var branchName = branchNamePrefix + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss"); + var prBranchName = $"merge-{_options.SourceBranchName}-into-{_options.DestinationBranchName}-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}"; + var prSourceBranch = $"{_options.PullRequestBranchSourceRemote}/{_options.SourceBranchName}"; + WriteLine($"Creating branch '{prBranchName}' from '{prSourceBranch}'."); + if (_options.Debug) + { + WriteLine("Debug: Skiping branch creation."); + } + else + { + var prBranch = prRepo.Repository.CreateBranch(prBranchName, prSourceBranch); + } + // push the branch + var remote = prRepo.Repository.Network.Remotes[prRemoteName]; + WriteLine($"Pushing branch '{prBranchName}'."); if (_options.Debug) { - WriteDebugLine($"Create remote branch '{user}/{repo}/{branchName}' at {sha}"); + WriteLine("Debug: Skipping branch push."); } else { - var resp = await _client.Connection.Post( - uri: new Uri($"https://api.github.com/repos/{user}/{repo}/git/refs"), - body: $"{{\"ref\": \"refs/heads/{branchName}\", \"sha\": \"{sha}\"}}", - accepts: "*/*", - contentType: "application/json"); - var statusCode = resp.HttpResponse.StatusCode; - if (statusCode != HttpStatusCode.Created) + var pushOptions = new PushOptions() { - throw new Exception($"Failed creating a new branch {branchName} on {user}/{repo} with code {statusCode}"); - } + CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials() + { + Username = prUserName, + Password = prPassword + } + }; + prRepo.Repository.Network.Push(remote, $"+refs/heads/{prBranchName}:refs/heads/{prBranchName}", pushOptions); } - return branchName; - } - - /// - /// Creates a pull request - /// - private async Task SubmitPullRequest(string newBranchName) - { - var remoteName = $"{_options.SourceUser}-{_options.RepoName}"; - var prTitle = $"Merge {_options.SourceBranch} into {_options.DestinationBranch}"; - var prMessage = $@" -This is an automatically generated pull request from {_options.SourceBranch} into {_options.DestinationBranch}. - -@dotnet/roslyn-infrastructure: - -``` bash -git remote add {remoteName} ""https://github.com/{_options.SourceUser}/{_options.RepoName}.git"" -git fetch {remoteName} -git fetch upstream -git checkout {newBranchName} -git reset --hard upstream/{_options.DestinationBranch} -git merge upstream/{_options.SourceBranch} -# Fix merge conflicts -git commit -git push {remoteName} {newBranchName} --force -``` - -Once the merge can be made and all the tests pass, you are free to merge the pull request. -".Trim(); - + // create PR + WriteLine("Creating PR."); if (_options.Debug) { - WriteDebugLine($"Create PR with title: {prTitle}."); - WriteDebugLine($"Create PR with body:\r\n{prMessage}"); - return null; + WriteLine("Debug: Skipping PR creation."); } else { - return await _client.PullRequest.Create( - owner: _options.DestinationUser, - name: _options.RepoName, - newPullRequest: new NewPullRequest( - title: prTitle, - head: $"{_options.SourceUser}:{newBranchName}", - baseRef: _options.DestinationBranch) - { - Body = prMessage - } - ); + await _destRepo.CreatePullRequestAsync(title, _options.DestinationRepoOwner, prBranchName, _options.PullRequestBranchSourceRemote, _options.SourceBranchName, _options.DestinationBranchName); } } - - private void WriteDebugLine(string line) - { - Console.WriteLine("Debug: " + line); - } } } diff --git a/src/Tools/Github/GitMergeBot/README.md b/src/Tools/Github/GitMergeBot/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c02ec2be119aaa15a9ec6c4fbfed0bf1e438bb39 --- /dev/null +++ b/src/Tools/Github/GitMergeBot/README.md @@ -0,0 +1,13 @@ +# Examples + +## Merge from `https://github.com/dotnet/roslyn`:`dev15-rc2` to `master` where the credentials used belong to a fictional user `merge-bot`. + +``` cmd +GitMergeBot.exe --repopath=C:\path\to\roslyn\repo --sourcetype=GitHub --sourcereponame=roslyn --sourceuser=merge-bot --sourcepassword=super-secret-key --sourceremote=origin --sourcebranch=dev15-rc2 --pushtodestination- --prbranchsourceremote=upstream --destinationrepoowner=dotnet --destinationremote=upstream --destinationbranch=master +``` + +## Merge from `https://github.com/Microsoft/visualfsharp`:`master` to `https://`:`microbuild` on a VSO instance where the credentials belong to a fictional user `merge-bot`. + +``` cmd +GitMergeBot.exe --repopath=C:\path\to\fsharp\repo --sourcetype=GitHub --sourcereponame=visualfsharp --sourceuser=merge-bot --sourcepassword=super-secret-key --sourceremote=origin --sourcebranch=master --pushtodestination+ --prbranchsourceremote=upstream --destinationtype=VisualStudioOnline --destinationreponame=FSharp --destinationproject=DevDiv --destinationuserid=[GUID] --destinationuser= --destinationpassword=super-secret-key --destinationremote=vso --destinationbranch=microbuild +``` diff --git a/src/Tools/Github/GitMergeBot/RepositoryBase.cs b/src/Tools/Github/GitMergeBot/RepositoryBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..fdde7a7a52fe84b58b5cb4f8fb43fe01731dafd8 --- /dev/null +++ b/src/Tools/Github/GitMergeBot/RepositoryBase.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using LibGit2Sharp; + +namespace GitMergeBot +{ + internal abstract class RepositoryBase + { + public Repository Repository { get; } + public string RepositoryName { get; } + public string UserName { get; } + public string Password { get; } + + protected RepositoryBase(string path, string repoName, string userName, string password) + { + Repository = new Repository(path); + RepositoryName = repoName; + UserName = userName; + Password = password; + } + + public virtual Task Initialize() + { + return Task.CompletedTask; + } + + public abstract Task ShouldMakePullRequestAsync(string title); + public abstract Task CreatePullRequestAsync(string title, string destinationOwner, string pullRequestBranch, string prBranchSourceRemote, string sourceBranch, string destinationBranch); + + protected void WriteDebugLine(string line) + { + Console.WriteLine("Debug: " + line); + } + + public void Fetch(string remoteName) + { + var fetchOptions = new FetchOptions() + { + CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials() + { + Username = UserName, + Password = Password + } + }; + Repository.Fetch(remoteName, fetchOptions); + } + + public static RepositoryBase Create(RepositoryType type, string path, string repoName, string project, string userId, string userName, string password, string remoteName) + { + switch (type) + { + case RepositoryType.GitHub: + return new GitHubRepository(path, repoName, userName, password); + case RepositoryType.VisualStudioOnline: + return new VisualStudioOnlineRepository(path, repoName, project, userId, userName, password, remoteName); + default: + throw new InvalidOperationException("Unknown repository type."); + } + } + } +} diff --git a/src/Tools/Github/GitMergeBot/RepositoryType.cs b/src/Tools/Github/GitMergeBot/RepositoryType.cs new file mode 100644 index 0000000000000000000000000000000000000000..b0b5638df350c9c5c7842a92a2579e3d2a86a79d --- /dev/null +++ b/src/Tools/Github/GitMergeBot/RepositoryType.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace GitMergeBot +{ + internal enum RepositoryType + { + GitHub, + VisualStudioOnline + } +} diff --git a/src/Tools/Github/GitMergeBot/VisualStudioOnlineRepository.cs b/src/Tools/Github/GitMergeBot/VisualStudioOnlineRepository.cs new file mode 100644 index 0000000000000000000000000000000000000000..97cbccb1d0f1dbd9e36d9e28b41ff837d307a79f --- /dev/null +++ b/src/Tools/Github/GitMergeBot/VisualStudioOnlineRepository.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace GitMergeBot +{ + internal sealed class VisualStudioOnlineRepository : RepositoryBase + { + private const string ApiVersion = "3.0"; + + private HttpClient _client; + private string _project; + private string _remoteName; + private string _repositoryId; + private string _userId; + + public VisualStudioOnlineRepository(string path, string repoName, string project, string userId, string userName, string password, string remoteName) + : base(path, repoName, userName, password) + { + _project = project; + _remoteName = remoteName; + _userId = userId; + + var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{userName}:{password}")); + var remote = Repository.Network.Remotes[remoteName]; + var remoteUri = new Uri(remote.Url); + _client = new HttpClient(); + _client.BaseAddress = new Uri($"{remoteUri.Scheme}://{remoteUri.Host}"); + _client.DefaultRequestHeaders.Accept.Clear(); + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); + } + + public override async Task Initialize() + { + // find the repository ID + // https://www.visualstudio.com/en-us/docs/integrate/api/git/repositories#get-a-list-of-repositories + var repositories = await GetJsonAsync($"DefaultCollection/{_project}/_apis/git/repositories?api-version={ApiVersion}"); + _repositoryId = (string)repositories["value"].Single(r => r?["name"].Type == JTokenType.String && (string)r["name"] == RepositoryName)["id"]; + } + + public override async Task ShouldMakePullRequestAsync(string title) + { + // https://www.visualstudio.com/en-us/docs/integrate/api/git/pull-requests/pull-requests#get-a-list-of-pull-requests-in-the-repository + var foundMatch = false; + var result = await GetJsonAsync($"DefaultCollection/_apis/git/repositories/{_repositoryId}/pullRequests?api-version={ApiVersion}&creatorId={_userId}"); + var pullRequests = (JArray)result["value"]; + foreach (JObject pr in pullRequests) + { + if (pr?["repository"]?["name"].Type == JTokenType.String && (string)pr["repository"]["name"] == RepositoryName) + { + var prTitle = (string)pr["title"]; + Console.WriteLine($" Open PR: {prTitle}"); + foundMatch |= prTitle == title; + } + } + + return !foundMatch; + } + + public override async Task CreatePullRequestAsync(string title, string destinationOwner, string pullRequestBranch, string prBranchSourceRemote, string sourceBranch, string destinationBranch) + { + // https://www.visualstudio.com/en-us/docs/integrate/api/git/pull-requests/pull-requests#create-a-pull-request + var prMessage = $@" +This is an automatically generated pull request from {sourceBranch} into {destinationBranch}. + +``` bash +git remote add {_remoteName} {Repository.Network.Remotes[_remoteName].Url} +git fetch --all +git checkout {pullRequestBranch} +git reset --hard {_remoteName}/{destinationBranch} +git merge {prBranchSourceRemote}/{sourceBranch} +# Fix merge conflicts +git commit +git push {pullRequestBranch} --force +``` + +Once all conflicts are resolved and all the tests pass, you are free to merge the pull request. +".Trim(); + var request = new JObject() + { + ["sourceRefName"] = $"refs/heads/{pullRequestBranch}", + ["targetRefName"] = $"refs/heads/{destinationBranch}", + ["title"] = title, + ["description"] = prMessage, + ["reviewers"] = new JArray() // no required reviewers, but necessary for the request + }; + var result = await GetJsonAsync( + $"DefaultCollection/_apis/git/repositories/{_repositoryId}/pullRequests?api-version={ApiVersion}", + body: request, + method: "POST"); + + var pullRequestId = (string)result["pullRequestId"]; + + // mark the PR to auto complete + // https://www.visualstudio.com/en-us/docs/integrate/api/git/pull-requests/pull-requests#auto-complete + var autoCompleteRequest = new JObject() + { + ["autoCompleteSetBy"] = new JObject() + { + ["id"] = _userId + }, + ["completionOptions"] = new JObject() + { + ["deleteSourceBranch"] = true, + ["mergeCommitMessage"] = $"Pull request #{pullRequestId} auto-completed after passing checks.", + ["squashMerge"] = false + } + }; + + result = await GetJsonAsync( + $"DefaultCollection/_apis/git/repositories/{_repositoryId}/pullRequests/{pullRequestId}?api-version={ApiVersion}", + body: autoCompleteRequest, + method: "PATCH"); + } + + private async Task GetJsonAsync(string requestUri, JObject body = null, string method = "GET") + { + HttpResponseMessage response; + if (body == null) + { + response = await _client.GetAsync(requestUri); + } + else + { + var requestMessage = new HttpRequestMessage(new HttpMethod(method), requestUri); + requestMessage.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(body.ToString())); + requestMessage.Content.Headers.Add("Content-Type", "application/json"); + response = await _client.SendAsync(requestMessage); + } + + var result = await response.Content.ReadAsStringAsync(); + return JObject.Parse(result); + } + } +} diff --git a/src/Tools/Github/GitMergeBot/packages.config b/src/Tools/Github/GitMergeBot/packages.config index f1f10057e91245d34a84ecc3a7408b179e9ab0d0..4a01748c371b14c1c3fb8e8e4f0c21fc5160fa34 100644 --- a/src/Tools/Github/GitMergeBot/packages.config +++ b/src/Tools/Github/GitMergeBot/packages.config @@ -1,5 +1,9 @@  + + + + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/PersistentStorageTests.cs index 4f361a97d41e09fba72116d0162a5a6dce345630..7c37fe279e02aaedca253865538d5d5faf19d652 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/PersistentStorageTests.cs @@ -283,47 +283,6 @@ private void DoSimultaneousReads(Func> read, string expectedValue) countdown.Wait(); } - [Fact] - public async Task PersistentService_IdentifierSet() - { - var solution = CreateOrOpenSolution(); - - var newId = DocumentId.CreateNewId(solution.ProjectIds[0]); - - string documentFile = Path.Combine(Path.GetDirectoryName(solution.FilePath), "IdentifierSet.cs"); - - File.WriteAllText(documentFile, @" -class A -{ - public int Test(int i, A a) - { - return a; - } -}"); - - var newSolution = solution.AddDocument(DocumentInfo.Create(newId, "IdentifierSet", loader: new FileTextLoader(documentFile, Encoding.UTF8), filePath: documentFile)); - - using (var storage = GetStorage(newSolution)) - { - var syntaxTreeStorage = storage as ISyntaxTreeInfoPersistentStorage; - Assert.NotNull(syntaxTreeStorage); - - var document = newSolution.GetDocument(newId); - var version = await document.GetSyntaxVersionAsync(); - var root = await document.GetSyntaxRootAsync(); - - Assert.True(syntaxTreeStorage.WriteIdentifierLocations(document, version, root, CancellationToken.None)); - - Assert.Equal(version, syntaxTreeStorage.GetIdentifierSetVersion(document)); - - List positions = new List(); - Assert.True(syntaxTreeStorage.ReadIdentifierPositions(document, version, "Test", positions, CancellationToken.None)); - - Assert.Equal(1, positions.Count); - Assert.Equal(29, positions[0]); - } - } - private Solution CreateOrOpenSolution() { string solutionFile = Path.Combine(_persistentFolder, "Solution1.sln"); diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/DefinitionsAndReferencesPresenter.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/DefinitionsAndReferencesPresenter.cs index bdd7606a02ab05fafbfb07c25f3fc878fed13315..99f4586a72016d274bd50c6923ad84f63ef57510 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/DefinitionsAndReferencesPresenter.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/DefinitionsAndReferencesPresenter.cs @@ -4,7 +4,7 @@ using System.ComponentModel.Composition; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.VisualStudio.LanguageServices.Implementation.Library.FindResults; using Microsoft.VisualStudio.Shell; diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs index c66ca4fd036dbd1d479f10ea8f8d74fa76f83369..4c8ceff75e13a8c9b5381afd19868eb1172913c9 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/VisualStudioDefinitionsAndReferencesFactory.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.Shared.Extensions; diff --git a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_FindReferences.cs b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_FindReferences.cs index b1fa0f56c08fbfa20e76887c67621c182539d400..2215cea7f0023789936dd2a86651c65defd8b8a3 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_FindReferences.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_FindReferences.cs @@ -6,7 +6,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/DefinitionTreeItem.cs b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/DefinitionTreeItem.cs index c16f7ba5a5717beb9015f80540b15e7d29fba397..ad63ca8c6b79c44191a4867202b0421346abec2f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/DefinitionTreeItem.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/DefinitionTreeItem.cs @@ -4,7 +4,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs b/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs index b54b1a8363c4e2e15e2ff3002345fbe6cbe8a73b..7837f7d643ad84ead02e281fc2074b1cad2e8e70 100644 --- a/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs +++ b/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs @@ -6,12 +6,11 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.GoToDefinition; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition; using Microsoft.CodeAnalysis.Editor.Undo; -using Microsoft.CodeAnalysis.FindReferences; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.GeneratedCodeRecognition; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.LanguageServices.Implementation; @@ -29,16 +28,16 @@ namespace Microsoft.VisualStudio.LanguageServices internal class RoslynVisualStudioWorkspace : VisualStudioWorkspaceImpl { private readonly IEnumerable> _navigableItemsPresenters; + private readonly IEnumerable> _streamingPresenters; private readonly IEnumerable> _referencedSymbolsPresenters; - private readonly IEnumerable> _externalDefinitionProviders; [ImportingConstructor] private RoslynVisualStudioWorkspace( SVsServiceProvider serviceProvider, SaveEventsService saveEventsService, [ImportMany] IEnumerable> navigableItemsPresenters, + [ImportMany] IEnumerable> streamingPresenters, [ImportMany] IEnumerable> referencedSymbolsPresenters, - [ImportMany] IEnumerable> externalDefinitionProviders, [ImportMany] IEnumerable documentOptionsProviderFactories) : base( serviceProvider, @@ -49,8 +48,8 @@ internal class RoslynVisualStudioWorkspace : VisualStudioWorkspaceImpl InitializeStandardVisualStudioWorkspace(serviceProvider, saveEventsService); _navigableItemsPresenters = navigableItemsPresenters; + _streamingPresenters = streamingPresenters; _referencedSymbolsPresenters = referencedSymbolsPresenters; - _externalDefinitionProviders = externalDefinitionProviders; foreach (var providerFactory in documentOptionsProviderFactories) { @@ -183,22 +182,24 @@ private static bool TryResolveSymbol(ISymbol symbol, Project project, Cancellati return true; } - public override bool TryGoToDefinition(ISymbol symbol, Project project, CancellationToken cancellationToken) + public override bool TryGoToDefinition( + ISymbol symbol, Project project, CancellationToken cancellationToken) { - if (!_navigableItemsPresenters.Any()) + if (!_navigableItemsPresenters.Any() && + !_streamingPresenters.Any()) { return false; } - ISymbol searchSymbol; - Project searchProject; - if (!TryResolveSymbol(symbol, project, cancellationToken, out searchSymbol, out searchProject)) + if (!TryResolveSymbol(symbol, project, cancellationToken, + out var searchSymbol, out var searchProject)) { return false; } return GoToDefinitionHelpers.TryGoToDefinition( - searchSymbol, searchProject, _externalDefinitionProviders, _navigableItemsPresenters, cancellationToken: cancellationToken); + searchSymbol, searchProject, + _navigableItemsPresenters, _streamingPresenters, cancellationToken); } public override bool TryFindAllReferences(ISymbol symbol, Project project, CancellationToken cancellationToken) @@ -208,9 +209,7 @@ public override bool TryFindAllReferences(ISymbol symbol, Project project, Cance return false; } - ISymbol searchSymbol; - Project searchProject; - if (!TryResolveSymbol(symbol, project, cancellationToken, out searchSymbol, out searchProject)) + if (!TryResolveSymbol(symbol, project, cancellationToken, out var searchSymbol, out var searchProject)) { return false; } @@ -309,4 +308,4 @@ internal override object GetBrowseObject(SymbolListItem symbolListItem) return null; } } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Next/FindReferences/FindReferencesTableControlEventProcessorProvider.cs b/src/VisualStudio/Core/Next/FindReferences/FindReferencesTableControlEventProcessorProvider.cs index d0058874f6668b314a15df101a4d691c945d757c..fadb3cff5e17f1c79518227ead654db1b7485607 100644 --- a/src/VisualStudio/Core/Next/FindReferences/FindReferencesTableControlEventProcessorProvider.cs +++ b/src/VisualStudio/Core/Next/FindReferences/FindReferencesTableControlEventProcessorProvider.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Text.Classification; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { /// /// Event processor that we export so we can control how navigation works in the streaming @@ -14,11 +14,11 @@ namespace Microsoft.VisualStudio.LanguageServices.FindReferences /// ourselves so that we can do things like navigate to MetadataAsSource. /// [Export(typeof(ITableControlEventProcessorProvider))] - [DataSourceType(StreamingFindReferencesPresenter.RoslynFindReferencesTableDataSourceSourceTypeIdentifier)] - [DataSource(StreamingFindReferencesPresenter.RoslynFindReferencesTableDataSourceIdentifier)] - [Name(nameof(FindReferencesTableControlEventProcessorProvider))] + [DataSourceType(StreamingFindUsagesPresenter.RoslynFindUsagesTableDataSourceSourceTypeIdentifier)] + [DataSource(StreamingFindUsagesPresenter.RoslynFindUsagesTableDataSourceIdentifier)] + [Name(nameof(FindUsagesTableControlEventProcessorProvider))] [Order(Before = Priority.Default)] - internal class FindReferencesTableControlEventProcessorProvider : ITableControlEventProcessorProvider + internal class FindUsagesTableControlEventProcessorProvider : ITableControlEventProcessorProvider { public ITableControlEventProcessor GetAssociatedEventProcessor( IWpfTableControl tableControl) diff --git a/src/VisualStudio/Core/Next/FindReferences/ISupportsNavigation.cs b/src/VisualStudio/Core/Next/FindReferences/ISupportsNavigation.cs index 7436c5da9b9a7c3d05e6b1055981ef742f44591a..bbd27fe9259b46c5e66b7c3fcdf319925a670948 100644 --- a/src/VisualStudio/Core/Next/FindReferences/ISupportsNavigation.cs +++ b/src/VisualStudio/Core/Next/FindReferences/ISupportsNavigation.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { internal interface ISupportsNavigation { bool TryNavigateTo(); } -} +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DisposableToolTip.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DisposableToolTip.cs similarity index 88% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DisposableToolTip.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DisposableToolTip.cs index c0235c95c4d27cf7568c3296e9bff160e7e37861..e055d25bc18e91298d55f14b0cc2a7cf15897abe 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DisposableToolTip.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DisposableToolTip.cs @@ -9,9 +9,9 @@ using System.Windows.Controls; using Microsoft.CodeAnalysis.Editor.Shared.Preview; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { private class DisposableToolTip : IDisposable { diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DocumentLocationEntry.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DocumentLocationEntry.cs similarity index 96% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DocumentLocationEntry.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DocumentLocationEntry.cs index ea06113b2b07c770126ecc85aed74ed30986b9aa..6b3f9421d0ce7f83cb2a9e793075be5ad29665ac 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.DocumentLocationEntry.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.DocumentLocationEntry.cs @@ -23,15 +23,15 @@ using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { private class DocumentSpanEntry : Entry { private static readonly object s_boxedProjectGuid = Guid.Empty; - private readonly TableDataSourceFindReferencesContext _context; + private readonly TableDataSourceFindUsagesContext _context; private readonly DocumentSpan _documentSpan; private readonly bool _isDefinitionLocation; @@ -39,7 +39,7 @@ private class DocumentSpanEntry : Entry private readonly ClassifiedSpansAndHighlightSpan _classifiedSpans; public DocumentSpanEntry( - TableDataSourceFindReferencesContext context, + TableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, DocumentSpan documentSpan, bool isDefinitionLocation, @@ -55,7 +55,7 @@ private class DocumentSpanEntry : Entry _classifiedSpans = classifiedSpans; } - private StreamingFindReferencesPresenter Presenter => _context.Presenter; + private StreamingFindUsagesPresenter Presenter => _context.Presenter; private Document Document => _documentSpan.Document; private TextSpan SourceSpan => _documentSpan.SourceSpan; @@ -103,7 +103,7 @@ public override bool TryCreateColumnContent(string columnName, out FrameworkElem } private static IList GetHighlightedInlines( - StreamingFindReferencesPresenter presenter, + StreamingFindUsagesPresenter presenter, SourceText sourceText, ClassifiedSpansAndHighlightSpan classifiedSpansAndHighlight, bool isDefinition) diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.Entry.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.Entry.cs similarity index 92% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.Entry.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.Entry.cs index a96f0283350a2484066ba0163b6825f1856897ff..247ef985cfb759d60516532c43c245d03d468eec 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.Entry.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.Entry.cs @@ -5,9 +5,9 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Shell.TableControl; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { /// /// Represents a single entry (i.e. row) in the ungrouped FAR table. diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.LazyToolTip.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.LazyToolTip.cs similarity index 95% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.LazyToolTip.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.LazyToolTip.cs index 84cd6cb366f3280fbbfdea5d3cacb4ecd6f5aadf..d6d980b739d4b4208237843f51cc655bbc766406 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.LazyToolTip.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.LazyToolTip.cs @@ -7,9 +7,9 @@ using System.Windows.Media; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { /// /// Class which allows us to provide a delay-created tooltip for our reference entries. diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.RoslynDefinitionBucket.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.RoslynDefinitionBucket.cs similarity index 84% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.RoslynDefinitionBucket.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.RoslynDefinitionBucket.cs index ec6fdab2a9bc02aa595fbf78027a36e060945c83..f92a20e5f532cc0d0f983727fb313c4780480ced 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.RoslynDefinitionBucket.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.RoslynDefinitionBucket.cs @@ -5,25 +5,25 @@ using System.Windows.Documents; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation { - private readonly StreamingFindReferencesPresenter _presenter; - private readonly TableDataSourceFindReferencesContext _context; + private readonly StreamingFindUsagesPresenter _presenter; + private readonly TableDataSourceFindUsagesContext _context; public readonly DefinitionItem DefinitionItem; public RoslynDefinitionBucket( - StreamingFindReferencesPresenter presenter, - TableDataSourceFindReferencesContext context, + StreamingFindUsagesPresenter presenter, + TableDataSourceFindUsagesContext context, DefinitionItem definitionItem) : base(name: definitionItem.DisplayParts.JoinText() + " " + definitionItem.GetHashCode(), sourceTypeIdentifier: context.SourceTypeIdentifier, diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.SimpleMessageEntry.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.SimpleMessageEntry.cs similarity index 90% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.SimpleMessageEntry.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.SimpleMessageEntry.cs index 8f7812c54f38080af898f3df7a5f40b8328e7b24..75a4a6f2ab6d0e1059118d8efbec7a0373f1bf2b 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.SimpleMessageEntry.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.SimpleMessageEntry.cs @@ -4,9 +4,9 @@ using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.Shell.TableManager; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { private class SimpleMessageEntry : Entry { diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableDataSourceFindReferencesContext.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableDataSourceFindUsagesContext.cs similarity index 97% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableDataSourceFindReferencesContext.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableDataSourceFindUsagesContext.cs index a2009c662baf6890730327b41dbe41c02deaec82..26e08c97ac29d568fc62bb56ef6301afac5d9205 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableDataSourceFindReferencesContext.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableDataSourceFindUsagesContext.cs @@ -11,27 +11,26 @@ using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.FindReferences; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableManager; using Roslyn.Utilities; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { - private class TableDataSourceFindReferencesContext : - FindReferencesContext, ITableDataSource, ITableEntriesSnapshotFactory + private class TableDataSourceFindUsagesContext : + FindUsagesContext, ITableDataSource, ITableEntriesSnapshotFactory { private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private ITableDataSink _tableDataSink; - public readonly StreamingFindReferencesPresenter Presenter; + public readonly StreamingFindUsagesPresenter Presenter; private readonly IFindAllReferencesWindow _findReferencesWindow; // Lock which protects _definitionToShoudlShowWithoutReferences, @@ -55,8 +54,8 @@ private class TableDataSourceFindReferencesContext : private TableEntriesSnapshot _lastSnapshot; public int CurrentVersionNumber { get; private set; } - public TableDataSourceFindReferencesContext( - StreamingFindReferencesPresenter presenter, + public TableDataSourceFindUsagesContext( + StreamingFindUsagesPresenter presenter, IFindAllReferencesWindow findReferencesWindow) { presenter.AssertIsForeground(); @@ -98,9 +97,9 @@ internal void OnSubscriptionDisposed() public string DisplayName => "Roslyn Data Source"; - public string Identifier => RoslynFindReferencesTableDataSourceIdentifier; + public string Identifier => RoslynFindUsagesTableDataSourceIdentifier; - public string SourceTypeIdentifier => RoslynFindReferencesTableDataSourceSourceTypeIdentifier; + public string SourceTypeIdentifier => RoslynFindUsagesTableDataSourceSourceTypeIdentifier; public IDisposable Subscribe(ITableDataSink sink) { @@ -117,7 +116,7 @@ public IDisposable Subscribe(ITableDataSink sink) #endregion - #region FindReferencesContext overrides. + #region FindUsagesContext overrides. public override void SetSearchLabel(string displayName) { diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableEntriesSnapshot.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableEntriesSnapshot.cs similarity index 93% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableEntriesSnapshot.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableEntriesSnapshot.cs index b8c153c74620b05671180f17f91ca3600c46f7c3..fa2e9249c6cc07c157c0e798a5d2329a30f47b16 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.TableEntriesSnapshot.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.TableEntriesSnapshot.cs @@ -5,9 +5,9 @@ using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - internal partial class StreamingFindReferencesPresenter + internal partial class StreamingFindUsagesPresenter { private class TableEntriesSnapshot : WpfTableEntriesSnapshotBase { diff --git a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.cs b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.cs similarity index 79% rename from src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.cs rename to src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.cs index 2adf28b695ff92298a89d57167ac813abd0faaab..67250920eab22558582a40d18c0f0c1a0d6a0b78 100644 --- a/src/VisualStudio/Core/Next/FindReferences/StreamingFindReferencesPresenter.cs +++ b/src/VisualStudio/Core/Next/FindReferences/StreamingFindUsagesPresenter.cs @@ -14,18 +14,19 @@ using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Utilities; using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.CodeAnalysis.FindUsages; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { - [Export(typeof(IStreamingFindReferencesPresenter)), Shared] - internal partial class StreamingFindReferencesPresenter : - ForegroundThreadAffinitizedObject, IStreamingFindReferencesPresenter + [Export(typeof(IStreamingFindUsagesPresenter)), Shared] + internal partial class StreamingFindUsagesPresenter : + ForegroundThreadAffinitizedObject, IStreamingFindUsagesPresenter { - public const string RoslynFindReferencesTableDataSourceIdentifier = - nameof(RoslynFindReferencesTableDataSourceIdentifier); + public const string RoslynFindUsagesTableDataSourceIdentifier = + nameof(RoslynFindUsagesTableDataSourceIdentifier); - public const string RoslynFindReferencesTableDataSourceSourceTypeIdentifier = - nameof(RoslynFindReferencesTableDataSourceSourceTypeIdentifier); + public const string RoslynFindUsagesTableDataSourceSourceTypeIdentifier = + nameof(RoslynFindUsagesTableDataSourceSourceTypeIdentifier); private readonly IServiceProvider _serviceProvider; @@ -40,7 +41,7 @@ internal partial class StreamingFindReferencesPresenter : private readonly IFindAllReferencesService _vsFindAllReferencesService; [ImportingConstructor] - public StreamingFindReferencesPresenter( + public StreamingFindUsagesPresenter( Shell.SVsServiceProvider serviceProvider, ITextBufferFactoryService textBufferFactoryService, IProjectionBufferFactoryService projectionBufferFactoryService, @@ -63,15 +64,15 @@ internal partial class StreamingFindReferencesPresenter : _vsFindAllReferencesService = (IFindAllReferencesService)_serviceProvider.GetService(typeof(SVsFindAllReferences)); } - public FindReferencesContext StartSearch() + public FindUsagesContext StartSearch(string title) { this.AssertIsForeground(); // Get the appropriate window for FAR results to go into. - var window = _vsFindAllReferencesService.StartSearch(label: null); + var window = _vsFindAllReferencesService.StartSearch(title); // Make the data source that will feed data into this window. - var dataSource = new TableDataSourceFindReferencesContext(this, window); + var dataSource = new TableDataSourceFindUsagesContext(this, window); // And return the data source so that the FindRefs engine can report results // which the data source can then create the appropriate presentation items for diff --git a/src/VisualStudio/Core/Next/FindReferences/TaggedTextAndHighlightSpan.cs b/src/VisualStudio/Core/Next/FindReferences/TaggedTextAndHighlightSpan.cs index 1c78f7ec7eb0060a7056775b2222bad169554783..c184647dce23c676d2c87d60a18b7d7f5df66d14 100644 --- a/src/VisualStudio/Core/Next/FindReferences/TaggedTextAndHighlightSpan.cs +++ b/src/VisualStudio/Core/Next/FindReferences/TaggedTextAndHighlightSpan.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.VisualStudio.LanguageServices.FindReferences +namespace Microsoft.VisualStudio.LanguageServices.FindUsages { internal struct ClassifiedSpansAndHighlightSpan { diff --git a/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj b/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj index 811919bbf751d1da837ec64a67a9b6129131314a..da846eb31dfbc77d3e2836bfa5528e46dbc6d6cb 100644 --- a/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj +++ b/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj @@ -84,15 +84,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/VisualStudio/Core/Test/FindResults/FindResultsTests.vb b/src/VisualStudio/Core/Test/FindResults/FindResultsTests.vb index 80a5a39af2be9471b308d4b3ba5553fae7313011..bf153765e6b1e6ee67bab86d53e20382449647a8 100644 --- a/src/VisualStudio/Core/Test/FindResults/FindResultsTests.vb +++ b/src/VisualStudio/Core/Test/FindResults/FindResultsTests.vb @@ -6,7 +6,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.FindReferences +Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.FindResults diff --git a/src/VisualStudio/Core/Test/GoToDefinition/GoToDefinitionApiTests.vb b/src/VisualStudio/Core/Test/GoToDefinition/GoToDefinitionApiTests.vb index 1de8421d0ed945260b14e678b3a7bb987128bfb8..583e43014c45ef11287eeb582c80a02a85403b1f 100644 --- a/src/VisualStudio/Core/Test/GoToDefinition/GoToDefinitionApiTests.vb +++ b/src/VisualStudio/Core/Test/GoToDefinition/GoToDefinitionApiTests.vb @@ -2,9 +2,8 @@ Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Editor +Imports Microsoft.CodeAnalysis.Editor.GoToDefinition Imports Microsoft.CodeAnalysis.Editor.Host -Imports Microsoft.CodeAnalysis.Editor.Implementation.GoToDefinition Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Roslyn.Test.Utilities @@ -39,7 +38,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.GoToDefinition WpfTestCase.RequireWpfFact($"{NameOf(GoToDefinitionHelpers)}.{NameOf(GoToDefinitionHelpers.TryGoToDefinition)} assumes it's on the UI thread with a WaitAndGetResult call") Dim success = GoToDefinitionHelpers.TryGoToDefinition( - symbolInfo.Symbol, document.Project, {}, {New Lazy(Of INavigableItemsPresenter)(Function() presenter)}, thirdPartyNavigationAllowed:=True, throwOnHiddenDefinition:=False, cancellationToken:=CancellationToken.None) + symbolInfo.Symbol, document.Project, + {New Lazy(Of INavigableItemsPresenter)(Function() presenter)}, {}, + thirdPartyNavigationAllowed:=True, throwOnHiddenDefinition:=False, cancellationToken:=CancellationToken.None) Assert.Equal(expectSuccess, success) End Using diff --git a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs index e361aff04bedecb889a8a66d99d59f1568cd5313..86576708f7e35bed9487cb3fde4823dfd08a8254 100644 --- a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs @@ -10,16 +10,20 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeStyle internal static class CSharpCodeStyleOptions { // TODO: get sign off on public api changes. - public static readonly Option UseVarWhenDeclaringLocals = new Option(nameof(CodeStyleOptions), nameof(UseVarWhenDeclaringLocals), defaultValue: true, + public static readonly Option UseVarWhenDeclaringLocals = new Option( + nameof(CodeStyleOptions), nameof(UseVarWhenDeclaringLocals), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.UseVarWhenDeclaringLocals")); - public static readonly Option> UseImplicitTypeForIntrinsicTypes = new Option>(nameof(CodeStyleOptions), nameof(UseImplicitTypeForIntrinsicTypes), defaultValue: CodeStyleOption.Default, + public static readonly Option> UseImplicitTypeForIntrinsicTypes = new Option>( + nameof(CodeStyleOptions), nameof(UseImplicitTypeForIntrinsicTypes), defaultValue: CodeStyleOption.Default, storageLocations: new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.UseImplicitTypeForIntrinsicTypes")); - public static readonly Option> UseImplicitTypeWhereApparent = new Option>(nameof(CodeStyleOptions), nameof(UseImplicitTypeWhereApparent), defaultValue: CodeStyleOption.Default, + public static readonly Option> UseImplicitTypeWhereApparent = new Option>( + nameof(CodeStyleOptions), nameof(UseImplicitTypeWhereApparent), defaultValue: CodeStyleOption.Default, storageLocations: new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.UseImplicitTypeWhereApparent")); - public static readonly Option> UseImplicitTypeWherePossible = new Option>(nameof(CodeStyleOptions), nameof(UseImplicitTypeWherePossible), defaultValue: CodeStyleOption.Default, + public static readonly Option> UseImplicitTypeWherePossible = new Option>( + nameof(CodeStyleOptions), nameof(UseImplicitTypeWherePossible), defaultValue: CodeStyleOption.Default, storageLocations: new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.UseImplicitTypeWherePossible")); public static readonly Option> PreferConditionalDelegateCall = new Option>(nameof(CodeStyleOptions), nameof(PreferConditionalDelegateCall), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, diff --git a/src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs index b53c2a9ca55ecb46573d8e42b9d1ec3b1480e138..bb2317df6ea42f97db3b8aa57c03e6c6422984d0 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -292,6 +292,46 @@ public static bool IsAttributeNameContext(this SyntaxTree syntaxTree, int positi return false; } + public static bool IsLocalFunctionDeclarationContext( + this SyntaxTree syntaxTree, + int position, + CancellationToken cancellationToken) + { + var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken); + var token = leftToken.GetPreviousTokenIfTouchingWord(position); + + // Local functions are always valid in a statement context + if (syntaxTree.IsStatementContext(position, leftToken, cancellationToken)) + { + return true; + } + + // Also valid after certain modifiers + var validModifiers = SyntaxKindSet.LocalFunctionModifiers; + + var modifierTokens = syntaxTree.GetPrecedingModifiers( + position, token, out int beforeModifiersPosition); + + if (modifierTokens.IsSubsetOf(validModifiers)) + { + if (token.HasMatchingText(SyntaxKind.AsyncKeyword)) + { + // second appearance of "async" not followed by modifier: treat as type + if (syntaxTree.GetPrecedingModifiers(token.SpanStart, token, cancellationToken) + .Contains(SyntaxKind.AsyncKeyword)) + { + return false; + } + } + + leftToken = syntaxTree.FindTokenOnLeftOfPosition(beforeModifiersPosition, cancellationToken); + token = leftToken.GetPreviousTokenIfTouchingWord(beforeModifiersPosition); + return syntaxTree.IsStatementContext(beforeModifiersPosition, token, cancellationToken); + } + + return false; + } + public static bool IsTypeDeclarationContext( this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken) { diff --git a/src/Workspaces/CSharp/Portable/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ITypeSymbolExtensions.cs index 1443ba3fa3ae921aca1656bb61c1d44d6e95cfba..2e68bb14e5109c31463559f2825f5b7a32e5f170 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ITypeSymbolExtensions.cs @@ -6,9 +6,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; @@ -133,5 +135,34 @@ public static bool IsIntrinsicType(this ITypeSymbol typeSymbol) return false; } } + + public static TypeSyntax GenerateTypeSyntaxOrVar( + this ITypeSymbol symbol, OptionSet options, bool typeIsApperant) + { + var useVar = IsVarDesired(symbol, options, typeIsApperant); + + return useVar + ? SyntaxFactory.IdentifierName("var") + : symbol.GenerateTypeSyntax(); + } + + private static bool IsVarDesired(ITypeSymbol type, OptionSet options, bool typeIsApperant) + { + // If they want it for intrinsics, and this is an intrinsic, then use var. + if (type.IsSpecialType() == true) + { + return options.GetOption(CSharpCodeStyleOptions.UseImplicitTypeForIntrinsicTypes).Value; + } + + // If they want it only for apperant types, then only use "var" if the caller + // says the type was apperant. + if (typeIsApperant) + { + return options.GetOption(CSharpCodeStyleOptions.UseImplicitTypeWhereApparent).Value; + } + + // If they want "var" whenever possible, then use "var". + return options.GetOption(CSharpCodeStyleOptions.UseImplicitTypeWherePossible).Value; + } } -} +} \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs index d7b5bbf0fc257b9a5f524cba8d0b66a5f453981a..3b18d66726392319992a9248820015fbe5108375 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs @@ -16,7 +16,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions internal static partial class SyntaxTreeExtensions { public static ISet GetPrecedingModifiers( - this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken) + this SyntaxTree syntaxTree, + int position, + SyntaxToken tokenOnLeftOfPosition, + CancellationToken cancellationToken) + => syntaxTree.GetPrecedingModifiers(position, tokenOnLeftOfPosition, out var _); + + public static ISet GetPrecedingModifiers( + this SyntaxTree syntaxTree, + int position, + SyntaxToken tokenOnLeftOfPosition, + out int positionBeforeModifiers) { var token = tokenOnLeftOfPosition; token = token.GetPreviousTokenIfTouchingWord(position); @@ -58,6 +68,7 @@ internal static partial class SyntaxTreeExtensions break; } + positionBeforeModifiers = token.FullSpan.End; return result; } diff --git a/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs index 6081a8d26236e2d694714cbde840d1eba98900f4..16c169cea54ea6b750560580b0e044a313b216e4 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs @@ -2,11 +2,9 @@ using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Extensions { @@ -93,4 +91,4 @@ public static bool IsTypeInferred(this TypeSyntax typeSyntax, SemanticModel sema return true; } } -} +} \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/Utilities/SyntaxKindSet.cs b/src/Workspaces/CSharp/Portable/Utilities/SyntaxKindSet.cs index 6504971282790f902abbe8655954b69fb2c5f32d..4207b933c72cd87503ddaae7d1695d0c0bac0afd 100644 --- a/src/Workspaces/CSharp/Portable/Utilities/SyntaxKindSet.cs +++ b/src/Workspaces/CSharp/Portable/Utilities/SyntaxKindSet.cs @@ -52,6 +52,12 @@ internal class SyntaxKindSet SyntaxKind.VolatileKeyword, }; + public static readonly ISet LocalFunctionModifiers = new HashSet(SyntaxFacts.EqualityComparer) + { + SyntaxKind.AsyncKeyword, + SyntaxKind.UnsafeKeyword + }; + public static readonly ISet AllTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.InterfaceDeclaration, diff --git a/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt index f35ad7b9e3f2de60fa6a94f207d4284ab18240ed..c98bf9b55c78ef739f432256719c24840367c9b3 100644 --- a/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Desktop/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ -Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Diagnostics.get -> System.Collections.Immutable.ImmutableList \ No newline at end of file +static Microsoft.CodeAnalysis.Host.Mef.DesktopMefHostServices.DefaultAssemblies.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Diagnostics.get -> System.Collections.Immutable.ImmutableList diff --git a/src/Workspaces/Core/Desktop/SymbolSearch/SymbolSearchUpdateEngine.Update.cs b/src/Workspaces/Core/Desktop/SymbolSearch/SymbolSearchUpdateEngine.Update.cs index 7471a810b1781620010d0ea5ede067aba73f788e..50414749dba46e908cf14ba5eec7d30361a78bb5 100644 --- a/src/Workspaces/Core/Desktop/SymbolSearch/SymbolSearchUpdateEngine.Update.cs +++ b/src/Workspaces/Core/Desktop/SymbolSearch/SymbolSearchUpdateEngine.Update.cs @@ -562,6 +562,7 @@ private async Task RepeatIOAsync(Func action) try { await action().ConfigureAwait(false); + return; } catch (Exception e) { diff --git a/src/Workspaces/Core/Desktop/Workspace/Esent/EsentPersistentStorage_IdentifierTable.cs b/src/Workspaces/Core/Desktop/Workspace/Esent/EsentPersistentStorage_IdentifierTable.cs deleted file mode 100644 index f90b17e163ebc015ff33e6e564e63659a4937b19..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Desktop/Workspace/Esent/EsentPersistentStorage_IdentifierTable.cs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Versions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Esent -{ - internal partial class EsentPersistentStorage : ISyntaxTreeInfoPersistentStorage - { - private const string IdentifierSetVersion = ""; - private const string IdentifierSetSerializationVersion = "1"; - private const int NotSupported = -1; - private const int FlushThreshold = 5000; - - private int? _identifierSetVersionId; - - private bool TryGetUniqueIdentifierId(string identifier, out int id) - { - id = default(int); - - if (string.IsNullOrWhiteSpace(identifier)) - { - return false; - } - - try - { - id = _esentStorage.GetUniqueIdentifierId(identifier); - return true; - } - catch (Exception) - { - return false; - } - } - - private bool TryGetIdentifierSetVersionId(out int id) - { - if (_identifierSetVersionId.HasValue) - { - id = _identifierSetVersionId.Value; - return true; - } - - if (TryGetUniqueIdentifierId(IdentifierSetVersion, out id)) - { - _identifierSetVersionId = id; - return true; - } - - return false; - } - - private VersionStamp GetIdentifierSetVersion(EsentStorage.Key key) - { - if (!PersistenceEnabled) - { - return VersionStamp.Default; - } - - if (!TryGetIdentifierSetVersionId(out var identifierId)) - { - return VersionStamp.Default; - } - - // TODO: verify that project is in solution associated with the storage - return EsentExceptionWrapper(key, identifierId, GetIdentifierSetVersion, CancellationToken.None); - } - - private VersionStamp GetIdentifierSetVersion(EsentStorage.Key key, int identifierId, object unused1, object unused2, CancellationToken cancellationToken) - { - using (var accessor = _esentStorage.GetIdentifierLocationTableAccessor()) - using (var stream = accessor.GetReadStream(key, identifierId)) - { - if (stream == null) - { - return VersionStamp.Default; - } - - using (var reader = new ObjectReader(stream)) - { - return VersionStamp.ReadFrom(reader); - } - } - } - - public VersionStamp GetIdentifierSetVersion(Document document) - { - if (!PersistenceEnabled) - { - return VersionStamp.Default; - } - - if (!TryGetProjectAndDocumentKey(document, out var key)) - { - return VersionStamp.Default; - } - - return GetIdentifierSetVersion(key); - } - - public bool ReadIdentifierPositions(Document document, VersionStamp syntaxVersion, string identifier, List positions, CancellationToken cancellationToken) - { - Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(identifier)); - - if (!PersistenceEnabled) - { - return false; - } - - if (!TryGetProjectAndDocumentKey(document, out var key)) - { - return false; - } - - var persistedVersion = GetIdentifierSetVersion(key); - if (!document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistedVersion)) - { - return false; - } - - if (!TryGetUniqueIdentifierId(identifier, out var identifierId)) - { - return false; - } - - return EsentExceptionWrapper(key, identifierId, positions, ReadIdentifierPositions, cancellationToken); - } - - private bool ReadIdentifierPositions(EsentStorage.Key key, int identifierId, List positions, object unused, CancellationToken cancellationToken) - { - using (var accessor = _esentStorage.GetIdentifierLocationTableAccessor()) - using (var stream = accessor.GetReadStream(key, identifierId)) - { - if (stream == null) - { - // no such identifier exist. - return true; - } - - using (var reader = new ObjectReader(stream)) - { - var formatVersion = reader.ReadString(); - if (formatVersion != IdentifierSetSerializationVersion) - { - return false; - } - - return ReadFrom(reader, positions, cancellationToken); - } - } - } - - private bool DeleteIdentifierLocations(EsentStorage.Key key, CancellationToken cancellationToken) - { - using (var accessor = _esentStorage.GetIdentifierLocationTableAccessor()) - { - accessor.Delete(key, cancellationToken); - return accessor.ApplyChanges(); - } - } - - public bool WriteIdentifierLocations(Document document, VersionStamp version, SyntaxNode root, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(root); - - if (!PersistenceEnabled) - { - return false; - } - - if (!TryGetProjectAndDocumentKey(document, out var key)) - { - return false; - } - - return EsentExceptionWrapper(key, document, version, root, WriteIdentifierLocations, cancellationToken); - } - - private bool WriteIdentifierLocations(EsentStorage.Key key, Document document, VersionStamp version, SyntaxNode root, CancellationToken cancellationToken) - { - // delete any existing data - if (!DeleteIdentifierLocations(key, cancellationToken)) - { - return false; - } - - var identifierMap = SharedPools.StringIgnoreCaseDictionary().AllocateAndClear(); - - Dictionary> map = null; - try - { - map = CreateIdentifierLocations(document, root, cancellationToken); - - // okay, write new data - using (var accessor = _esentStorage.GetIdentifierLocationTableAccessor()) - { - // make sure I have all identifier ready before starting big insertion - int identifierId; - foreach (var identifier in map.Keys) - { - if (!TryGetUniqueIdentifierId(identifier, out identifierId)) - { - return false; - } - - identifierMap[identifier] = identifierId; - } - - // save whole map - var uncommittedCount = 0; - - foreach (var kv in map) - { - cancellationToken.ThrowIfCancellationRequested(); - - var identifier = kv.Key; - var positions = kv.Value; - - if ((uncommittedCount + positions.Count) > FlushThreshold) - { - accessor.Flush(); - uncommittedCount = 0; - } - - accessor.PrepareBatchOneInsert(); - - identifierId = identifierMap[identifier]; - - using (var stream = accessor.GetWriteStream(key, identifierId)) - using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) - { - writer.WriteString(IdentifierSetSerializationVersion); - WriteList(writer, positions); - } - - accessor.FinishBatchOneInsert(); - - uncommittedCount += positions.Count; - } - - // save special identifier that indicates version for this document - if (!TrySaveIdentifierSetVersion(accessor, key, version)) - { - return false; - } - - return accessor.ApplyChanges(); - } - } - finally - { - SharedPools.StringIgnoreCaseDictionary().ClearAndFree(identifierMap); - Free(map); - } - } - - private void Free(Dictionary> map) - { - if (map == null) - { - return; - } - - foreach (var value in map.Values) - { - if (value == null) - { - continue; - } - - SharedPools.BigDefault>().ClearAndFree(value); - } - - SharedPools.StringIgnoreCaseDictionary>().ClearAndFree(map); - } - - private bool TrySaveIdentifierSetVersion( - EsentStorage.IdentifierLocationTableAccessor accessor, EsentStorage.Key key, VersionStamp version) - { - if (!TryGetIdentifierSetVersionId(out var identifierId)) - { - return false; - } - - accessor.PrepareBatchOneInsert(); - using (var stream = accessor.GetWriteStream(key, identifierId)) - using (var writer = new ObjectWriter(stream)) - { - version.WriteTo(writer); - } - - accessor.FinishBatchOneInsert(); - return true; - } - - private bool ReadFrom(ObjectReader reader, List result, CancellationToken cancellationToken) - { - var count = reader.ReadInt32(); - if (count < 0) - { - return false; - } - - for (int i = 0; i < count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - result.Add(reader.ReadInt32()); - } - - return true; - } - - private void WriteList(ObjectWriter writer, List positions) - { - if (positions.Count > FlushThreshold) - { - writer.WriteInt32(NotSupported); - return; - } - - writer.WriteInt32(positions.Count); - - foreach (var position in positions) - { - writer.WriteInt32(position); - } - } - - private static Dictionary> CreateIdentifierLocations(Document document, SyntaxNode root, CancellationToken cancellationToken) - { - var syntaxFacts = document.GetLanguageService(); - - var identifierMap = SharedPools.StringIgnoreCaseDictionary>().AllocateAndClear(); - foreach (var token in root.DescendantTokens(descendIntoTrivia: true)) - { - if (token.IsMissing || token.Span.Length == 0) - { - continue; - } - - if (syntaxFacts.IsIdentifier(token) || syntaxFacts.IsGlobalNamespaceKeyword(token)) - { - var valueText = token.ValueText; - identifierMap.GetOrAdd(valueText, _ => SharedPools.BigDefault>().AllocateAndClear()).Add(token.Span.Start); - } - } - - return identifierMap; - } - } -} diff --git a/src/Workspaces/Core/Desktop/Workspace/Host/Mef/DesktopMefHostServices.cs b/src/Workspaces/Core/Desktop/Workspace/Host/Mef/DesktopMefHostServices.cs index dabc1d616b3725c06191d1179359b27dffae2f2f..3e2d89c48adf35f91a2e4120d6c93193de5d7601 100644 --- a/src/Workspaces/Core/Desktop/Workspace/Host/Mef/DesktopMefHostServices.cs +++ b/src/Workspaces/Core/Desktop/Workspace/Host/Mef/DesktopMefHostServices.cs @@ -25,7 +25,7 @@ public static MefHostServices DefaultServices } private static ImmutableArray s_defaultAssemblies; - private static ImmutableArray DefaultAssemblies + public static ImmutableArray DefaultAssemblies { get { diff --git a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj index e091ac484bc79703e6915a1d1e029b5eb3bd5c54..d2ac57a98e0146ecc9f1a93068904d20b8952156 100644 --- a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj @@ -79,7 +79,6 @@ - @@ -190,4 +189,4 @@ - + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs b/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs index 5c5b645a95f7a02f2c1489dcbefed09391be1703..afab5f103106159872c9a1f94d0ee0b7477297b8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs @@ -46,7 +46,7 @@ public static async Task> GetConstructorInitializerToke // It's very costly to walk an entire tree. So if the tree is simple and doesn't contain // any unicode escapes in it, then we do simple string matching to find the tokens. - var info = await SyntaxTreeInfo.GetIdentifierInfoAsync(document, cancellationToken).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false); if (!info.ProbablyContainsIdentifier(identifier)) { return ImmutableArray.Empty; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 5044dbd55832a93cdfb8e39ebf4958db9bd5c5a3..1d56cc9e34da4ac1421bdeed6db6c67e2c824ab1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -534,7 +534,7 @@ internal static class DependentTypeFinder // models and DeclaredSymbolInfo for hte documents we look at. // Because we're only processing a project at a time, this is not an issue. var cachedModels = new ConcurrentSet(); - var cachedInfos = new ConcurrentSet(); + var cachedInfos = new ConcurrentSet(); var finalResult = CreateSymbolAndProjectIdSet(); @@ -609,11 +609,11 @@ internal static class DependentTypeFinder SymbolAndProjectIdSet typesToSearchFor, InheritanceQuery inheritanceQuery, ConcurrentSet cachedModels, - ConcurrentSet cachedInfos, + ConcurrentSet cachedInfos, Func typeImmediatelyMatches, CancellationToken cancellationToken) { - var declarationInfo = await document.GetDeclarationInfoAsync(cancellationToken).ConfigureAwait(false); + var declarationInfo = await document.GetSyntaxTreeIndexAsync(cancellationToken).ConfigureAwait(false); cachedInfos.Add(declarationInfo); var result = CreateSymbolAndProjectIdSet(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index c76a6b4dc48058f4c8f59a135ded17d1e20bb3d1..d4663093bc509fc8fa26c6c8c87f5a3d3f3fc084 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -96,20 +96,9 @@ public static SymbolInfo GetSymbolInfo(SemanticModel model, SyntaxNode node, Can ISyntaxFactsService syntaxFacts, Document document, VersionStamp version, SyntaxNode root, SourceText content, string text, Func candidate, CancellationToken cancellationToken) { - if (text.Length > 0) - { - using (var positions = SharedPools.BigDefault>().GetPooledObject()) - { - if (SyntaxTreeIdentifierInfo.TryGetIdentifierLocations(document, version, text, positions.Object, cancellationToken)) - { - return GetTokensFromText(root, positions.Object, text, candidate, cancellationToken); - } - } - - return GetTokensFromText(syntaxFacts, root, content, text, candidate, cancellationToken); - } - - return ImmutableArray.Empty; + return text.Length > 0 + ? GetTokensFromText(syntaxFacts, root, content, text, candidate, cancellationToken) + : ImmutableArray.Empty; } private static ImmutableArray GetTokensFromText( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 0475d677a9e7a3383cdd8ed9fc703149de82ac49..ae0102480b1540a8b667d96f161087df91a71156 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -68,7 +68,7 @@ protected Task> FindDocumentsAsync(Project project, IIm { return FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetIdentifierInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); foreach (var value in values) { if (!info.ProbablyContainsIdentifier(value)) @@ -89,7 +89,7 @@ protected Task> FindDocumentsAsync(Project project, IIm { return FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); return info.ContainsPredefinedType(predefinedType); }, cancellationToken); } @@ -107,7 +107,7 @@ protected Task> FindDocumentsAsync(Project project, IIm return await FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); return info.ContainsPredefinedOperator(op); }, cancellationToken).ConfigureAwait(false); } @@ -389,7 +389,7 @@ protected Task> FindDocumentsWithForEachStatementsAsync { return FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); return info.ContainsForEachStatement; }, cancellationToken); } @@ -399,7 +399,7 @@ protected Task> FindDocumentsWithForEachStatementsAsync Document document, CancellationToken cancellationToken) { - var syntaxTreeInfo = await SyntaxTreeInfo.GetContextInfoAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTreeInfo = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false); if (syntaxTreeInfo.ContainsForEachStatement) { var semanticFacts = document.Project.LanguageServices.GetService(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 5c0a126558f695771b9d686a560ec125909a3e76..b89623d6722b77f799ced67fded74663a01d40ba 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -27,20 +27,19 @@ protected override bool CanFind(IMethodSymbol symbol) { return FindDocumentsAsync(project, documents, async (d, c) => { - var contextInfo = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); - if (contextInfo.ContainsBaseConstructorInitializer) + var index = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); + if (index.ContainsBaseConstructorInitializer) { return true; } - var identifierInfo = await SyntaxTreeInfo.GetIdentifierInfoAsync(d, c).ConfigureAwait(false); - if (identifierInfo.ProbablyContainsIdentifier(symbol.ContainingType.Name)) + if (index.ProbablyContainsIdentifier(symbol.ContainingType.Name)) { - if (contextInfo.ContainsThisConstructorInitializer) + if (index.ContainsThisConstructorInitializer) { return true; } - else if (project.Language == LanguageNames.VisualBasic && identifierInfo.ProbablyContainsIdentifier("New")) + else if (project.Language == LanguageNames.VisualBasic && index.ProbablyContainsIdentifier("New")) { // "New" can be explicitly accessed in xml doc comments to reference a constructor. return true; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 9596b9ddd09f322b95a865caca762fcac0d487e3..9924453b66a82091d2dc29da38926559fc3de055 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -103,7 +102,7 @@ private static bool IsForEachProperty(IPropertySymbol symbol) { return FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); return info.ContainsElementAccessExpression; }, cancellationToken); } @@ -113,7 +112,7 @@ private static bool IsForEachProperty(IPropertySymbol symbol) { return FindDocumentsAsync(project, documents, async (d, c) => { - var info = await SyntaxTreeInfo.GetContextInfoAsync(d, c).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false); return info.ContainsIndexerMemberCref; }, cancellationToken); } @@ -175,4 +174,4 @@ private static bool IsForEachProperty(IPropertySymbol symbol) return locations.ToImmutableAndFree(); } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractPersistableState.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractPersistableState.cs index db68e193da471dd2aadc2c030b3858aa267a38f1..2a328a9ee5147a15b89a6b4d61acda0ab9f5238d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractPersistableState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractPersistableState.cs @@ -55,8 +55,7 @@ private static bool TryReadVersion(ObjectReader reader, string formatVersion, ou using (var reader = new ObjectReader(stream)) { - VersionStamp persistVersion; - if (TryReadVersion(reader, formatVersion, out persistVersion) && + if (TryReadVersion(reader, formatVersion, out var persistVersion) && document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistVersion)) { return readFrom(reader, syntaxVersion); @@ -102,8 +101,7 @@ protected static async Task PrecalculatedAsync(Document document, string p { using (var reader = new ObjectReader(stream)) { - VersionStamp persistVersion; - return TryReadVersion(reader, formatVersion, out persistVersion) && + return TryReadVersion(reader, formatVersion, out var persistVersion) && document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistVersion); } } @@ -112,4 +110,4 @@ protected static async Task PrecalculatedAsync(Document document, string p return false; } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/IDeclarationInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/IDeclarationInfo.cs deleted file mode 100644 index 4652598d2471b7f906096f8b6377c7130d6f3859..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/IDeclarationInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - /// - /// Information about all the declarations defined within a document. Each declaration in the - /// document get a single item in . - /// - internal interface IDeclarationInfo - { - ImmutableArray DeclaredSymbolInfos { get; } - } -} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/ISyntaxTreeInfoPersistentStorage.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/ISyntaxTreeInfoPersistentStorage.cs deleted file mode 100644 index d61c0bf0c46aa17fe7475621f8969c92e55c5f13..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/ISyntaxTreeInfoPersistentStorage.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal interface ISyntaxTreeInfoPersistentStorage - { - VersionStamp GetIdentifierSetVersion(Document document); - bool ReadIdentifierPositions(Document document, VersionStamp version, string identifier, List positions, CancellationToken cancellationToken); - bool WriteIdentifierLocations(Document document, VersionStamp version, SyntaxNode root, CancellationToken cancellationToken); - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeContextInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeContextInfo.cs deleted file mode 100644 index 7d02caf261800c0b857293d07ed5e024f3e9b77a..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeContextInfo.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal class SyntaxTreeContextInfo : AbstractPersistableState, IObjectWritable - { - private const string PersistenceName = ""; - private const string SerializationFormat = "1"; - - /// - /// hold context info in memory. since context info is quite small (less than 30 bytes per a document), - /// holding this in memory should be fine. - /// - private static readonly ConditionalWeakTable> s_cache = - new ConditionalWeakTable>(); - - private readonly int _predefinedTypes; - private readonly int _predefinedOperators; - private readonly ContainingNodes _containingNodes; - - internal SyntaxTreeContextInfo( - VersionStamp version, - int predefinedTypes, - int predefinedOperators, - bool containsForEachStatement, - bool containsLockStatement, - bool containsUsingStatement, - bool containsQueryExpression, - bool containsThisConstructorInitializer, - bool containsBaseConstructorInitializer, - bool containsElementAccessExpression, - bool containsIndexerMemberCref) : - this(version, predefinedTypes, predefinedOperators, - ConvertToContainingNodeFlag( - containsForEachStatement, - containsLockStatement, - containsUsingStatement, - containsQueryExpression, - containsThisConstructorInitializer, - containsBaseConstructorInitializer, - containsElementAccessExpression, - containsIndexerMemberCref)) - { - } - - private SyntaxTreeContextInfo(VersionStamp version, int predefinedTypes, int predefinedOperators, ContainingNodes containingNodes) : - base(version) - { - _predefinedTypes = predefinedTypes; - _predefinedOperators = predefinedOperators; - _containingNodes = containingNodes; - } - - private static ContainingNodes ConvertToContainingNodeFlag( - bool containsForEachStatement, - bool containsLockStatement, - bool containsUsingStatement, - bool containsQueryExpression, - bool containsThisConstructorInitializer, - bool containsBaseConstructorInitializer, - bool containsElementAccessExpression, - bool containsIndexerMemberCref) - { - var containingNodes = ContainingNodes.None; - - containingNodes = containsForEachStatement ? (containingNodes | ContainingNodes.ContainsForEachStatement) : containingNodes; - containingNodes = containsLockStatement ? (containingNodes | ContainingNodes.ContainsLockStatement) : containingNodes; - containingNodes = containsUsingStatement ? (containingNodes | ContainingNodes.ContainsUsingStatement) : containingNodes; - containingNodes = containsQueryExpression ? (containingNodes | ContainingNodes.ContainsQueryExpression) : containingNodes; - containingNodes = containsThisConstructorInitializer ? (containingNodes | ContainingNodes.ContainsThisConstructorInitializer) : containingNodes; - containingNodes = containsBaseConstructorInitializer ? (containingNodes | ContainingNodes.ContainsBaseConstructorInitializer) : containingNodes; - containingNodes = containsElementAccessExpression ? (containingNodes | ContainingNodes.ContainsElementAccessExpression) : containingNodes; - containingNodes = containsIndexerMemberCref ? (containingNodes | ContainingNodes.ContainsIndexerMemberCref) : containingNodes; - - return containingNodes; - } - - public bool ContainsPredefinedType(PredefinedType type) - { - return (_predefinedTypes & (int)type) == (int)type; - } - - public bool ContainsPredefinedOperator(PredefinedOperator op) - { - return (_predefinedOperators & (int)op) == (int)op; - } - - public bool ContainsForEachStatement - { - get - { - return (_containingNodes & ContainingNodes.ContainsForEachStatement) == ContainingNodes.ContainsForEachStatement; - } - } - - public bool ContainsLockStatement - { - get - { - return (_containingNodes & ContainingNodes.ContainsLockStatement) == ContainingNodes.ContainsLockStatement; - } - } - - public bool ContainsUsingStatement - { - get - { - return (_containingNodes & ContainingNodes.ContainsUsingStatement) == ContainingNodes.ContainsUsingStatement; - } - } - - public bool ContainsQueryExpression - { - get - { - return (_containingNodes & ContainingNodes.ContainsQueryExpression) == ContainingNodes.ContainsQueryExpression; - } - } - - public bool ContainsThisConstructorInitializer - { - get - { - return (_containingNodes & ContainingNodes.ContainsThisConstructorInitializer) == ContainingNodes.ContainsThisConstructorInitializer; - } - } - - public bool ContainsBaseConstructorInitializer - { - get - { - return (_containingNodes & ContainingNodes.ContainsBaseConstructorInitializer) == ContainingNodes.ContainsBaseConstructorInitializer; - } - } - - public bool ContainsElementAccessExpression - { - get - { - return (_containingNodes & ContainingNodes.ContainsElementAccessExpression) == ContainingNodes.ContainsElementAccessExpression; - } - } - - public bool ContainsIndexerMemberCref - { - get - { - return (_containingNodes & ContainingNodes.ContainsIndexerMemberCref) == ContainingNodes.ContainsIndexerMemberCref; - } - } - - public void WriteTo(ObjectWriter writer) - { - // TODO: convert these set to use bit array rather than enum hashset - writer.WriteInt32(_predefinedTypes); - writer.WriteInt32(_predefinedOperators); - writer.WriteInt32((int)_containingNodes); - } - - private static SyntaxTreeContextInfo ReadFrom(ObjectReader reader, VersionStamp version) - { - try - { - var predefinedTypes = reader.ReadInt32(); - var predefinedOperators = reader.ReadInt32(); - var containingNodes = (ContainingNodes)reader.ReadInt32(); - - return new SyntaxTreeContextInfo(version, predefinedTypes, predefinedOperators, containingNodes); - } - catch (Exception) - { - } - - return null; - } - - public static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) - { - return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); - } - - public static async Task LoadAsync(Document document, CancellationToken cancellationToken) - { - var infoTable = s_cache.GetValue(document.Project.Solution.BranchId, _ => new ConditionalWeakTable()); - var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - - // first look to see if we already have the info in the cache - SyntaxTreeContextInfo info; - if (infoTable.TryGetValue(document.Id, out info) && info.Version == version) - { - return info; - } - - // cache is invalid. remove it - infoTable.Remove(document.Id); - - // check primary cache to see whether we have valid info there - var primaryInfoTable = s_cache.GetValue(document.Project.Solution.Workspace.PrimaryBranchId, _ => new ConditionalWeakTable()); - if (primaryInfoTable.TryGetValue(document.Id, out info) && info.Version == version) - { - return info; - } - - // check whether we can get it from persistence service - info = await LoadAsync(document, PersistenceName, SerializationFormat, ReadFrom, cancellationToken).ConfigureAwait(false); - if (info != null) - { - // save it in the cache. persisted info is always from primary branch. no reason to save it to the branched document cache. - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => info); - return info; - } - - // well, we don't have this information. - return null; - } - - public async Task SaveAsync(Document document, CancellationToken cancellationToken) - { - var infoTable = s_cache.GetValue(document.Project.Solution.BranchId, _ => new ConditionalWeakTable()); - - // if it is forked document, no reason to persist - if (await document.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken).ConfigureAwait(false)) - { - // cache new information to forked document cache - infoTable.Remove(document.Id); - infoTable.GetValue(document.Id, _ => this); - return false; - } - - // if it is not forked document, save it and cache to primary branch cache - var primaryInfoTable = s_cache.GetValue(document.Project.Solution.Workspace.PrimaryBranchId, _ => new ConditionalWeakTable()); - primaryInfoTable.Remove(document.Id); - primaryInfoTable.GetValue(document.Id, _ => this); - - return await SaveAsync(document, PersistenceName, SerializationFormat, this, cancellationToken).ConfigureAwait(false); - } - - [Flags] - private enum ContainingNodes - { - None = 0, - ContainsForEachStatement = 1, - ContainsLockStatement = 1 << 1, - ContainsUsingStatement = 1 << 2, - ContainsQueryExpression = 1 << 3, - ContainsThisConstructorInitializer = 1 << 4, - ContainsBaseConstructorInitializer = 1 << 5, - ContainsElementAccessExpression = 1 << 6, - ContainsIndexerMemberCref = 1 << 7, - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs deleted file mode 100644 index 8f4457084dda459f539a0880bad243c470f52354..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeDeclarationInfo.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal class SyntaxTreeDeclarationInfo : AbstractSyntaxTreeInfo, IDeclarationInfo - { - private const string PersistenceName = ""; - private const string SerializationFormat = "3"; - - /// - /// in memory cache will hold onto any info related to opened documents in primary branch or all documents in forked branch - /// - /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. - /// - private static readonly ConditionalWeakTable> s_cache = - new ConditionalWeakTable>(); - - public ImmutableArray DeclaredSymbolInfos { get; } - - public SyntaxTreeDeclarationInfo(VersionStamp version, ImmutableArray declaredSymbolInfos) - : base(version) - { - DeclaredSymbolInfos = declaredSymbolInfos; - } - - public override void WriteTo(ObjectWriter writer) - { - writer.WriteInt32(DeclaredSymbolInfos.Length); - foreach (var declaredSymbolInfo in DeclaredSymbolInfos) - { - declaredSymbolInfo.WriteTo(writer); - } - } - - public override Task SaveAsync(Document document, CancellationToken cancellationToken) - { - return SaveAsync(document, s_cache, PersistenceName, SerializationFormat, cancellationToken); - } - - public static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) - { - return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); - } - - public static async Task LoadAsync(Document document, CancellationToken cancellationToken) - { - var info = await LoadAsync(document, ReadFrom, s_cache, PersistenceName, SerializationFormat, cancellationToken).ConfigureAwait(false); - return (SyntaxTreeDeclarationInfo)info; - } - - private static SyntaxTreeDeclarationInfo ReadFrom(ObjectReader reader, VersionStamp version) - { - try - { - var declaredSymbolCount = reader.ReadInt32(); - var builder = ImmutableArray.CreateBuilder(declaredSymbolCount); - for (int i = 0; i < declaredSymbolCount; i++) - { - builder.Add(DeclaredSymbolInfo.ReadFrom(reader)); - } - - return new SyntaxTreeDeclarationInfo( - version, builder.MoveToImmutable()); - } - catch (Exception) - { - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs deleted file mode 100644 index 9ea308800d7fff6915e7a0ea84eee6667fcb245e..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal partial class SyntaxTreeIdentifierInfo : AbstractSyntaxTreeInfo - { - private const string PersistenceName = ""; - private const string SerializationFormat = "2"; - - /// - /// in memory cache will hold onto any info related to opened documents in primary branch or all documents in forked branch - /// - /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. - /// - private static readonly ConditionalWeakTable> s_cache = - new ConditionalWeakTable>(); - - private readonly VersionStamp _version; - private readonly BloomFilter _identifierFilter; - private readonly BloomFilter _escapedIdentifierFilter; - - public SyntaxTreeIdentifierInfo( - VersionStamp version, - BloomFilter identifierFilter, - BloomFilter escapedIdentifierFilter) : - base(version) - { - _version = version; - _identifierFilter = identifierFilter ?? throw new ArgumentNullException(nameof(identifierFilter)); - _escapedIdentifierFilter = escapedIdentifierFilter ?? throw new ArgumentNullException(nameof(escapedIdentifierFilter)); - } - - /// - /// Returns true when the identifier is probably (but not guaranteed) to be within the - /// syntax tree. Returns false when the identifier is guaranteed to not be within the - /// syntax tree. - /// - public bool ProbablyContainsIdentifier(string identifier) - { - return _identifierFilter.ProbablyContains(identifier); - } - - /// - /// Returns true when the identifier is probably (but not guaranteed) escaped within the - /// text of the syntax tree. Returns false when the identifier is guaranteed to not be - /// escaped within the text of the syntax tree. An identifier that is not escaped within - /// the text can be found by searching the text directly. An identifier that is escaped can - /// only be found by parsing the text and syntactically interpreting any escaping - /// mechanisms found in the language ("\uXXXX" or "@XXXX" in C# or "[XXXX]" in Visual - /// Basic). - /// - public bool ProbablyContainsEscapedIdentifier(string identifier) - { - return _escapedIdentifierFilter.ProbablyContains(identifier); - } - - public override void WriteTo(ObjectWriter writer) - { - _identifierFilter.WriteTo(writer); - _escapedIdentifierFilter.WriteTo(writer); - } - - public static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) - { - return PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); - } - - public static async Task LoadAsync(Document document, CancellationToken cancellationToken) - { - var info = await LoadAsync(document, ReadFrom, s_cache, PersistenceName, SerializationFormat, cancellationToken).ConfigureAwait(false); - return (SyntaxTreeIdentifierInfo)info; - } - - public override Task SaveAsync(Document document, CancellationToken cancellationToken) - { - return SaveAsync(document, s_cache, PersistenceName, SerializationFormat, cancellationToken); - } - - private static SyntaxTreeIdentifierInfo ReadFrom(ObjectReader reader, VersionStamp version) - { - try - { - var identifierFilter = BloomFilter.ReadFrom(reader); - var escapedIdentifierFilter = BloomFilter.ReadFrom(reader); - - return new SyntaxTreeIdentifierInfo(version, identifierFilter, escapedIdentifierFilter); - } - catch (Exception) - { - } - - return null; - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs deleted file mode 100644 index ab1e4aed57f05cd2bcc2245c673df23b798aba87..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIdentifierInfo_Set.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Versions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.FindSymbols -{ - internal partial class SyntaxTreeIdentifierInfo : AbstractSyntaxTreeInfo - { - public static bool TryGetIdentifierLocations(Document document, VersionStamp version, string identifier, List positions, CancellationToken cancellationToken) - { - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) - { - var esentStorage = storage as ISyntaxTreeInfoPersistentStorage; - if (esentStorage == null) - { - // basically, we don't support it. return true so that we don't try to precalculate it - return false; - } - - return esentStorage.ReadIdentifierPositions(document, version, identifier, positions, cancellationToken); - } - } - - public static async Task IdentifierSetPrecalculatedAsync(Document document, CancellationToken cancellationToken) - { - var syntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); - - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) - { - var esentStorage = storage as ISyntaxTreeInfoPersistentStorage; - if (esentStorage == null) - { - // basically, we don't support it. return true so that we don't try to precalculate it - return true; - } - - var persistedVersion = esentStorage.GetIdentifierSetVersion(document); - return document.CanReusePersistedSyntaxTreeVersion(syntaxVersion, persistedVersion); - } - } - - public static async Task SaveIdentifierSetAsync(Document document, CancellationToken cancellationToken) - { - Contract.Requires(document.IsFromPrimaryBranch()); - - var persistentStorageService = document.Project.Solution.Workspace.Services.GetService(); - - using (var storage = persistentStorageService.GetStorage(document.Project.Solution)) - { - var esentStorage = storage as ISyntaxTreeInfoPersistentStorage; - if (esentStorage == null) - { - return; - } - - var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - esentStorage.WriteIdentifierLocations(document, version, root, cancellationToken); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.ContextInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.ContextInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..4e891d933d7580c2310eacaf9a208fb4f31f80ef --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.ContextInfo.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.LanguageServices; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class SyntaxTreeIndex + { + private struct ContextInfo + { + private readonly int _predefinedTypes; + private readonly int _predefinedOperators; + private readonly ContainingNodes _containingNodes; + + public ContextInfo( + int predefinedTypes, + int predefinedOperators, + bool containsForEachStatement, + bool containsLockStatement, + bool containsUsingStatement, + bool containsQueryExpression, + bool containsThisConstructorInitializer, + bool containsBaseConstructorInitializer, + bool containsElementAccessExpression, + bool containsIndexerMemberCref) : + this(predefinedTypes, predefinedOperators, + ConvertToContainingNodeFlag( + containsForEachStatement, + containsLockStatement, + containsUsingStatement, + containsQueryExpression, + containsThisConstructorInitializer, + containsBaseConstructorInitializer, + containsElementAccessExpression, + containsIndexerMemberCref)) + { + } + + private ContextInfo(int predefinedTypes, int predefinedOperators, ContainingNodes containingNodes) + { + _predefinedTypes = predefinedTypes; + _predefinedOperators = predefinedOperators; + _containingNodes = containingNodes; + } + + private static ContainingNodes ConvertToContainingNodeFlag( + bool containsForEachStatement, + bool containsLockStatement, + bool containsUsingStatement, + bool containsQueryExpression, + bool containsThisConstructorInitializer, + bool containsBaseConstructorInitializer, + bool containsElementAccessExpression, + bool containsIndexerMemberCref) + { + var containingNodes = ContainingNodes.None; + + containingNodes = containsForEachStatement ? (containingNodes | ContainingNodes.ContainsForEachStatement) : containingNodes; + containingNodes = containsLockStatement ? (containingNodes | ContainingNodes.ContainsLockStatement) : containingNodes; + containingNodes = containsUsingStatement ? (containingNodes | ContainingNodes.ContainsUsingStatement) : containingNodes; + containingNodes = containsQueryExpression ? (containingNodes | ContainingNodes.ContainsQueryExpression) : containingNodes; + containingNodes = containsThisConstructorInitializer ? (containingNodes | ContainingNodes.ContainsThisConstructorInitializer) : containingNodes; + containingNodes = containsBaseConstructorInitializer ? (containingNodes | ContainingNodes.ContainsBaseConstructorInitializer) : containingNodes; + containingNodes = containsElementAccessExpression ? (containingNodes | ContainingNodes.ContainsElementAccessExpression) : containingNodes; + containingNodes = containsIndexerMemberCref ? (containingNodes | ContainingNodes.ContainsIndexerMemberCref) : containingNodes; + + return containingNodes; + } + + public bool ContainsPredefinedType(PredefinedType type) + => (_predefinedTypes & (int)type) == (int)type; + + public bool ContainsPredefinedOperator(PredefinedOperator op) + => (_predefinedOperators & (int)op) == (int)op; + + public bool ContainsForEachStatement + => (_containingNodes & ContainingNodes.ContainsForEachStatement) == ContainingNodes.ContainsForEachStatement; + + public bool ContainsLockStatement + => (_containingNodes & ContainingNodes.ContainsLockStatement) == ContainingNodes.ContainsLockStatement; + + public bool ContainsUsingStatement + => (_containingNodes & ContainingNodes.ContainsUsingStatement) == ContainingNodes.ContainsUsingStatement; + + public bool ContainsQueryExpression + => (_containingNodes & ContainingNodes.ContainsQueryExpression) == ContainingNodes.ContainsQueryExpression; + + public bool ContainsThisConstructorInitializer + => (_containingNodes & ContainingNodes.ContainsThisConstructorInitializer) == ContainingNodes.ContainsThisConstructorInitializer; + + public bool ContainsBaseConstructorInitializer + => (_containingNodes & ContainingNodes.ContainsBaseConstructorInitializer) == ContainingNodes.ContainsBaseConstructorInitializer; + + public bool ContainsElementAccessExpression + => (_containingNodes & ContainingNodes.ContainsElementAccessExpression) == ContainingNodes.ContainsElementAccessExpression; + + public bool ContainsIndexerMemberCref + => (_containingNodes & ContainingNodes.ContainsIndexerMemberCref) == ContainingNodes.ContainsIndexerMemberCref; + + public void WriteTo(ObjectWriter writer) + { + writer.WriteInt32(_predefinedTypes); + writer.WriteInt32(_predefinedOperators); + writer.WriteInt32((int)_containingNodes); + } + + public static ContextInfo? ReadFrom(ObjectReader reader) + { + try + { + var predefinedTypes = reader.ReadInt32(); + var predefinedOperators = reader.ReadInt32(); + var containingNodes = (ContainingNodes)reader.ReadInt32(); + + return new ContextInfo(predefinedTypes, predefinedOperators, containingNodes); + } + catch (Exception) + { + } + + return null; + } + + [Flags] + private enum ContainingNodes + { + None = 0, + ContainsForEachStatement = 1, + ContainsLockStatement = 1 << 1, + ContainsUsingStatement = 1 << 2, + ContainsQueryExpression = 1 << 3, + ContainsThisConstructorInitializer = 1 << 4, + ContainsBaseConstructorInitializer = 1 << 5, + ContainsElementAccessExpression = 1 << 6, + ContainsIndexerMemberCref = 1 << 7, + } + } + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.DeclarationInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.DeclarationInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..9912251358a96a1c972c75022a7932d29775a9da --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.DeclarationInfo.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class SyntaxTreeIndex + { + private struct DeclarationInfo + { + public ImmutableArray DeclaredSymbolInfos { get; } + + public DeclarationInfo(ImmutableArray declaredSymbolInfos) + { + DeclaredSymbolInfos = declaredSymbolInfos; + } + + public void WriteTo(ObjectWriter writer) + { + writer.WriteInt32(DeclaredSymbolInfos.Length); + foreach (var declaredSymbolInfo in DeclaredSymbolInfos) + { + declaredSymbolInfo.WriteTo(writer); + } + } + + public static DeclarationInfo? ReadFrom(ObjectReader reader) + { + try + { + var declaredSymbolCount = reader.ReadInt32(); + var builder = ImmutableArray.CreateBuilder(declaredSymbolCount); + for (int i = 0; i < declaredSymbolCount; i++) + { + builder.Add(DeclaredSymbolInfo.ReadFrom(reader)); + } + + return new DeclarationInfo(builder.MoveToImmutable()); + } + catch (Exception) + { + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..557c5dbd4b68bcae720ea821f0c1154b7c1963c5 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal partial class SyntaxTreeIndex + { + private struct IdentifierInfo + { + private readonly BloomFilter _identifierFilter; + private readonly BloomFilter _escapedIdentifierFilter; + + public IdentifierInfo( + BloomFilter identifierFilter, + BloomFilter escapedIdentifierFilter) + { + _identifierFilter = identifierFilter ?? throw new ArgumentNullException(nameof(identifierFilter)); + _escapedIdentifierFilter = escapedIdentifierFilter ?? throw new ArgumentNullException(nameof(escapedIdentifierFilter)); + } + + /// + /// Returns true when the identifier is probably (but not guaranteed) to be within the + /// syntax tree. Returns false when the identifier is guaranteed to not be within the + /// syntax tree. + /// + public bool ProbablyContainsIdentifier(string identifier) + => _identifierFilter.ProbablyContains(identifier); + + /// + /// Returns true when the identifier is probably (but not guaranteed) escaped within the + /// text of the syntax tree. Returns false when the identifier is guaranteed to not be + /// escaped within the text of the syntax tree. An identifier that is not escaped within + /// the text can be found by searching the text directly. An identifier that is escaped can + /// only be found by parsing the text and syntactically interpreting any escaping + /// mechanisms found in the language ("\uXXXX" or "@XXXX" in C# or "[XXXX]" in Visual + /// Basic). + /// + public bool ProbablyContainsEscapedIdentifier(string identifier) + => _escapedIdentifierFilter.ProbablyContains(identifier); + + public void WriteTo(ObjectWriter writer) + { + _identifierFilter.WriteTo(writer); + _escapedIdentifierFilter.WriteTo(writer); + } + + public static IdentifierInfo? ReadFrom(ObjectReader reader) + { + try + { + var identifierFilter = BloomFilter.ReadFrom(reader); + var escapedIdentifierFilter = BloomFilter.ReadFrom(reader); + + return new IdentifierInfo(identifierFilter, escapedIdentifierFilter); + } + catch (Exception) + { + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs new file mode 100644 index 0000000000000000000000000000000000000000..e402ed8f08f0567a7974afd9e738e91fea95c9ef --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.FindSymbols +{ + internal sealed partial class SyntaxTreeIndex : AbstractPersistableState, IObjectWritable + { + private readonly VersionStamp _version; + + private readonly IdentifierInfo _identifierInfo; + private readonly ContextInfo _contextInfo; + private readonly DeclarationInfo _declarationInfo; + + private SyntaxTreeIndex( + VersionStamp version, + IdentifierInfo identifierInfo, + ContextInfo contextInfo, + DeclarationInfo declarationInfo) + : base(version) + { + _identifierInfo = identifierInfo; + _contextInfo = contextInfo; + _declarationInfo = declarationInfo; + } + + /// + /// snapshot based cache to guarantee same info is returned without re-calculating for + /// same solution snapshot. + /// + /// since document will be re-created per new solution, this should go away as soon as + /// there is any change on workspace. + /// + private static readonly ConditionalWeakTable s_infoCache + = new ConditionalWeakTable(); + + public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken) + { + Contract.Requires(document.IsFromPrimaryBranch()); + + // we already have information. move on + if (await PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) + { + return; + } + + var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); + await data.SaveAsync(document, cancellationToken).ConfigureAwait(false); + } + + private static async Task GetIndexAsync( + Document document, + ConditionalWeakTable cache, + Func> generator, + CancellationToken cancellationToken) + { + if (cache.TryGetValue(document, out var info)) + { + return info; + } + + info = await generator(document, cancellationToken).ConfigureAwait(false); + if (info != null) + { + return cache.GetValue(document, _ => info); + } + + // alright, we don't have cached information, re-calculate them here. + var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); + + // okay, persist this info + await data.SaveAsync(document, cancellationToken).ConfigureAwait(false); + + return cache.GetValue(document, _ => data); + } + + public static Task GetIndexAsync(Document document, CancellationToken cancellationToken) + => GetIndexAsync(document, s_infoCache, s_loadAsync, cancellationToken); + + private static Func> s_loadAsync = LoadAsync; + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs similarity index 58% rename from src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs rename to src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs index 88aed040177d6777d48b613d64ae8cc9e3648253..6173ffd2a34605c6792cea634d559f63440eed29 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Create.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -13,118 +12,12 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - internal partial class SyntaxTreeInfo + internal sealed partial class SyntaxTreeIndex : AbstractPersistableState, IObjectWritable { - /// - /// snapshot based cache to guarantee same info is returned without re-calculating for same solution snapshot. - /// since document will be re-created per new solution, this should go away as soon as there is any change on workspace. - /// - private static readonly ConditionalWeakTable s_identifierSnapshotCache = new ConditionalWeakTable(); - private static readonly ConditionalWeakTable s_contextSnapshotCache = new ConditionalWeakTable(); - private static readonly ConditionalWeakTable s_declaredSymbolsSnapshotCache = new ConditionalWeakTable(); - - public static async Task PrecalculateAsync(Document document, CancellationToken cancellationToken) - { - Contract.Requires(document.IsFromPrimaryBranch()); - - await PrecalculateBasicInfoAsync(document, cancellationToken).ConfigureAwait(false); - - // for now, don't put identifier locations in esent db - //// await PrecalculateAdvancedInfoAsync(document, cancellationToken).ConfigureAwait(false); - } - - private static async Task PrecalculateAdvancedInfoAsync(Document document, CancellationToken cancellationToken) - { - // we do not support precalculating opened file. - if (document.IsOpen()) - { - return; - } - - // we already have information. move on - if (await SyntaxTreeIdentifierInfo.IdentifierSetPrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) - { - return; - } - - await SyntaxTreeIdentifierInfo.SaveIdentifierSetAsync(document, cancellationToken).ConfigureAwait(false); - } - - private static async Task PrecalculateBasicInfoAsync(Document document, CancellationToken cancellationToken) - { - // we already have information. move on - if (await SyntaxTreeIdentifierInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) && - await SyntaxTreeContextInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false) && - await SyntaxTreeDeclarationInfo.PrecalculatedAsync(document, cancellationToken).ConfigureAwait(false)) - { - return; - } - - var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); - - await data.Item1.SaveAsync(document, cancellationToken).ConfigureAwait(false); - await data.Item2.SaveAsync(document, cancellationToken).ConfigureAwait(false); - await data.Item3.SaveAsync(document, cancellationToken).ConfigureAwait(false); - } - - private static async Task GetInfoAsync( - Document document, - ConditionalWeakTable cache, - Func> generator, - Func<(SyntaxTreeIdentifierInfo identifierInfo, SyntaxTreeContextInfo contextInfo, SyntaxTreeDeclarationInfo declarationInfo), T> selector, - CancellationToken cancellationToken) - where T : class - { - if (cache.TryGetValue(document, out var info)) - { - return info; - } - - info = await generator(document, cancellationToken).ConfigureAwait(false); - if (info != null) - { - return cache.GetValue(document, _ => info); - } - - // alright, we don't have cached information, re-calculate them here. - var data = await CreateInfoAsync(document, cancellationToken).ConfigureAwait(false); - - // okay, persist this info - await data.identifierInfo.SaveAsync(document, cancellationToken).ConfigureAwait(false); - await data.contextInfo.SaveAsync(document, cancellationToken).ConfigureAwait(false); - await data.declarationInfo.SaveAsync(document, cancellationToken).ConfigureAwait(false); - - info = selector(data); - return cache.GetValue(document, _ => info); - } - - public static Task GetContextInfoAsync(Document document, CancellationToken cancellationToken) - { - return GetInfoAsync(document, s_contextSnapshotCache, SyntaxTreeContextInfo.LoadAsync, tuple => tuple.Item2, cancellationToken); - } - - public static Task GetIdentifierInfoAsync(Document document, CancellationToken cancellationToken) - { - return GetInfoAsync(document, s_identifierSnapshotCache, SyntaxTreeIdentifierInfo.LoadAsync, tuple => tuple.Item1, cancellationToken); - } - - private static Func> s_loadAsync - = SyntaxTreeDeclarationInfo.LoadAsync; - private static Func<(SyntaxTreeIdentifierInfo identifierInfo, SyntaxTreeContextInfo contextInfo, SyntaxTreeDeclarationInfo declarationInfo), SyntaxTreeDeclarationInfo> s_getThirdItem - = tuple => tuple.Item3; - - public static Task GetDeclarationInfoAsync( - Document document, CancellationToken cancellationToken) - { - return GetInfoAsync( - document, s_declaredSymbolsSnapshotCache, s_loadAsync, - s_getThirdItem, cancellationToken); - } - // The probability of getting a false positive when calling ContainsIdentifier. private const double FalsePositiveProbability = 0.0001; - private static async Task<(SyntaxTreeIdentifierInfo identifierInfo, SyntaxTreeContextInfo contextInfo, SyntaxTreeDeclarationInfo declarationInfo)> CreateInfoAsync(Document document, CancellationToken cancellationToken) + private static async Task CreateInfoAsync(Document document, CancellationToken cancellationToken) { var syntaxFacts = document.GetLanguageService(); var ignoreCase = syntaxFacts != null && !syntaxFacts.IsCaseSensitive; @@ -223,12 +116,12 @@ private static async Task<(SyntaxTreeIdentifierInfo identifierInfo, SyntaxTreeCo var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - return (new SyntaxTreeIdentifierInfo( - version, - new BloomFilter(FalsePositiveProbability, isCaseSensitive, identifiers), - new BloomFilter(FalsePositiveProbability, isCaseSensitive, escapedIdentifiers)), - new SyntaxTreeContextInfo( - version, + return new SyntaxTreeIndex( + version, + new IdentifierInfo( + new BloomFilter(FalsePositiveProbability, isCaseSensitive, identifiers), + new BloomFilter(FalsePositiveProbability, isCaseSensitive, escapedIdentifiers)), + new ContextInfo( predefinedTypes, predefinedOperators, containsForEachStatement, @@ -239,8 +132,7 @@ private static async Task<(SyntaxTreeIdentifierInfo identifierInfo, SyntaxTreeCo containsBaseConstructorInitializer, containsElementAccess, containsIndexerMemberCref), - new SyntaxTreeDeclarationInfo( - version, + new DeclarationInfo( declaredSymbolInfos.ToImmutableAndFree())); } finally @@ -287,4 +179,4 @@ private static void Free(bool ignoreCase, HashSet identifiers, HashSet DeclaredSymbolInfos => _declarationInfo.DeclaredSymbolInfos; + + public bool ProbablyContainsIdentifier(string identifier) => _identifierInfo.ProbablyContainsIdentifier(identifier); + public bool ProbablyContainsEscapedIdentifier(string identifier) => _identifierInfo.ProbablyContainsEscapedIdentifier(identifier); + + public bool ContainsPredefinedType(PredefinedType type) => _contextInfo.ContainsPredefinedType(type); + public bool ContainsPredefinedOperator(PredefinedOperator op) => _contextInfo.ContainsPredefinedOperator(op); + + public bool ContainsForEachStatement => _contextInfo.ContainsForEachStatement; + public bool ContainsLockStatement => _contextInfo.ContainsLockStatement; + public bool ContainsUsingStatement => _contextInfo.ContainsUsingStatement; + public bool ContainsQueryExpression => _contextInfo.ContainsQueryExpression; + public bool ContainsThisConstructorInitializer => _contextInfo.ContainsThisConstructorInitializer; + public bool ContainsBaseConstructorInitializer => _contextInfo.ContainsBaseConstructorInitializer; + public bool ContainsElementAccessExpression => _contextInfo.ContainsElementAccessExpression; + public bool ContainsIndexerMemberCref => _contextInfo.ContainsIndexerMemberCref; + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs similarity index 56% rename from src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs rename to src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs index a915765c5869381d3594f9c50ee8b106f68ae8a8..edc22d388e2792a3ea06481f67de78e596b8493d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/AbstractSyntaxTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs @@ -9,20 +9,47 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - internal abstract class AbstractSyntaxTreeInfo : AbstractPersistableState, IObjectWritable + internal sealed partial class SyntaxTreeIndex : AbstractPersistableState, IObjectWritable { - protected AbstractSyntaxTreeInfo(VersionStamp version) - : base(version) + private const string PersistenceName = ""; + private const string SerializationFormat = "1"; + + /// + /// in memory cache will hold onto any info related to opened documents in primary branch or all documents in forked branch + /// + /// this is not snapshot based so multiple versions of snapshots can re-use same data as long as it is relevant. + /// + private static readonly ConditionalWeakTable> s_cache = + new ConditionalWeakTable>(); + + public void WriteTo(ObjectWriter writer) { + _identifierInfo.WriteTo(writer); + _contextInfo.WriteTo(writer); + _declarationInfo.WriteTo(writer); } - public abstract void WriteTo(ObjectWriter writer); + private static SyntaxTreeIndex ReadFrom(ObjectReader reader, VersionStamp version) + { + var identifierInfo = IdentifierInfo.ReadFrom(reader); + var contextInfo = ContextInfo.ReadFrom(reader); + var declarationInfo = DeclarationInfo.ReadFrom(reader); - public abstract Task SaveAsync(Document document, CancellationToken cancellationToken); + if (identifierInfo == null || contextInfo == null || declarationInfo == null) + { + return null; + } + + return new SyntaxTreeIndex( + version, identifierInfo.Value, contextInfo.Value, declarationInfo.Value); + } - protected async Task SaveAsync( + private Task SaveAsync(Document document, CancellationToken cancellationToken) + => SaveAsync(document, s_cache, PersistenceName, SerializationFormat, cancellationToken); + + private async Task SaveAsync( Document document, - ConditionalWeakTable> cache, + ConditionalWeakTable> cache, string persistenceName, string serializationFormat, CancellationToken cancellationToken) @@ -50,20 +77,23 @@ protected AbstractSyntaxTreeInfo(VersionStamp version) return persisted; } - protected static async Task LoadAsync( + private static Task LoadAsync(Document document, CancellationToken cancellationToken) + => LoadAsync(document, ReadFrom, s_cache, PersistenceName, SerializationFormat, cancellationToken); + + private static async Task LoadAsync( Document document, - Func reader, - ConditionalWeakTable> cache, + Func reader, + ConditionalWeakTable> cache, string persistenceName, string serializationFormat, CancellationToken cancellationToken) { - var infoTable = cache.GetValue(document.Project.Solution.BranchId, _ => new ConditionalWeakTable()); + var infoTable = cache.GetValue( + document.Project.Solution.BranchId, + _ => new ConditionalWeakTable()); var version = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); - // first look to see if we already have the info in the cache - AbstractSyntaxTreeInfo info; - if (infoTable.TryGetValue(document.Id, out info) && info.Version == version) + if (infoTable.TryGetValue(document.Id, out var info) && info.Version == version) { return info; } @@ -72,7 +102,9 @@ protected AbstractSyntaxTreeInfo(VersionStamp version) infoTable.Remove(document.Id); // check primary cache to see whether we have valid info there - var primaryInfoTable = cache.GetValue(document.Project.Solution.Workspace.PrimaryBranchId, _ => new ConditionalWeakTable()); + var primaryInfoTable = cache.GetValue( + document.Project.Solution.Workspace.PrimaryBranchId, + _ => new ConditionalWeakTable()); if (primaryInfoTable.TryGetValue(document.Id, out info) && info.Version == version) { return info; @@ -92,10 +124,13 @@ protected AbstractSyntaxTreeInfo(VersionStamp version) return null; } - private static ConditionalWeakTable GetInfoTable( + private static Task PrecalculatedAsync(Document document, CancellationToken cancellationToken) + => PrecalculatedAsync(document, PersistenceName, SerializationFormat, cancellationToken); + + private static ConditionalWeakTable GetInfoTable( BranchId branchId, Workspace workspace, - ConditionalWeakTable> cache) + ConditionalWeakTable> cache) { return cache.GetValue(branchId, id => { @@ -108,8 +143,7 @@ protected AbstractSyntaxTreeInfo(VersionStamp version) return; } - ConditionalWeakTable infoTable; - if (cache.TryGetValue(e.Document.Project.Solution.BranchId, out infoTable)) + if (cache.TryGetValue(e.Document.Project.Solution.BranchId, out var infoTable)) { // remove closed document from primary branch from live cache. infoTable.Remove(e.Document.Id); @@ -117,8 +151,8 @@ protected AbstractSyntaxTreeInfo(VersionStamp version) }; } - return new ConditionalWeakTable(); + return new ConditionalWeakTable(); }); } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index 378dec43185fb98311903929eb8c61c3b055726a..97cb6fac1e94e9a518d6c47f1fbe8a4e76f5f97b 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -682,7 +682,7 @@ private async Task AddDocumentsWithPotentialConflicts(IEnumerable docu continue; } - var info = await SyntaxTreeInfo.GetIdentifierInfoAsync(document, CancellationToken.None).ConfigureAwait(false); + var info = await SyntaxTreeIndex.GetIndexAsync(document, CancellationToken.None).ConfigureAwait(false); if (info.ProbablyContainsEscapedIdentifier(_originalText)) { _documentsIdsToBeCheckedForConflict.Add(document.Id); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 01627b532e5505544d9c29178d91d0b5041effe7..869e9c0a8a2508da154ce612722098ee5f4c5521 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -170,10 +170,8 @@ public static async Task IsForkedDocumentWithSyntaxChangesAsync(this Docum } } - public static async Task GetDeclarationInfoAsync(this Document document, CancellationToken cancellationToken) - { - return await SyntaxTreeInfo.GetDeclarationInfoAsync(document, cancellationToken).ConfigureAwait(false); - } + public static Task GetSyntaxTreeIndexAsync(this Document document, CancellationToken cancellationToken) + => SyntaxTreeIndex.GetIndexAsync(document, cancellationToken); /// /// Returns the semantic model for this document that may be produced from partial semantics. The semantic model diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs b/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs index 6bd1c8f7fa4c4745268e42d91c0ec9325fa3f010..bd795248c38ea1271464a7874f5a1da04633faa9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs @@ -37,7 +37,7 @@ public static MefHostServices Create(IEnumerable ass throw new ArgumentNullException(nameof(assemblies)); } - var compositionConfiguration = new ContainerConfiguration().WithAssemblies(assemblies); + var compositionConfiguration = new ContainerConfiguration().WithAssemblies(assemblies.Distinct()); var container = compositionConfiguration.CreateContainer(); return new MefHostServices(container); } @@ -99,11 +99,15 @@ public static ImmutableArray DefaultAssemblies private static ImmutableArray LoadDefaultAssemblies() { // build a MEF composition using the main workspaces assemblies and the known VisualBasic/CSharp workspace assemblies. + // updated: includes feature assemblies since they now have public API's. var assemblyNames = new string[] { "Microsoft.CodeAnalysis.Workspaces", "Microsoft.CodeAnalysis.CSharp.Workspaces", "Microsoft.CodeAnalysis.VisualBasic.Workspaces", + "Microsoft.CodeAnalysis.Features", + "Microsoft.CodeAnalysis.CSharp.Features", + "Microsoft.CodeAnalysis.VisualBasic.Features" }; return LoadNearbyAssemblies(assemblyNames); diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 858c0b8eb6fec7465629a779f2e4fe5aa6987e3c..5adf5ced627ba29eca590fae4c9d1045ed3cfd9c 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -361,6 +361,13 @@ + + + + + + + @@ -395,7 +402,6 @@ - @@ -530,8 +536,6 @@ - - @@ -644,11 +648,6 @@ - - - - -