diff --git a/.config/branch-merge.json b/.config/branch-merge.json new file mode 100644 index 0000000000000..0b1db28cc334a --- /dev/null +++ b/.config/branch-merge.json @@ -0,0 +1,9 @@ +{ + "merge-flow-configurations": { + // Merge any main changes to release/dev18.0. + "main": { + "MergeToBranch": "release/dev18.0", + "ExtraSwitches": "-QuietComments" + } + } +} \ No newline at end of file diff --git a/.github/workflows/main-merge.yml b/.github/workflows/main-merge.yml new file mode 100644 index 0000000000000..21dfaefe4cd4c --- /dev/null +++ b/.github/workflows/main-merge.yml @@ -0,0 +1,18 @@ +# Merges any changes from release/prerelease to main (e.g. servicing changes) + +name: Flow main to release/dev18.0 +on: + schedule: + # once a day at 13:00 UTC to cleanup old runs + - cron: '0 13 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + check-script: + uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main + with: + configuration_file_path: '.config/branch-merge.json' \ No newline at end of file diff --git a/Roslyn.sln b/Roslyn.sln index c49637445381e..34d550842ddc4 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -535,7 +535,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Exte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost", "src\Workspaces\MSBuild\BuildHost\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj", "{B1481D94-682E-46EC-ADBE-A16EB46FEEE9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Copilot", "src\EditorFeatures\ExternalAccess\Copilot\Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj", "{5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Copilot", "src\Features\ExternalAccess\Copilot\Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj", "{5D60CF30-28A9-9F0F-7610-D90E4A69AE73}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator", "src\VisualStudio\ExternalAccess\EditorConfigGenerator\Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator.csproj", "{09AEDEE4-6358-47C9-8022-3BD37A518070}" EndProject @@ -569,6 +569,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExternalAccess", "ExternalA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities", "src\LanguageServer\Protocol.TestUtilities\Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj", "{7465CE63-A7B7-475F-8C57-48D2F8BC665A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot", "src\LanguageServer\ExternalAccess\Copilot\Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.csproj", "{900168D7-D982-86CE-5097-C5F173BA4D8B}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Threading", "src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.shproj", "{967723E8-4FDD-447B-99F6-4F8C47CB5433}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Threading.Package", "src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.Package.csproj", "{2559DAF9-784E-4C29-E0E1-70204B1FD56E}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Contracts", "src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.shproj", "{BD974609-C68B-4BE6-9682-EB132462B50D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Contracts.Package", "src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.Package.csproj", "{A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1355,10 +1365,6 @@ Global {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.Build.0 = Release|Any CPU - {5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD}.Release|Any CPU.Build.0 = Release|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Debug|Any CPU.Build.0 = Debug|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1399,6 +1405,22 @@ Global {7465CE63-A7B7-475F-8C57-48D2F8BC665A}.Debug|Any CPU.Build.0 = Debug|Any CPU {7465CE63-A7B7-475F-8C57-48D2F8BC665A}.Release|Any CPU.ActiveCfg = Release|Any CPU {7465CE63-A7B7-475F-8C57-48D2F8BC665A}.Release|Any CPU.Build.0 = Release|Any CPU + {900168D7-D982-86CE-5097-C5F173BA4D8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {900168D7-D982-86CE-5097-C5F173BA4D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {900168D7-D982-86CE-5097-C5F173BA4D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {900168D7-D982-86CE-5097-C5F173BA4D8B}.Release|Any CPU.Build.0 = Release|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.Build.0 = Release|Any CPU + {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Release|Any CPU.Build.0 = Release|Any CPU + {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1644,7 +1666,6 @@ Global {A833B11C-5072-4A1F-A32B-2700433B0D3D} = {806F0C6F-3640-4C92-8D55-6767B1535467} {8988270E-393A-4B92-AC1A-534F903CFD34} = {8977A560-45C2-4EC2-A849-97335B382C74} {B1481D94-682E-46EC-ADBE-A16EB46FEEE9} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} - {5E8FB6D6-6C5C-42E6-9220-1EAA7ED9BCAD} = {1AE9182D-B03E-4B00-B32E-37AE01715F57} {09AEDEE4-6358-47C9-8022-3BD37A518070} = {5880FECB-91F1-4AB8-8726-75EAFA8A918E} {5BABC440-4F1B-46E8-9068-DD7F02ED25D3} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} {5762E483-75CE-4328-A410-511F30737712} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} @@ -1661,6 +1682,12 @@ Global {1AE9182D-B03E-4B00-B32E-37AE01715F57} = {EE97CB90-33BB-4F3A-9B3D-69375DEC6AC6} {806F0C6F-3640-4C92-8D55-6767B1535467} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {7465CE63-A7B7-475F-8C57-48D2F8BC665A} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} + {900168D7-D982-86CE-5097-C5F173BA4D8B} = {806F0C6F-3640-4C92-8D55-6767B1535467} + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73} = {58A2876A-618D-4AE6-A136-E44B42BBDE11} + {967723E8-4FDD-447B-99F6-4F8C47CB5433} = {C2D1346B-9665-4150-B644-075CF1636BAA} + {2559DAF9-784E-4C29-E0E1-70204B1FD56E} = {C2D1346B-9665-4150-B644-075CF1636BAA} + {BD974609-C68B-4BE6-9682-EB132462B50D} = {C2D1346B-9665-4150-B644-075CF1636BAA} + {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA} = {C2D1346B-9665-4150-B644-075CF1636BAA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} @@ -1669,25 +1696,34 @@ Global src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 src\Compilers\CSharp\csc\CscCommandLine.projitems*{0161e25c-918a-4dc8-9648-30fdcc8e31e9}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{0c2e1633-1462-4712-88f4-a0c945bad3a8}*SharedItemsImports = 5 src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{1b6c4a1a-413b-41fb-9f85-5c09118e541b}*SharedItemsImports = 13 src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{2523d0e6-df32-4a3e-8ae0-a19bffae2ef6}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{2559daf9-784e-4c29-e0e1-70204b1fd56e}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{2559daf9-784e-4c29-e0e1-70204b1fd56e}*SharedItemsImports = 5 + src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{2559daf9-784e-4c29-e0e1-70204b1fd56e}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{2801f82b-78ce-4bae-b06f-537574751e2e}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e35}*SharedItemsImports = 13 src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e36}*SharedItemsImports = 13 src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{41ed1bfa-fdad-4fe4-8118-db23fb49b0b0}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{438db8af-f3f0-4ed9-80b5-13fddd5b8787}*SharedItemsImports = 13 src\Compilers\CSharp\csc\CscCommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{5018d049-5870-465a-889b-c742ce1e31cb}*SharedItemsImports = 5 @@ -1696,7 +1732,9 @@ Global src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{58969243-7f59-4236-93d0-c93b81f569b3}*SharedItemsImports = 13 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 @@ -1705,12 +1743,18 @@ Global src\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{64eaded3-4b5d-4431-bbe5-a4aba1c38c00}*SharedItemsImports = 13 src\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{686bf57e-a6ff-467b-aab3-44de916a9772}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{6fc8e6f5-659c-424d-aeb5-331b95883e29}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{7ad4fe65-9a30-41a6-8004-aa8f89bcb7f3}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 src\Compilers\VisualBasic\vbc\VbcCommandLine.projitems*{810b02ad-2ea5-4422-88ac-b71b8ab0df0b}*SharedItemsImports = 13 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 + src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{94faf461-2e74-4dbb-9813-6b2cde6f1880}*SharedItemsImports = 13 src\Compilers\Server\VBCSCompiler\VBCSCompilerCommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 + src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{967723e8-4fdd-447b-99f6-4f8c47cb5433}*SharedItemsImports = 13 src\Compilers\VisualBasic\vbc\VbcCommandLine.projitems*{975cd834-45f4-4ea0-a395-cb60dbd0e214}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{99f594b1-3916-471d-a761-a6731fc50e9a}*SharedItemsImports = 13 src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{9f9ccc78-7487-4127-9d46-db23e501f001}*SharedItemsImports = 13 @@ -1721,21 +1765,26 @@ Global src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{abdbac1e-350e-4dc3-bb45-3504404545ee}*SharedItemsImports = 5 src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{abdbac1e-350e-4dc3-bb45-3504404545ee}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{ace53515-482c-4c6a-e2d2-4242a687dfee}*SharedItemsImports = 5 src\Compilers\CSharp\csc\CscCommandLine.projitems*{b021ccbc-b2af-4560-af28-ed055f0ed696}*SharedItemsImports = 13 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{b1481d94-682e-46ec-adbe-a16eb46feee9}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{b1481d94-682e-46ec-adbe-a16eb46feee9}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{b1481d94-682e-46ec-adbe-a16eb46feee9}*SharedItemsImports = 5 src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 5 src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{bb3ca047-5d00-48d4-b7d3-233c1265c065}*SharedItemsImports = 13 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{bd9539eb-aa5e-4e67-ac7f-97d7cbc4d0c9}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{bd974609-c68b-4be6-9682-eb132462b50d}*SharedItemsImports = 13 src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{bf9dac1e-3a5e-4dc3-bb44-9a64e0d4e9d4}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{c1930979-c824-496b-a630-70f5369a636f}*SharedItemsImports = 13 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{cec0dce7-8d52-45c3-9295-fc7b16bd2451}*SharedItemsImports = 13 src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{d0bc9be7-24f6-40ca-8dc6-fcb93bd44b34}*SharedItemsImports = 13 - src\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework.Shared\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{d2589bce-4f2e-4113-b7e7-37392c0c5492}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{d2589bce-4f2e-4113-b7e7-37392c0c5492}*SharedItemsImports = 5 src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{d73adf7d-2c1c-42ae-b2ab-edc9497e4b71}*SharedItemsImports = 13 src\Compilers\Server\VBCSCompiler\VBCSCompilerCommandLine.projitems*{d8ef0777-9d65-4849-a7d6-ac81e58e2317}*SharedItemsImports = 13 src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{da973826-c985-4128-9948-0b445e638bdb}*SharedItemsImports = 13 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{db96c25f-39a9-4a6a-92bc-d1e42717308f}*SharedItemsImports = 5 src\Compilers\Server\VBCSCompiler\VBCSCompilerCommandLine.projitems*{dc8c78cc-b6fe-47bf-93b1-b65a1c67c08d}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{e512c6c1-f085-4ad7-b0d9-e8f1a0a2a510}*SharedItemsImports = 5 src\Compilers\VisualBasic\vbc\VbcCommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 @@ -1749,6 +1798,8 @@ Global src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 5 src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{fce88bbd-9bbd-4871-b9b0-de176d73a6b0}*SharedItemsImports = 5 EndGlobalSection EndGlobal diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index d2a8322ffaa92..dbdef3f0058d7 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -5,6 +5,7 @@ trigger: - main-vs-deps - release/dev16.*-vs-deps - release/dev17.* + - release/dev18.* - features/lsp_tools_host exclude: - release/dev17.0 @@ -16,6 +17,11 @@ resources: type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + pipelines: + - pipeline: profilingInputs + source: dotnet-vscode-csharp-profiling + branch: main + trigger: none parameters: - name: IbcDrop @@ -89,6 +95,9 @@ variables: - ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}: - name: enableSourceIndex value: true + - name: VSCodeOptimizationDataRoot + value: $(Pipeline.Workspace)/profilingInputs/merged mibc + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates @@ -273,6 +282,10 @@ extends: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json condition: and(succeeded(), in(variables['SignType'], 'test', 'real')) + - download: profilingInputs + artifact: merged mibc + displayName: Download VSCode optimization inputs + - task: PowerShell@2 displayName: Build inputs: @@ -294,6 +307,7 @@ extends: -officialVisualStudioDropAccessToken $(_DevDivDropAccessToken) /p:RepositoryName=$(Build.Repository.Name) /p:VisualStudioDropName=$(VisualStudio.DropName) + /p:VSCodeOptimizationDataRoot="$(VSCodeOptimizationDataRoot)" /p:DotNetSignType=$(SignType) /p:DotnetPublishUsingPipelines=true /p:IgnoreIbcMergeErrors=true diff --git a/azure-pipelines-pr-validation.yml b/azure-pipelines-pr-validation.yml index 66c0b4cd7a32c..d779e1a90d00a 100644 --- a/azure-pipelines-pr-validation.yml +++ b/azure-pipelines-pr-validation.yml @@ -143,6 +143,9 @@ extends: ArtifactName: AssetManifests steps: + - pwsh: Set-MpPreference -DisableRealtimeMonitoring $true + displayName: Disable Real-time Monitoring + - task: Powershell@2 displayName: Setting OriginalBuildNumber variable condition: succeeded() diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 7b2fa79411cce..a35439764e167 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -19,7 +19,7 @@ efforts behind them. | [`field` keyword in properties](https://github.com/dotnet/csharplang/issues/140) | [field-keyword](https://github.com/dotnet/roslyn/tree/features/field-keyword) | [Merged into 17.12p3](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313), [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [First-class Span Types](https://github.com/dotnet/csharplang/issues/7905) | [FirstClassSpan](https://github.com/dotnet/roslyn/tree/features/FirstClassSpan) | [Merged into 17.13p1](https://github.com/dotnet/roslyn/issues/73445) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [333fred](https://github.com/333fred) | | [333fred](https://github.com/333fred), [stephentoub](https://github.com/stephentoub) | | [Unbound generic types in `nameof`](https://github.com/dotnet/csharplang/issues/8480) | [PR](https://github.com/dotnet/roslyn/pull/75368) | [Merged into 17.13p2](https://github.com/dotnet/roslyn/pull/75368) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [AlekseyTs](https://github.com/AlekseyTs) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [String literals in data section as UTF8](https://github.com/dotnet/roslyn/blob/main/docs/features/string-literals-data-section.md) | [PR](https://github.com/dotnet/roslyn/pull/76036) | [Merged into 17.14p1](https://github.com/dotnet/roslyn/issues/76234) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | N/A | N/A | +| [String literals in data section as UTF8](https://github.com/dotnet/roslyn/blob/main/docs/features/string-literals-data-section.md) | [PR](https://github.com/dotnet/roslyn/pull/76036) | [Merged into 17.13p4](https://github.com/dotnet/roslyn/issues/76234) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | N/A | N/A | | [Simple lambda parameters with modifiers](https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md) | [PR](https://github.com/dotnet/roslyn/pull/75400) | [Merged into 17.14p1](https://github.com/dotnet/roslyn/pull/75400) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jjonescz](https://github.com/jjonescz), [cston](https://github.com/cston) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | # Working Set VB diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md index 6075637771e03..da3ecdd5166a7 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md @@ -291,3 +291,26 @@ unsafe record struct R( public bool Equals(R other) => true; } ``` + +## Emitting metadata-only executables requires an entrypoint + +***Introduced in Visual Studio 2022 version 17.14*** + +Previously, the entrypoint was [unintentionally unset](https://github.com/dotnet/roslyn/issues/76707) +when emitting executables in metadata-only mode (also known as ref assemblies). +That is now corrected but it also means that a missing entrypoint is a compilation error: + +```cs +// previously successful, now fails: +CSharpCompilation.Create("test").Emit(new MemoryStream(), + options: EmitOptions.Default.WithEmitMetadataOnly(true)) + +CSharpCompilation.Create("test", + // workaround - mark as DLL instead of EXE (the default): + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .Emit(new MemoryStream(), + options: EmitOptions.Default.WithEmitMetadataOnly(true)) +``` + +Similarly this can be observed when using the command-line argument `/refonly` +or the `ProduceOnlyReferenceAssembly` MSBuild property. diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md index 470853cd3c8f8..cbf8984cc876c 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md @@ -35,7 +35,7 @@ public class C ***Introduced in Visual Studio 2022 version 17.10*** *Conversion* of a collection expression to a `struct` or `class` that implements `System.Collections.IEnumerable` and *does not* have a strongly-typed `GetEnumerator()` -requires the elements in the collection expression are implicitly convertible to the `object`. +requires that the elements in the collection expression are implicitly convertible to `object`. Previously, the elements of a collection expression targeting an `IEnumerable` implementation were assumed to be convertible to `object`, and converted only when binding to the applicable `Add` method. This additional requirement means that collection expression conversions to `IEnumerable` implementations are treated consistently with other target types where the elements in the collection expression must be implicitly convertible to the *iteration type* of the target type. diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md index ce0ac3ee11fe5..3ebc24cd2fe0a 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md @@ -157,3 +157,30 @@ class C ``` A workaround is to use explicit delegate types instead of relying on `var` inference in those cases. + +## `dotnet_style_require_accessibility_modifiers` now consistently applies to interface members + +PR: https://github.com/dotnet/roslyn/pull/76324 + +Prior to this change, the analyzer for dotnet_style_require_accessibility_modifiers would simply ignore interface +members. This was because C# initially disallowed modifiers for interface members entirely, having them always +be public. + +Later versions of the language relaxed this restriction, allowing users to provide accessibility modifiers on +interface members, including a redundant `public` modifier. + +The analyzer was updated to now enforce the value for this option on interface members as well. The meaning +of the value is as follows: + +1. `never`. The analyzer does no analysis. Redundant modifiers are allowed on all members. +2. `always`. Redundant modifiers are always required on all members (including interface members). For example: + a `private` modifier on a class member, and a `public` modifier on an interface member. This is the option to + use if you feel that all members no matter what should state their accessibility explicitly. +4. `for_non_interface_members`. Redundant modifiers are required on all members *that are not* part of an interface, + but disallowed for interface members. For example: `private` will be required on private class members. However, + a public interface member will not be allowed to have redundant `public` modifiers. This matches the standard + modifier approach present prior to the language allowing modifiers on interface members. +5. `omit_if_default`. Redundant modifiers are disallowed. For example a private class member will be disallowed from + using `private`, and a public interface member will be disallowed from using `public`. This is the option to use + if you feel that restating the accessibility when it matches what the language chooses by default is redundant and + should be disalloed. diff --git a/docs/compilers/CSharp/Runtime Async Design.md b/docs/compilers/CSharp/Runtime Async Design.md index e577604505e9f..f7dc760b13a78 100644 --- a/docs/compilers/CSharp/Runtime Async Design.md +++ b/docs/compilers/CSharp/Runtime Async Design.md @@ -27,20 +27,33 @@ We use the following helper APIs to indicate suspension points to the runtime, i ```cs namespace System.Runtime.CompilerServices; -// These methods are used to await things that cannot use runtime async signature form // TODO: Clarify which of these should be preferred? Should we always emit the `Unsafe` version when awaiting something that implements `ICriticalNotifyCompletion`? namespace System.Runtime.CompilerServices; public static class RuntimeHelpers { - [RuntimeAsyncMethod] - public static Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion; - [RuntimeAsyncMethod] - public static Task UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; + // These methods are used to await things that cannot use the Await helpers below + [MethodImpl(MethodImplOptions.Async)] + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion; + [MethodImpl(MethodImplOptions.Async)] + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; + + // These methods are used to directly await method calls + [MethodImpl(MethodImplOptions.Async)] + public static void Await(Task task); + [MethodImpl(MethodImplOptions.Async)] + public static T Await(Task task); + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ValueTask task); + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ValueTask task); } ``` -We presume the following `MethodImplOptions` bit is present. This is used to indicate to the JIT that it should generate an async state machine for the method. +We presume the following `MethodImplOptions` bit is present. This is used to indicate to the JIT that it should generate an async state machine for the method. This bit is not allowed to be used manually on any method; it is added by the compiler +to an `async` method. + +TODO: We may want to block directly calling `MethodImplOptions.Async` methods with non-`Task`/`ValueTask` return types. ```cs namespace System.Runtime.CompilerServices; @@ -77,6 +90,8 @@ public class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribut As mentioned previously, we try to expose as little of this to initial binding as possible. The one major exception to this is our handling of the `MethodImplOption.Async`; we do not let this be applied to user code, and will issue an error if a user tries to do this by hand. +TODO: We may need special handling for the implementation of the `RuntimeHelpers.Await` methods in corelib to permit usage of `MethodImplOptions.Async` directly, as they will not be `async` as we think of it in C#. + Compiler generated async state machines and runtime generated async share some of the same building blocks. Both need to have `await`s with in `catch` and `finally` blocks rewritten to pend the exceptions, perform the `await` outside of the `catch`/`finally` region, and then have the exceptions restored as necessary. @@ -121,7 +136,12 @@ for given scenarios are elaborated in more detail below. TODO: Async iterators (returning `IAsyncEnumerable`) -#### Await `Task`-returning method +#### `Task`, `Task`, `ValueTask`, `ValueTask` Scenarios + +For any lvalue of one of these types, we'll generally rewrite `await expr` into `System.Runtime.CompilerServices.RuntimeHelpers.Await(expr)`. A number of different example scenarios for this are covered below. The +main interesting deviations are when `struct` rvalues need to be hoisted across an `await`, and exception handling rewriting. + +##### Await `Task`-returning method ```cs class C @@ -132,8 +152,15 @@ class C await C.M(); ``` +Translated C#: + +```cs +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() +call [System.Runtime]System.Threading.Tasks.Task C::M() +call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) ``` --------------------------- @@ -148,12 +175,23 @@ class C } ``` +Translated C#: + +```cs +var c = new C(); +System.Runtime.CompilerServices.RuntimeHelpers.Await(c.M()); +``` + ```il newobj instance void C::.ctor() -callvirt instance modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() +callvirt instance class [System.Runtime]System.Threading.Tasks.Task C::M() +call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) ``` -#### Await a concrete `T` `Task`-returning method +
+Extended examples of further variations on the simple `await expr` scenario + +##### Await a concrete `T` `Task`-returning method ```cs int i = await C.M(); @@ -164,8 +202,15 @@ class C } ``` +Translated C#: + +```cs +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -call modreq(class [System.Runtime]System.Threading.Tasks.Task`1) int32 C::M() +call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() +call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) stloc.0 ``` @@ -181,13 +226,21 @@ class C } ``` +Translated C#: + +```cs +var c = new C(); +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(c.M()); +``` + ```il newobj instance void C::.ctor() -callvirt instance modreq(class [System.Runtime]System.Threading.Tasks.Task`1) int32 C::M() +callvirt instance class [System.Runtime]System.Threading.Tasks.Task`1 C::M() +call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) stloc.0 ``` -#### Await local of type `Task` +##### Await local of type `Task` ```cs var local = M(); @@ -203,14 +256,7 @@ Translated C#: ```cs var local = C.M(); -{ - var awaiter = local.GetAwaiter(); - if (!awaiter.IsComplete) - { - /* Runtime-Async Call */ System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); - } - awaiter.GetResult() -} +System.Runtime.CompilerServices.RuntimeHelpers.Await(local); ``` ```il @@ -220,26 +266,14 @@ var local = C.M(); ) IL_0000: call class [System.Runtime]System.Threading.Tasks.Task C::M() - IL_0005: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter [System.Runtime]System.Threading.Tasks.Task::GetAwaiter() - IL_000a: stloc.0 - IL_000b: ldloca.s 0 - IL_000d: call instance bool [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted() - IL_0012: brtrue.s IL_001b - - IL_0014: ldloc.0 - IL_0015: call class [System.Runtime]System.Threading.Tasks.Task System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync(!!0) - IL_001a: pop - - IL_001b: ldloca.s 0 - IL_001d: call instance void [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::GetResult() - IL_0022: ret + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_000c: ret } ``` -#### Await local of concrete type `Task` - -This strategy will also be used for `Task`-like types that are not `Task`, `ValueTask`, `Task`, or `ValueTask`, both in value form, and in direct method call form. We will use either `AwaitAwaiterFromRuntimeAsync` or -`UnsafeAwaitAwaiterFromRuntimeAsync`, depending on the interface implemented by the custom awaitable. +##### Await local of concrete type `Task` ```cs var local = M(); @@ -255,42 +289,26 @@ Translated C#: ```cs var local = C.M(); -var i = -{ - var awaiter = local.GetAwaiter(); - if (!awaiter.IsComplete) - { - /* Runtime-Async Call */ System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); - } - awaiter.GetResult() -}; +var i = System.Runtime.CompilerServices.RuntimeHelpers.Await(local); ``` ```il { .locals init ( - [0] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 awaiter + [0] class [System.Runtime]System.Threading.Tasks.Task`1 local, + [1] int32 i ) IL_0000: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() - IL_0005: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() - IL_000a: stloc.0 - IL_000b: ldloca.s 0 - IL_000d: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted() - IL_0012: brtrue.s IL_001b - - IL_0014: ldloc.0 - IL_0015: call class [System.Runtime]System.Threading.Tasks.Task System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync>(!!0) - IL_001a: pop - - IL_001b: ldloca.s 0 - IL_001d: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() - IL_0022: pop - IL_0023: ret + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_000c: stloc.1 + IL_000d: ret } ``` -#### Await a `T`-returning method +##### Await a `T`-returning method ```cs await C.M(); @@ -301,14 +319,24 @@ class C } ``` +Translated C#: + +```cs +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: call !!0 C::M() + IL_0005: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_000a: ret +} ``` -#### Await a generic `T` `Task`-returning method +##### Await a generic `T` `Task`-returning method ```cs -await C.M(); +int i = await C.M(); class C { @@ -316,11 +344,22 @@ class C } ``` +Translated C#: + +```cs +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() + IL_0005: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_000a: stloc.0 + IL_000b: ret +} ``` -#### Await a `Task`-returning delegate +##### Await a `Task`-returning delegate ```cs AsyncDelegate d = C.M; @@ -334,11 +373,33 @@ class C } ``` +Translated C# + +```cs +AsyncDelegate d = C.M; +System.Runtime.CompilerServices.RuntimeHelpers.Await(d()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: ldsfld class AsyncDelegate Program/'<>O'::'<0>__M' + IL_0005: dup + IL_0006: brtrue.s IL_001b + + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0010: newobj instance void AsyncDelegate::.ctor(object, native int) + IL_0015: dup + IL_0016: stsfld class AsyncDelegate Program/'<>O'::'<0>__M' + + IL_001b: callvirt instance class [System.Runtime]System.Threading.Tasks.Task AsyncDelegate::Invoke() + IL_0020: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0025: ret +} ``` -#### Await a `T`-returning delegate +##### Await a `T`-returning delegate where `T` becomes `Task` ```cs Func d = C.M; @@ -350,11 +411,35 @@ class C } ``` +Translated C#: + +```cs +Func d = C.M; +System.Runtime.CompilerServices.RuntimeHelpers.Await(d()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: ldsfld class [System.Runtime]System.Func`1 Program/'<>O'::'<0>__M' + IL_0005: dup + IL_0006: brtrue.s IL_001b + + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0010: newobj instance void class [System.Runtime]System.Func`1::.ctor(object, native int) + IL_0015: dup + IL_0016: stsfld class [System.Runtime]System.Func`1 Program/'<>O'::'<0>__M' + + IL_001b: callvirt instance !0 class [System.Runtime]System.Func`1::Invoke() + IL_0020: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0025: ret +} ``` -#### Awaiting in a `catch` block +
+ +##### Awaiting in a `catch` block ```cs try @@ -390,7 +475,7 @@ catch (Exception e) if (pendingCatch == 1) { - /* Runtime-Async Call */ C.M(); + System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); throw pendingException; } ``` @@ -402,33 +487,35 @@ if (pendingCatch == 1) [1] class [System.Runtime]System.Exception pendingException ) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 .try { - IL_0000: newobj instance void [System.Runtime]System.Exception::.ctor() - IL_0005: throw - } + IL_0002: newobj instance void [System.Runtime]System.Exception::.ctor() + IL_0007: throw + } // end .try catch [System.Runtime]System.Exception { - IL_0006: stloc.1 - IL_0007: ldc.i4.1 - IL_0008: stloc.0 - IL_0009: leave.s IL_000b - } + IL_0008: ldc.i4.1 + IL_0009: stloc.0 + IL_000a: stloc.1 + IL_000b: leave.s IL_000d + } // end handler - IL_000b: ldloc.0 - IL_000c: ldc.i4.1 - IL_000d: bne.un.s IL_0017 + IL_000d: ldloc.0 + IL_000e: ldc.i4.1 + IL_000f: bne.un.s IL_001d - IL_000f: ldloc.1 - IL_0010: call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() - IL_0015: pop - IL_0016: throw + IL_0011: call class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0016: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_001b: ldloc.1 + IL_001c: throw - IL_0017: ret + IL_001d: ret } ``` -#### Awaiting in a `finally` block +##### Awaiting in a `finally` block ```cs try @@ -459,7 +546,7 @@ catch (Exception e) pendingException = e; } -/* Runtime-Async Call */ C.M(); +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); if (pendingException != null) { @@ -477,26 +564,26 @@ if (pendingException != null) { IL_0000: newobj instance void [System.Runtime]System.Exception::.ctor() IL_0005: throw - } + } // end .try catch [System.Runtime]System.Exception { IL_0006: stloc.0 IL_0007: leave.s IL_0009 - } + } // end handler - IL_0009: call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() - IL_000e: pop - IL_000f: ldloc.0 - IL_0010: brfalse.s IL_0014 + IL_0009: call class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_000e: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0013: ldloc.0 + IL_0014: brfalse.s IL_0018 - IL_0012: ldloc.0 - IL_0013: throw + IL_0016: ldloc.0 + IL_0017: throw - IL_0014: ret + IL_0018: ret } ``` -#### Preserving compound assignments +##### Preserving compound assignments ```cs int[] a = new int[] { }; @@ -515,36 +602,152 @@ Translated C#: int[] a = new int[] { }; int _tmp1 = C.M2(); int _tmp2 = a[_tmp1]; -int _tmp3 = /* Runtime-Async Call */ C.M1(); +int _tmp3 = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M1()); a[_tmp1] = _tmp2 + _tmp3; ``` ```il { .locals init ( - [0] int32[] a, - [1] int32 _tmp1, - [2] int32 _tmp2, - [3] int32 _tmp3 + [0] int32 _tmp1, + [1] int32 _tmp2, + [2] int32 _tmp3 ) IL_0000: ldc.i4.0 IL_0001: newarr [System.Runtime]System.Int32 - IL_0006: stloc.0 - IL_0007: call int32 C::M2() - IL_000c: stloc.1 + IL_0006: call int32 C::M2() + IL_000b: stloc.0 + IL_000c: dup IL_000d: ldloc.0 - IL_000e: ldloc.1 - IL_000f: ldelem.i4 - IL_0010: stloc.2 - IL_0011: call modreq(class [System.Runtime]System.Threading.Tasks.Task) int32 C::M1() - IL_0016: stloc.3 - IL_0017: ldloc.0 - IL_0018: ldloc.1 - IL_0019: ldloc.2 - IL_001a: ldloc.3 - IL_001b: add - IL_001c: stelem.i4 - IL_001d: ret + IL_000e: ldelem.i4 + IL_000f: stloc.1 + IL_0010: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M1() + IL_0015: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_001a: stloc.2 + IL_001b: ldloc.0 + IL_001c: ldloc.1 + IL_001d: ldloc.2 + IL_001e: add + IL_001f: stelem.i4 + IL_0020: ret } ``` + +#### Await a non-Task/ValueTask + +For anything that isn't a `Task`, `Task`, `ValueTask`, and `ValueTask`, we instead use `System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync` or +`System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync`. These are covered below. + +##### Implementor of ICriticalNotifyCompletion + +`ICriticalNotifyCompletion` lowering is always preferred over `INotifyCompletion` lowering, when we statically know `ICriticalNotifyCompletion` is implemented by the expression. + +```cs +var c = new C(); +await c; + +class C +{ + public class Awaiter : ICriticalNotifyCompletion + { + public void OnCompleted(Action continuation) { } + public void UnsafeOnCompleted(Action continuation) { } + public bool IsCompleted => true; + public void GetResult() { } + } + + public Awaiter GetAwaiter() => new Awaiter(); +} +``` + +Translated C#: + +```cs +var c = new C(); +_ = { + var awaiter = c.GetAwaiter(); + if (!awaiter.IsCompleted) + { + System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + awaiter.GetResult() +}; +``` + +```il +{ + .locals init ( + [0] class C/Awaiter awaiter + ) + + IL_0000: newobj instance void C::.ctor() + IL_0005: callvirt instance class C/Awaiter C::GetAwaiter() + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: callvirt instance bool C/Awaiter::get_IsCompleted() + IL_0011: brtrue.s IL_0019 + + IL_0013: ldloc.0 + IL_0014: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + + IL_0019: ldloc.0 + IL_001a: callvirt instance void C/Awaiter::GetResult() + IL_001f: ret +} +``` + +##### Implementor of INotifyCompletion + +```cs +var c = new C(); +await c; + +class C +{ + public class Awaiter : INotifyCompletion + { + public void OnCompleted(Action continuation) { } + public bool IsCompleted => true; + public void GetResult() { } + } + + public Awaiter GetAwaiter() => new Awaiter(); +} +``` + +Translated C#: + +```cs +var c = new C(); +_ = { + var awaiter = c.GetAwaiter(); + if (!awaiter.IsCompleted) + { + System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); + } + awaiter.GetResult() +}; +``` + +```il +{ + .locals init ( + [0] class C/Awaiter awaiter + ) + + IL_0000: newobj instance void C::.ctor() + IL_0005: callvirt instance class C/Awaiter C::GetAwaiter() + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: callvirt instance bool C/Awaiter::get_IsCompleted() + IL_0011: brtrue.s IL_0019 + + IL_0013: ldloc.0 + IL_0014: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync(!!0) + + IL_0019: ldloc.0 + IL_001a: callvirt instance void C/Awaiter::GetResult() + IL_001f: ret +} +``` diff --git a/docs/features/string-literals-data-section.md b/docs/features/string-literals-data-section.md index 6d61a39a0cf14..adda3e666eaf0 100644 --- a/docs/features/string-literals-data-section.md +++ b/docs/features/string-literals-data-section.md @@ -54,7 +54,7 @@ The utf8 string literal encoding emit strategy emits `ldsfld` of a field in a ge For every unique string literal, a unique internal static class is generated which: - has name composed of `` followed by a hex-encoded XXH128 hash of the string - (collisions [should not happen][xxh128] with XXH128 and so they aren't currently detected or reported, the behavior in that case is undefined), + (collisions [should not happen][xxh128] with XXH128, but if there are string literals which would result in the same XXH128 hash, a compile-time error is reported), - is nested in the `` type to avoid polluting the global namespace and to avoid having to enforce name uniqueness across modules, - has one internal static readonly `string` field which is initialized in a static constructor of the class, @@ -62,6 +62,7 @@ For every unique string literal, a unique internal static class is generated whi There is also an internal static readonly `.data` field generated into `` containing the actual bytes, similar to [u8 string literals][u8-literals] and [constant array initializers][constant-array-init]. +This field uses hex-encoded SHA-256 hash for its name and collisions are currently not reported by the compiler. These other scenarios might also reuse the data field, e.g., the following statements could all reuse the same data field: ```cs @@ -136,15 +137,63 @@ albeit with a disclaimer during the experimental phase of the feature. Throughput of `ldstr` vs `ldsfld` is very similar (both result in one or two move instructions). In the `ldsfld` emit strategy, the `string` instances won't ever be collected by the GC once the generated class is initialized. -`ldstr` has similar behavior, but there are some optimizations in the runtime around `ldstr`, +`ldstr` has similar behavior (GC does not collect the string literals either until the assembly is unloaded), +but there are some optimizations in the runtime around `ldstr`, e.g., they are loaded into a different frozen heap so machine codegen can be more efficient (no need to worry about pointer moves). Generating new types by the compiler means more type loads and hence runtime impact, e.g., startup performance and the overhead of keeping track of these types. +On the other hand, the PE size might be smaller due to UTF-8 vs UTF-16 encoding, +which can result in memory savings since the binary is also loaded to memory by the runtime. +See [below](#runtime-overhead-benchmark) for a more detailed analysis. The generated types are returned from reflection like `Assembly.GetTypes()` which might impact the performance of Dependency Injection and similar systems. +### Runtime overhead benchmark + +| [cost per string literal](https://github.com/jkotas/stringliteralperf) | feature on | feature off | +| --- | --- | --- | +| bytes | 1037 | 550 | +| microseconds | 20.3 | 3.1 | + +The benchmark results above [show](https://github.com/dotnet/roslyn/pull/76139#discussion_r1944144978) +that the runtime overhead of this feature per 100 char string literal +is ~500 bytes of working set memory (~2x of regular string literal) +and ~17 microseconds of startup time (~7x of regular string literal). + +The startup time overhead does depend on the length of the string literal. +It is cost of the type loads and JITing the static constructor. + +The working set has two components: private working set (r/w pages) and non-private working set (r/o pages backed by the binary). +The private working set overhead (~600 bytes) does not depend on the length of the string literal. +Again, it is the cost of the type loads and the static constructor code. +Non-private working set is reduced by this feature since the binary is smaller. +Once the string literal is about 600 characters, +the private working set overhead and non-private working set improvement will break even. +For string literals longer than 600 characters, this feature is total working set improvement. + +
+Why 600 bytes? + +When the feature is off, ~550 bytes cost of 100 char string literal is composed from: +- The string in the binary (~200 bytes). +- The string allocated on the GC heap (~200 bytes). +- Fixed overheads: metadata encoding, runtime hashtable of all allocated string literals, code that referenced the string in the benchmark (~150 bytes). + +When the feature is on, ~1050 bytes cost of 100 char string literal is composed from: +- The string in the binary (~100 bytes). +- The string allocated on the GC heap (~200 bytes). +- Fixed overheads: metadata encoding, the extra types, code that referenced the string in the benchmark (~750 bytes). + +750 - 150 = 600. Vast majority of it are the extra types. + +A bit of the extra fixed overheads with the feature on is probably in the non-private working set. +It is difficult to measure it since there is no managed API to get private vs. non-private working set. +It does not impact the estimate of the break-even point for the total working set. + +
+ ## Implementation `CodeGenerator` obtains [configuration of the feature flag](#configuration) from `Compilation` passed to its constructor. @@ -168,7 +217,7 @@ but that seems to require similar amount of implemented abstract properties/meth as the implementations of `Cci` interfaces require. But implementing `Cci` directly allows us to reuse the same implementation for VB if needed in the future. -## Future work +## Future work and alternatives ### Edit and Continue @@ -193,6 +242,9 @@ This fixup phase already exists in the compiler in `MetadataWriter.WriteInstruct It is called from `SerializeMethodBodies` which precedes `PopulateSystemTables` call, hence synthesizing the utf8 string classes in the former should be possible and they would be emitted in the latter. +Alternatively, we could collect string literals during binding, then before emit sort them by length and content (for determinism) +to find the ones that are over the threshold and should be emitted with this new strategy. + ### Statistics The compiler could emit an info diagnostic with useful statistics for customers to determine what threshold to set. @@ -209,7 +261,7 @@ We would generate a single `__StaticArrayInitTypeSize=*` structure for the entir add a single `.data` field to `` that points to the blob. At runtime, we would do an offset to where the required data reside in the blob and decode the required length from UTF-8 to UTF-16. -## Alternatives +However, this would be unfriendly to IL trimming. ### Configuration/emit granularity @@ -221,7 +273,8 @@ The idea is that strings from one class are likely used "together" so there is n ### GC -To avoid rooting the `string` references forever, we could turn the fields into `WeakReference`s. +To avoid rooting the `string` references forever, we could turn the fields into `WeakReference`s +(note that this would be quite expensive for both direct overhead and indirectly for the GC due to longer GC pause times). Or we could avoid the caching altogether (each eligible `ldstr` would be replaced with a direct call to `Encoding.UTF8.GetString`). This could be configurable as well. @@ -247,6 +300,24 @@ static class However, that would likely result in worse machine code due to more branches and function calls. +### String interning + +The compiler should report a diagnostic when the feature is enabled together with +`[assembly: System.Runtime.CompilerServices.CompilationRelaxations(0)]`, i.e., string interning enabled, +because that is incompatible with the feature. + +### Avoiding hash collisions + +Instead of XXH128 for the type names and SHA-256 for the data field names, we could use index-based names. +- The compiler could assign names lazily based on metadata tokens which are deterministic. + If building on the current approach, that might require some refactoring, + because internal data structures in the compiler might not be ready for lazy names like that. + But it would be easier if combined with the first strategy suggested for [automatic threshold](#automatic-threshold) above, + where we would not synthesize the types until very late in the emit phase (during fixup of the metadata tokens). +- We could build on the second strategy suggested for [automatic threshold](#automatic-threshold) where we would collect string literals during binding + (and perhaps also constant arrays and u8 strings if we want to extend this support to them as well), + then before emit we would sort them by length and content and assign indices to them to be then used for the synthesized names. + [u8-literals]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/utf8-string-literals [constant-array-init]: https://github.com/dotnet/roslyn/pull/24621 diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 319b5c9bb6b20..4fcb5b88ff93a 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -12,7 +12,6 @@ 9.0.0-rc.2.24462.10 6.0.0-rtm.21518.12 7.0.0-alpha.1.22060.1 - <_MicrosoftTestPlatformVersion>17.5.0 - - + + - + https://github.com/dotnet/command-line-api - 060374e56c1b2e741b6525ca8417006efb54fbd7 + feb61c7f328a2401d74f4317b39d02126cfdfe24 @@ -122,19 +122,19 @@ - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + 5da211e1c42254cb35e7ef3d5a8428fb24853169 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + 5da211e1c42254cb35e7ef3d5a8428fb24853169 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + 5da211e1c42254cb35e7ef3d5a8428fb24853169 https://github.com/dotnet/symreader @@ -150,9 +150,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + 5da211e1c42254cb35e7ef3d5a8428fb24853169 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/Versions.props b/eng/Versions.props index 43f29daca07ae..e9301bb188b0b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -46,7 +46,7 @@ Versions managed by Arcade (see Versions.Details.xml) --> - 2.0.0-beta4.25072.1 + 2.0.0-beta4.24528.1 8.0.0 8.0.0 8.0.0 @@ -106,4 +106,12 @@ true + + + 17.5.0 + diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index e4ee59538e82e..da865cfedad8c 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -31,9 +31,11 @@ "Microsoft.Net.Compilers.Toolset.Arm64": "arcade", "Microsoft.Net.Compilers.Toolset.Framework": "arcade", "Microsoft.NETCore.Compilers": "arcade", + "Microsoft.CodeAnalysis.Contracts": "arcade", "Microsoft.CodeAnalysis.Debugging": "arcade", "Microsoft.CodeAnalysis.PooledObjects": "arcade", "Microsoft.CodeAnalysis.Collections": "arcade", + "Microsoft.CodeAnalysis.Threading": "arcade", "Microsoft.CodeAnalysis.Features": "arcade", "Microsoft.CodeAnalysis.EditorFeatures": "vssdk", "Microsoft.CodeAnalysis.EditorFeatures.Common": "vssdk", @@ -83,6 +85,7 @@ "Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Xaml": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch": "vs-impl", + "Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot": "vs-impl", "Microsoft.CodeAnalysis.Remote.Razor.ServiceHub": "vs-impl", "Microsoft.CodeAnalysis.Remote.ServiceHub": "vs-impl", "Microsoft.CodeAnalysis.Remote.Workspaces": "vs-impl", @@ -252,6 +255,16 @@ "insertionTitlePrefix": "[d17.14 P1]", "insertionCreateDraftPR": false }, + "release/dev18.0": { + "nugetKind": [ + "Shipping", + "NonShipping" + ], + "vsBranch": "dev/monicaro/versioning", + "vsMajorVersion": 18, + "insertionTitlePrefix": "[d18.0 P1]", + "insertionCreateDraftPR": true + }, "main": { "nugetKind": [ "Shipping", diff --git a/eng/targets/TargetFrameworks.props b/eng/targets/TargetFrameworks.props index 58f90114f4d1e..8f037a4120685 100644 --- a/eng/targets/TargetFrameworks.props +++ b/eng/targets/TargetFrameworks.props @@ -15,7 +15,7 @@ net9.0 net8.0;net9.0 net8.0 - net8.0 + net9.0 net8.0 net6.0 net9.0 diff --git a/eng/targets/VisualStudio.FastUpToDateCheckWorkarounds.targets b/eng/targets/VisualStudio.FastUpToDateCheckWorkarounds.targets new file mode 100644 index 0000000000000..c6572e1048c9b --- /dev/null +++ b/eng/targets/VisualStudio.FastUpToDateCheckWorkarounds.targets @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + $(CollectUpToDateCheckInputDesignTimeDependsOn);AddUpToDateCheckVSIXSourceItems + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/targets/VisualStudio.targets b/eng/targets/VisualStudio.targets index 64e8116ca4adf..93a24918b4da5 100644 --- a/eng/targets/VisualStudio.targets +++ b/eng/targets/VisualStudio.targets @@ -147,6 +147,10 @@ + + + false - + $(NoWarn);CA1819 @@ -53,14 +53,10 @@ - - - - @@ -80,9 +76,7 @@ - + PreserveNewest @@ -94,8 +88,7 @@ - + <_CompilerApiVersion>$([System.Version]::Parse($(VersionPrefix)).Major).$([System.Version]::Parse($(VersionPrefix)).Minor) @@ -108,9 +101,8 @@ - + + + diff --git a/src/Compilers/Core/Portable/CodeGen/ArrayMembers.cs b/src/Compilers/Core/Portable/CodeGen/ArrayMembers.cs index 5d4e4c662f8ff..39662c00c568e 100644 --- a/src/Compilers/Core/Portable/CodeGen/ArrayMembers.cs +++ b/src/Compilers/Core/Portable/CodeGen/ArrayMembers.cs @@ -365,13 +365,13 @@ public override string ToString() public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs index 167f24f09be32..87b9df89e731c 100644 --- a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs +++ b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs @@ -100,6 +100,9 @@ internal sealed class PrivateImplementationDetails : DefaultTypeDef, Cci.INamesp // data section string literal holders (key is the full string literal) private readonly ConcurrentDictionary _dataSectionStringLiteralTypes = new ConcurrentDictionary(); + // map of data section string literal generated type names ( + hash) to the full text + private readonly ConcurrentDictionary _dataSectionStringLiteralNames = new ConcurrentDictionary(); + private ImmutableArray _orderedNestedTypes; internal PrivateImplementationDetails( @@ -333,16 +336,28 @@ internal MappedField GetOrAddDataField(ImmutableArray data, ushort alignme } var @this = moduleBuilder.GetPrivateImplClass(syntaxNode, diagnostics); - return @this._dataSectionStringLiteralTypes.GetOrAdd(text, static (key, arg) => + return @this._dataSectionStringLiteralTypes.GetOrAdd(text, static (text, arg) => { - var (@this, data, diagnostics) = arg; + var (@this, data, syntaxNode, diagnostics) = arg; - string name = "" + DataToHexViaXxHash128(data); + string name = "" + @this.DataToHexViaXxHash128(data); MappedField dataField = @this.GetOrAddDataField(data, alignment: 1); Cci.IMethodDefinition bytesToStringHelper = @this.GetOrSynthesizeBytesToStringHelper(diagnostics); + var previousText = @this._dataSectionStringLiteralNames.GetOrAdd(name, text); + if (previousText != text) + { + // If there is a hash collision, we cannot fallback to normal string literal emit strategy + // because the selection of which literal would get which emit strategy would not be deterministic. + var messageProvider = @this.ModuleBuilder.CommonCompilation.MessageProvider; + diagnostics.Add(messageProvider.CreateDiagnostic( + messageProvider.ERR_DataSectionStringLiteralHashCollision, + syntaxNode.GetLocation(), + previousText[..Math.Min(previousText.Length, 500)])); + } + return new DataSectionStringType( name: name, containingType: @this, @@ -350,7 +365,7 @@ internal MappedField GetOrAddDataField(ImmutableArray data, ushort alignme bytesToStringHelper: bytesToStringHelper, diagnostics: diagnostics); }, - (@this, ImmutableCollectionsMarshal.AsImmutableArray(data), diagnostics)).Field; + (@this, ImmutableCollectionsMarshal.AsImmutableArray(data), syntaxNode, diagnostics)).Field; } /// @@ -548,8 +563,13 @@ private static string DataToHex(ImmutableArray data) return HashToHex(hash.AsSpan()); } - private static string DataToHexViaXxHash128(ImmutableArray data) + private string DataToHexViaXxHash128(ImmutableArray data) { + if (ModuleBuilder.EmitOptions.TestOnly_DataToHexViaXxHash128 is { } handler) + { + return handler(data); + } + Span hash = stackalloc byte[sizeof(ulong) * 2]; int bytesWritten = XxHash128.Hash(data.AsSpan(), hash); Debug.Assert(bytesWritten == hash.Length); @@ -897,13 +917,13 @@ public MetadataConstant Constant public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } @@ -1111,13 +1131,13 @@ public TypeDefinitionHandle TypeDef public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Compilers/Core/Portable/Collections/Rope.cs b/src/Compilers/Core/Portable/Collections/Rope.cs index ac06ff20367fa..d70c278ee9864 100644 --- a/src/Compilers/Core/Portable/Collections/Rope.cs +++ b/src/Compilers/Core/Portable/Collections/Rope.cs @@ -19,6 +19,7 @@ internal abstract class Rope public abstract override string ToString(); public abstract string ToString(int maxLength); public abstract int Length { get; } + public bool IsEmpty => Length == 0; protected abstract IEnumerable GetChars(); private Rope() { } diff --git a/src/Compilers/Core/Portable/Diagnostic/CommonMessageProvider.cs b/src/Compilers/Core/Portable/Diagnostic/CommonMessageProvider.cs index 03af6e1a38996..b662165fee496 100644 --- a/src/Compilers/Core/Portable/Diagnostic/CommonMessageProvider.cs +++ b/src/Compilers/Core/Portable/Diagnostic/CommonMessageProvider.cs @@ -244,6 +244,7 @@ public string GetIdForErrorCode(int errorCode) public abstract int ERR_EncUpdateFailedMissingSymbol { get; } public abstract int ERR_InvalidDebugInfo { get; } public abstract int ERR_FunctionPointerTypesInAttributeNotSupported { get; } + public abstract int ERR_DataSectionStringLiteralHashCollision { get; } // Generators: public abstract int WRN_GeneratorFailedDuringInitialization { get; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActionsForAnalyzer.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActionsForAnalyzer.cs index aa67d28c38cd7..021710ef3d925 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActionsForAnalyzer.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActionsForAnalyzer.cs @@ -4,8 +4,8 @@ using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -37,7 +37,7 @@ public GroupedAnalyzerActionsForAnalyzer(DiagnosticAnalyzer analyzer, in Analyze public AnalyzerActions AnalyzerActions { get; } [Conditional("DEBUG")] - private static void VerifyActions(in ImmutableArray actions, DiagnosticAnalyzer analyzer) + private static void VerifyActions(ArrayBuilder actions, DiagnosticAnalyzer analyzer) where TAnalyzerAction : AnalyzerAction { foreach (var action in actions) @@ -46,22 +46,33 @@ private static void VerifyActions(in ImmutableArray GetFilteredActions(in ImmutableArray actions) + private void AddFilteredActions( + ImmutableArray actions, + ArrayBuilder builder) where TAnalyzerAction : AnalyzerAction - => GetFilteredActions(actions, _analyzer, _analyzerActionsNeedFiltering); + { + AddFilteredActions(actions, _analyzer, _analyzerActionsNeedFiltering, builder); + } - private static ImmutableArray GetFilteredActions( + private static void AddFilteredActions( in ImmutableArray actions, DiagnosticAnalyzer analyzer, - bool analyzerActionsNeedFiltering) + bool analyzerActionsNeedFiltering, + ArrayBuilder builder) where TAnalyzerAction : AnalyzerAction { if (!analyzerActionsNeedFiltering) { - return actions; + builder.AddRange(actions); + } + else + { + foreach (var action in actions) + { + if (action.Analyzer == analyzer) + builder.Add(action); + } } - - return actions.WhereAsArray((action, analyzer) => action.Analyzer == analyzer, analyzer); } public ImmutableSegmentedDictionary>> NodeActionsByAnalyzerAndKind @@ -70,14 +81,15 @@ public ImmutableSegmentedDictionary(_analyzer) : - AnalyzerActions.GetSyntaxNodeActions(); + var nodeActions = ArrayBuilder>.GetInstance(); + if (_analyzerActionsNeedFiltering) + AnalyzerActions.AddSyntaxNodeActions(_analyzer, nodeActions); + else + AnalyzerActions.AddSyntaxNodeActions(nodeActions); + VerifyActions(nodeActions, _analyzer); - var analyzerActionsByKind = !nodeActions.IsEmpty ? - AnalyzerExecutor.GetNodeActionsByKind(nodeActions) : - ImmutableSegmentedDictionary>>.Empty; - RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyNodeActionsByKind, analyzerActionsByKind); + RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyNodeActionsByKind, AnalyzerExecutor.GetNodeActionsByKind(nodeActions)); + nodeActions.Free(); } return _lazyNodeActionsByKind; @@ -90,12 +102,11 @@ public ImmutableSegmentedDictionary.GetInstance(); + AddFilteredActions(AnalyzerActions.OperationActions, operationActions); VerifyActions(operationActions, _analyzer); - var analyzerActionsByKind = operationActions.Any() ? - AnalyzerExecutor.GetOperationActionsByKind(operationActions) : - ImmutableSegmentedDictionary>.Empty; - RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyOperationActionsByKind, analyzerActionsByKind); + RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyOperationActionsByKind, AnalyzerExecutor.GetOperationActionsByKind(operationActions)); + operationActions.Free(); } return _lazyOperationActionsByKind; @@ -108,9 +119,10 @@ private ImmutableArray> CodeBloc { if (_lazyCodeBlockStartActions.IsDefault) { - var codeBlockActions = GetFilteredActions(AnalyzerActions.GetCodeBlockStartActions()); + var codeBlockActions = ArrayBuilder>.GetInstance(); + AddFilteredActions(AnalyzerActions.GetCodeBlockStartActions(), codeBlockActions); VerifyActions(codeBlockActions, _analyzer); - ImmutableInterlocked.InterlockedInitialize(ref _lazyCodeBlockStartActions, codeBlockActions); + ImmutableInterlocked.InterlockedInitialize(ref _lazyCodeBlockStartActions, codeBlockActions.ToImmutableAndFree()); } return _lazyCodeBlockStartActions; @@ -144,9 +156,10 @@ private static ImmutableArray GetExecutableCodeActions( { if (lazyCodeBlockActions.IsDefault) { - codeBlockActions = GetFilteredActions(codeBlockActions, analyzer, analyzerActionsNeedFiltering); - VerifyActions(codeBlockActions, analyzer); - ImmutableInterlocked.InterlockedInitialize(ref lazyCodeBlockActions, codeBlockActions); + var finalActions = ArrayBuilder.GetInstance(); + AddFilteredActions(codeBlockActions, analyzer, analyzerActionsNeedFiltering, finalActions); + VerifyActions(finalActions, analyzer); + ImmutableInterlocked.InterlockedInitialize(ref lazyCodeBlockActions, finalActions.ToImmutableAndFree()); } return lazyCodeBlockActions; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index fa01b2f7f0e9f..0528bc9c60098 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -844,7 +844,7 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( { AnalyzerDriver analyzerDriver = compilation.CreateAnalyzerDriver(analyzers, analyzerManager, severityFilter); newCompilation = compilation - .WithSemanticModelProvider(new CachingSemanticModelProvider()) + .WithSemanticModelProvider(CachingSemanticModelProvider.Instance) .WithEventQueue(new AsyncQueue()); var categorizeDiagnostics = false; @@ -2782,7 +2782,7 @@ void executeOperationsActionsByKind(ImmutableArray operationsToAnaly } } - void executeOperationsBlockActions(ImmutableArray operationBlocksToAnalyze, ImmutableArray operationsToAnalyze, IEnumerable codeBlockActions) + void executeOperationsBlockActions(ImmutableArray operationBlocksToAnalyze, ImmutableArray operationsToAnalyze, ArrayBuilder codeBlockActions) { if (!shouldExecuteOperationBlockActions) { @@ -2810,7 +2810,7 @@ void executeOperationsBlockActions(ImmutableArray operationBlocksToA } } - void executeCodeBlockActions(ImmutableArray executableCodeBlocks, IEnumerable codeBlockActions) + void executeCodeBlockActions(ImmutableArray executableCodeBlocks, ArrayBuilder codeBlockActions) { if (executableCodeBlocks.IsEmpty || !shouldExecuteCodeBlockActions) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index f21aa30a5b999..19d913bc9aa2a 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -667,21 +667,18 @@ public void ExecuteAdditionalFileActions( private void ExecuteSyntaxNodeAction( SyntaxNodeAnalyzerAction syntaxNodeAction, SyntaxNode node, - ISymbol containingSymbol, - SemanticModel semanticModel, + ExecutionData executionData, Action addDiagnostic, Func isSupportedDiagnostic, - TextSpan? filterSpan, - bool isGeneratedCode, CancellationToken cancellationToken) where TLanguageKindEnum : struct { - Debug.Assert(!isGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(syntaxNodeAction.Analyzer)); + Debug.Assert(!executionData.IsGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(syntaxNodeAction.Analyzer)); Debug.Assert(!IsAnalyzerSuppressedForTree(syntaxNodeAction.Analyzer, node.SyntaxTree, cancellationToken)); var syntaxNodeContext = new SyntaxNodeAnalysisContext( - node, containingSymbol, semanticModel, AnalyzerOptions, addDiagnostic, - isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); + node, executionData.DeclaredSymbol, executionData.SemanticModel, AnalyzerOptions, addDiagnostic, + isSupportedDiagnostic, executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); ExecuteAndCatchIfThrows( syntaxNodeAction.Analyzer, @@ -694,21 +691,18 @@ private void ExecuteSyntaxNodeAction( private void ExecuteOperationAction( OperationAnalyzerAction operationAction, IOperation operation, - ISymbol containingSymbol, - SemanticModel semanticModel, + ExecutionData executionData, Action addDiagnostic, Func isSupportedDiagnostic, - TextSpan? filterSpan, - bool isGeneratedCode, CancellationToken cancellationToken) { - Debug.Assert(!isGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(operationAction.Analyzer)); - Debug.Assert(!IsAnalyzerSuppressedForTree(operationAction.Analyzer, semanticModel.SyntaxTree, cancellationToken)); + Debug.Assert(!executionData.IsGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(operationAction.Analyzer)); + Debug.Assert(!IsAnalyzerSuppressedForTree(operationAction.Analyzer, executionData.SemanticModel.SyntaxTree, cancellationToken)); var operationContext = new OperationAnalysisContext( - operation, containingSymbol, semanticModel.Compilation, + operation, executionData.DeclaredSymbol, executionData.SemanticModel.Compilation, AnalyzerOptions, addDiagnostic, isSupportedDiagnostic, GetControlFlowGraph, - filterSpan, isGeneratedCode, cancellationToken); + executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); ExecuteAndCatchIfThrows( operationAction.Analyzer, @@ -718,13 +712,27 @@ private void ExecuteOperationAction( cancellationToken); } + private readonly struct ExecutionData( + DiagnosticAnalyzer analyzer, + ISymbol declaredSymbol, + SemanticModel semanticModel, + TextSpan? filterSpan, + bool isGeneratedCode) + { + public readonly DiagnosticAnalyzer Analyzer = analyzer; + public readonly ISymbol DeclaredSymbol = declaredSymbol; + public readonly SemanticModel SemanticModel = semanticModel; + public readonly TextSpan? FilterSpan = filterSpan; + public readonly bool IsGeneratedCode = isGeneratedCode; + } + /// /// Execute code block actions for the given analyzer for the given declaration. /// public void ExecuteCodeBlockActions( - IEnumerable> codeBlockStartActions, - IEnumerable codeBlockActions, - IEnumerable codeBlockEndActions, + ImmutableArray> startActions, + ImmutableArray actions, + ImmutableArray endActions, DiagnosticAnalyzer analyzer, SyntaxNode declaredNode, ISymbol declaredSymbol, @@ -736,32 +744,98 @@ public void ExecuteCodeBlockActions( CancellationToken cancellationToken) where TLanguageKindEnum : struct { - ExecuteBlockActionsCore, CodeBlockAnalyzerAction, SyntaxNodeAnalyzerAction, SyntaxNode, TLanguageKindEnum>( - codeBlockStartActions, codeBlockActions, codeBlockEndActions, analyzer, - declaredNode, declaredSymbol, executableCodeBlocks, (codeBlocks) => codeBlocks.SelectMany( - cb => + Debug.Assert(!executableCodeBlocks.IsEmpty); + + // The actions we discover in 'addActions' and then execute in 'executeActions'. + var ephemeralActions = ArrayBuilder>.GetInstance(); + ExecuteBlockActionsCore( + startActions, + actions, + endActions, + declaredNode, + new ExecutionData(analyzer, declaredSymbol, semanticModel, filterSpan, isGeneratedCode), + addActions: static (startAction, endActions, executionData, args, cancellationToken) => + { + var (@this, startActions, executableCodeBlocks, declaredNode, getKind, ephemeralActions) = args; + + var scope = new HostCodeBlockStartAnalysisScope(startAction.Analyzer); + var startContext = new AnalyzerCodeBlockStartAnalysisContext( + scope, declaredNode, executionData.DeclaredSymbol, executionData.SemanticModel, + @this.AnalyzerOptions, executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); + + // Catch Exception from the start action. + @this.ExecuteAndCatchIfThrows( + startAction.Analyzer, + static args => args.startAction.Action(args.startContext), + argument: (startAction, startContext), + new AnalysisContextInfo(@this.Compilation, executionData.DeclaredSymbol, declaredNode), + cancellationToken); + + endActions.AddAll(scope.CodeBlockEndActions); + ephemeralActions.AddRange(scope.SyntaxNodeActions); + }, + executeActions: static (diagReporter, isSupportedDiagnostic, executionData, args, cancellationToken) => + { + var (@this, startActions, executableCodeBlocks, declaredNode, getKind, ephemeralActions) = args; + if (ephemeralActions.Any()) { - var filter = semanticModel.GetSyntaxNodesToAnalyzeFilter(cb, declaredSymbol); + var executableNodeActionsByKind = GetNodeActionsByKind(ephemeralActions); + var syntaxNodesToAnalyze = ArrayBuilder.GetInstance(); - if (filter is object) + foreach (var block in executableCodeBlocks) { - return cb.DescendantNodesAndSelf(descendIntoChildren: filter).Where(filter); - } - else - { - return cb.DescendantNodesAndSelf(); + var filter = executionData.SemanticModel.GetSyntaxNodesToAnalyzeFilter(block, executionData.DeclaredSymbol); + if (filter is not null) + { + foreach (var descendantNode in block.DescendantNodesAndSelf(descendIntoChildren: filter)) + { + if (filter(descendantNode)) + syntaxNodesToAnalyze.Add(descendantNode); + } + } + else + { + syntaxNodesToAnalyze.AddRange(block.DescendantNodesAndSelf()); + } } - }), - semanticModel, getKind, filterSpan, isGeneratedCode, cancellationToken); + + @this.ExecuteSyntaxNodeActions( + syntaxNodesToAnalyze, executableNodeActionsByKind, executionData, + getKind, diagReporter, isSupportedDiagnostic, + hasCodeBlockStartOrSymbolStartActions: startActions.Any(), + cancellationToken); + syntaxNodesToAnalyze.Free(); + } + }, + executeBlockActions: static (blockActions, diagReporter, isSupportedDiagnostic, executionData, args, cancellationToken) => + { + var (@this, startActions, executableCodeBlocks, declaredNode, getKind, ephemeralActions) = args; + + var context = new CodeBlockAnalysisContext(declaredNode, executionData.DeclaredSymbol, executionData.SemanticModel, + @this.AnalyzerOptions, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); + + foreach (var blockAction in blockActions) + { + @this.ExecuteAndCatchIfThrows( + blockAction.Analyzer, + static data => data.blockAction.Action(data.context), + (blockAction, context), + new AnalysisContextInfo(@this.Compilation, executionData.DeclaredSymbol, declaredNode), + cancellationToken); + } + }, + argument: (@this: this, startActions, executableCodeBlocks, declaredNode, getKind, ephemeralActions), + cancellationToken); + ephemeralActions.Free(); } /// /// Execute operation block actions for the given analyzer for the given declaration. /// public void ExecuteOperationBlockActions( - IEnumerable operationBlockStartActions, - IEnumerable operationBlockActions, - IEnumerable operationBlockEndActions, + ImmutableArray startActions, + ImmutableArray actions, + ImmutableArray endActions, DiagnosticAnalyzer analyzer, SyntaxNode declaredNode, ISymbol declaredSymbol, @@ -772,39 +846,92 @@ public void ExecuteOperationBlockActions( bool isGeneratedCode, CancellationToken cancellationToken) { - ExecuteBlockActionsCore( - operationBlockStartActions, operationBlockActions, operationBlockEndActions, analyzer, - declaredNode, declaredSymbol, operationBlocks, (blocks) => operations, semanticModel, - getKind: null, filterSpan, isGeneratedCode, cancellationToken); + Debug.Assert(!operationBlocks.IsEmpty); + + // The actions we discover in 'addActions' and then execute in 'executeActions'. + var ephemeralActions = ArrayBuilder.GetInstance(); + ExecuteBlockActionsCore( + startActions, + actions, + endActions, + declaredNode, + new ExecutionData(analyzer, declaredSymbol, semanticModel, filterSpan, isGeneratedCode), + addActions: static (startAction, endActions, executionData, args, cancellationToken) => + { + var (@this, startActions, declaredNode, operationBlocks, operations, ephemeralActions) = args; + var scope = new HostOperationBlockStartAnalysisScope(startAction.Analyzer); + var startContext = new AnalyzerOperationBlockStartAnalysisContext( + scope, operationBlocks, executionData.DeclaredSymbol, executionData.SemanticModel.Compilation, @this.AnalyzerOptions, + @this.GetControlFlowGraph, declaredNode.SyntaxTree, executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); + + // Catch Exception from the start action. + @this.ExecuteAndCatchIfThrows( + startAction.Analyzer, + static args => args.startAction.Action(args.startContext), + argument: (startAction, startContext), + new AnalysisContextInfo(@this.Compilation, executionData.DeclaredSymbol), + cancellationToken); + + endActions.AddAll(scope.OperationBlockEndActions); + ephemeralActions.AddRange(scope.OperationActions); + }, + executeActions: static (diagReporter, isSupportedDiagnostic, executionData, args, cancellationToken) => + { + var (@this, startActions, declaredNode, operationBlocks, operations, ephemeralActions) = args; + if (ephemeralActions.Any()) + { + @this.ExecuteOperationActions( + operations, GetOperationActionsByKind(ephemeralActions), + executionData, diagReporter, isSupportedDiagnostic, + hasOperationBlockStartOrSymbolStartActions: startActions.Any(), + cancellationToken); + } + }, + executeBlockActions: static (blockActions, diagReporter, isSupportedDiagnostic, executionData, args, cancellationToken) => + { + var (@this, startActions, declaredNode, operationBlocks, operations, ephemeralActions) = args; + + var context = new OperationBlockAnalysisContext(operationBlocks, executionData.DeclaredSymbol, @this.Compilation, + @this.AnalyzerOptions, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, @this.GetControlFlowGraph, declaredNode.SyntaxTree, + executionData.FilterSpan, executionData.IsGeneratedCode, cancellationToken); + + foreach (var blockAction in blockActions) + { + @this.ExecuteAndCatchIfThrows( + blockAction.Analyzer, + static data => data.blockAction.Action(data.context), + (blockAction, context), + new AnalysisContextInfo(@this.Compilation, executionData.DeclaredSymbol), + cancellationToken); + } + }, + argument: (@this: this, startActions, declaredNode, operationBlocks, operations, ephemeralActions), + cancellationToken); + ephemeralActions.Free(); } - private void ExecuteBlockActionsCore( - IEnumerable startActions, - IEnumerable actions, - IEnumerable endActions, - DiagnosticAnalyzer analyzer, - SyntaxNode declaredNode, - ISymbol declaredSymbol, - ImmutableArray executableBlocks, - Func, IEnumerable> getNodesToAnalyze, - SemanticModel semanticModel, - Func? getKind, - TextSpan? filterSpan, - bool isGeneratedCode, - CancellationToken cancellationToken) - where TLanguageKindEnum : struct - where TBlockStartAction : AnalyzerAction - where TBlockAction : AnalyzerAction - where TNodeAction : AnalyzerAction + private void ExecuteBlockActionsCore( + ImmutableArray startActions, + ImmutableArray actions, + ImmutableArray endActions, + SyntaxNode declaredNode, + ExecutionData executionData, + Action, ExecutionData, TArgs, CancellationToken> addActions, + Action, ExecutionData, TArgs, CancellationToken> executeActions, + Action, AnalyzerDiagnosticReporter, Func, ExecutionData, TArgs, CancellationToken> executeBlockActions, + TArgs argument, + CancellationToken cancellationToken) + where TBlockStartAction : AnalyzerAction + where TBlockAction : AnalyzerAction + where TArgs : struct { Debug.Assert(declaredNode != null); - Debug.Assert(declaredSymbol != null); - Debug.Assert(CanHaveExecutableCodeBlock(declaredSymbol)); + Debug.Assert(executionData.DeclaredSymbol != null); + Debug.Assert(CanHaveExecutableCodeBlock(executionData.DeclaredSymbol)); Debug.Assert(startActions.Any() || endActions.Any() || actions.Any()); - Debug.Assert(!executableBlocks.IsEmpty); - if (isGeneratedCode && _shouldSkipAnalysisOnGeneratedCode(analyzer) || - IsAnalyzerSuppressedForTree(analyzer, declaredNode.SyntaxTree, cancellationToken)) + if (executionData.IsGeneratedCode && _shouldSkipAnalysisOnGeneratedCode(executionData.Analyzer) || + IsAnalyzerSuppressedForTree(executionData.Analyzer, declaredNode.SyntaxTree, cancellationToken)) { return; } @@ -813,10 +940,6 @@ private void ExecuteBlockActionsCore.GetInstance(); var blockActions = PooledHashSet.GetInstance(); - var executableNodeActions = ArrayBuilder.GetInstance(); - var syntaxNodeActions = executableNodeActions as ArrayBuilder>; - var operationActions = executableNodeActions as ArrayBuilder; - ImmutableArray operationBlocks = executableBlocks[0] is IOperation ? (ImmutableArray)(object)executableBlocks : ImmutableArray.Empty; // Include the code block actions. blockActions.AddAll(actions); @@ -824,143 +947,35 @@ private void ExecuteBlockActionsCore codeBlockStartAction) - { - var codeBlockEndActions = blockEndActions as PooledHashSet; - var codeBlockScope = new HostCodeBlockStartAnalysisScope(startAction.Analyzer); - var blockStartContext = new AnalyzerCodeBlockStartAnalysisContext( - codeBlockScope, declaredNode, declaredSymbol, semanticModel, AnalyzerOptions, filterSpan, isGeneratedCode, cancellationToken); - - ExecuteAndCatchIfThrows( - startAction.Analyzer, - static data => data.codeBlockStartAction.Action(data.blockStartContext), - (codeBlockStartAction, blockStartContext), - new AnalysisContextInfo(Compilation, declaredSymbol, declaredNode), - cancellationToken); - - codeBlockEndActions!.AddAll(codeBlockScope.CodeBlockEndActions); - syntaxNodeActions!.AddRange(codeBlockScope.SyntaxNodeActions); - } - else - { - if (startAction is OperationBlockStartAnalyzerAction operationBlockStartAction) - { - var operationBlockEndActions = blockEndActions as PooledHashSet; - var operationBlockScope = new HostOperationBlockStartAnalysisScope(startAction.Analyzer); - var operationStartContext = new AnalyzerOperationBlockStartAnalysisContext( - operationBlockScope, operationBlocks, declaredSymbol, semanticModel.Compilation, AnalyzerOptions, - GetControlFlowGraph, declaredNode.SyntaxTree, filterSpan, isGeneratedCode, cancellationToken); - - ExecuteAndCatchIfThrows( - startAction.Analyzer, - static data => data.operationBlockStartAction.Action(data.operationStartContext), - (operationBlockStartAction, operationStartContext), - new AnalysisContextInfo(Compilation, declaredSymbol), - cancellationToken); - - operationBlockEndActions!.AddAll(operationBlockScope.OperationBlockEndActions); - operationActions!.AddRange(operationBlockScope.OperationActions); - } - } - } + addActions(startAction, blockEndActions, executionData, argument, cancellationToken); using var _ = PooledDelegates.GetPooledFunction( - static (d, ct, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d, ct), - (self: this, analyzer), + static (d, ct, arg) => arg.self.IsSupportedDiagnostic(arg.Analyzer, d, ct), + (self: this, executionData.Analyzer), out Func isSupportedDiagnostic); // Execute stateful executable node analyzers, if any. - if (executableNodeActions.Any()) - { - if (syntaxNodeActions != null) - { - Debug.Assert(getKind != null); + executeActions(diagReporter, isSupportedDiagnostic, executionData, argument, cancellationToken); - var executableNodeActionsByKind = GetNodeActionsByKind(syntaxNodeActions); - var syntaxNodesToAnalyze = (IEnumerable)getNodesToAnalyze(executableBlocks); - ExecuteSyntaxNodeActions(syntaxNodesToAnalyze, executableNodeActionsByKind, analyzer, declaredSymbol, semanticModel, getKind, diagReporter, isSupportedDiagnostic, filterSpan, isGeneratedCode, hasCodeBlockStartOrSymbolStartActions: startActions.Any(), cancellationToken); - } - else if (operationActions != null) - { - var operationActionsByKind = GetOperationActionsByKind(operationActions); - var operationsToAnalyze = (IEnumerable)getNodesToAnalyze(executableBlocks); - ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, declaredSymbol, semanticModel, diagReporter, isSupportedDiagnostic, filterSpan, isGeneratedCode, hasOperationBlockStartOrSymbolStartActions: startActions.Any(), cancellationToken); - } - } - - executableNodeActions.Free(); - - ExecuteBlockActions(blockActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); - ExecuteBlockActions(blockEndActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); + executeBlockActions(blockActions, diagReporter, isSupportedDiagnostic, executionData, argument, cancellationToken); + executeBlockActions(blockEndActions, diagReporter, isSupportedDiagnostic, executionData, argument, cancellationToken); diagReporter.Free(); - } - - private void ExecuteBlockActions( - PooledHashSet blockActions, - SyntaxNode declaredNode, - ISymbol declaredSymbol, - DiagnosticAnalyzer analyzer, - SemanticModel semanticModel, - ImmutableArray operationBlocks, - Action addDiagnostic, - Func isSupportedDiagnostic, - TextSpan? filterSpan, - bool isGeneratedCode, - CancellationToken cancellationToken) - where TBlockAction : AnalyzerAction - { - Debug.Assert(!isGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(analyzer)); - Debug.Assert(!IsAnalyzerSuppressedForTree(analyzer, declaredNode.SyntaxTree, cancellationToken)); - - foreach (var blockAction in blockActions) - { - var codeBlockAction = blockAction as CodeBlockAnalyzerAction; - if (codeBlockAction != null) - { - // This context is a struct, so it's fine to create a new one for each action. - var context = new CodeBlockAnalysisContext(declaredNode, declaredSymbol, semanticModel, - AnalyzerOptions, addDiagnostic, isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); - - ExecuteAndCatchIfThrows( - codeBlockAction.Analyzer, - static data => data.codeBlockAction.Action(data.context), - (codeBlockAction, context), - new AnalysisContextInfo(Compilation, declaredSymbol, declaredNode), - cancellationToken); - } - else - { - var operationBlockAction = blockAction as OperationBlockAnalyzerAction; - if (operationBlockAction != null) - { - // This context is a struct, so it's fine to create a new one for each action. - var context = new OperationBlockAnalysisContext(operationBlocks, declaredSymbol, semanticModel.Compilation, - AnalyzerOptions, addDiagnostic, isSupportedDiagnostic, GetControlFlowGraph, declaredNode.SyntaxTree, filterSpan, isGeneratedCode, cancellationToken); - - ExecuteAndCatchIfThrows( - operationBlockAction.Analyzer, - static data => data.operationBlockAction.Action(data.context), - (operationBlockAction, context), - new AnalysisContextInfo(Compilation, declaredSymbol), - cancellationToken); - } - } - } - blockActions.Free(); + blockEndActions.Free(); } internal static ImmutableSegmentedDictionary>> GetNodeActionsByKind( - IEnumerable> nodeActions) + ArrayBuilder> nodeActions) where TLanguageKindEnum : struct { - Debug.Assert(nodeActions != null && nodeActions.Any()); + if (nodeActions.IsEmpty) + return ImmutableSegmentedDictionary>>.Empty; var nodeActionsByKind = PooledDictionary>>.GetInstance(); foreach (var nodeAction in nodeActions) @@ -978,7 +993,7 @@ internal static ImmutableSegmentedDictionary public void ExecuteSyntaxNodeActions( - IEnumerable nodesToAnalyze, + ArrayBuilder nodesToAnalyze, ImmutableSegmentedDictionary>> nodeActionsByKind, DiagnosticAnalyzer analyzer, SemanticModel model, @@ -1003,28 +1018,29 @@ public void ExecuteSyntaxNodeActions( static (d, ct, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d, ct), (self: this, analyzer), out Func isSupportedDiagnostic); - ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, declaredSymbol, model, getKind, diagReporter, isSupportedDiagnostic, filterSpan, isGeneratedCode, hasCodeBlockStartOrSymbolStartActions, cancellationToken); + + ExecuteSyntaxNodeActions( + nodesToAnalyze, nodeActionsByKind, + new ExecutionData(analyzer, declaredSymbol, model, filterSpan, isGeneratedCode), + getKind, diagReporter, isSupportedDiagnostic, hasCodeBlockStartOrSymbolStartActions, cancellationToken); + diagReporter.Free(); } private void ExecuteSyntaxNodeActions( - IEnumerable nodesToAnalyze, + ArrayBuilder nodesToAnalyze, ImmutableSegmentedDictionary>> nodeActionsByKind, - DiagnosticAnalyzer analyzer, - ISymbol containingSymbol, - SemanticModel model, + ExecutionData executionData, Func getKind, AnalyzerDiagnosticReporter diagReporter, Func isSupportedDiagnostic, - TextSpan? filterSpan, - bool isGeneratedCode, bool hasCodeBlockStartOrSymbolStartActions, CancellationToken cancellationToken) where TLanguageKindEnum : struct { Debug.Assert(nodeActionsByKind.Any()); - Debug.Assert(!isGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(analyzer)); - Debug.Assert(!IsAnalyzerSuppressedForTree(analyzer, model.SyntaxTree, cancellationToken)); + Debug.Assert(!executionData.IsGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(executionData.Analyzer)); + Debug.Assert(!IsAnalyzerSuppressedForTree(executionData.Analyzer, executionData.SemanticModel.SyntaxTree, cancellationToken)); foreach (var node in nodesToAnalyze) { @@ -1034,7 +1050,7 @@ private void ExecuteSyntaxNodeActions( if (nodeActionsByKind.TryGetValue(getKind(node), out var actionsForKind)) { RoslynDebug.Assert(!actionsForKind.IsEmpty, $"Unexpected empty action collection in {nameof(nodeActionsByKind)}"); - if (ShouldExecuteNode(node, analyzer, cancellationToken)) + if (ShouldExecuteNode(node, executionData.Analyzer, cancellationToken)) { // If analyzer hasn't registered any CodeBlockStart or SymbolStart actions, then update the filter span // for local diagnostics to be the callback node's full span. @@ -1045,16 +1061,18 @@ private void ExecuteSyntaxNodeActions( foreach (var action in actionsForKind) { - ExecuteSyntaxNodeAction(action, node, containingSymbol, model, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); + ExecuteSyntaxNodeAction(action, node, executionData, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, cancellationToken); } } } } } - internal static ImmutableSegmentedDictionary> GetOperationActionsByKind(IEnumerable operationActions) + internal static ImmutableSegmentedDictionary> GetOperationActionsByKind( + ArrayBuilder operationActions) { - Debug.Assert(operationActions.Any()); + if (operationActions.IsEmpty) + return ImmutableSegmentedDictionary>.Empty; var operationActionsByKind = PooledDictionary>.GetInstance(); foreach (var operationAction in operationActions) @@ -1076,7 +1094,7 @@ internal static ImmutableSegmentedDictionary public void ExecuteOperationActions( - IEnumerable operationsToAnalyze, + ImmutableArray operationsToAnalyze, ImmutableSegmentedDictionary> operationActionsByKind, DiagnosticAnalyzer analyzer, SemanticModel model, @@ -1099,27 +1117,28 @@ public void ExecuteOperationActions( static (d, ct, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d, ct), (self: this, analyzer), out Func isSupportedDiagnostic); - ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, declaredSymbol, model, diagReporter, isSupportedDiagnostic, filterSpan, isGeneratedCode, hasOperationBlockStartOrSymbolStartActions, cancellationToken); + + ExecuteOperationActions( + operationsToAnalyze, operationActionsByKind, + new ExecutionData(analyzer, declaredSymbol, model, filterSpan, isGeneratedCode), + diagReporter, isSupportedDiagnostic, hasOperationBlockStartOrSymbolStartActions, cancellationToken); + diagReporter.Free(); } private void ExecuteOperationActions( - IEnumerable operationsToAnalyze, + ImmutableArray operationsToAnalyze, ImmutableSegmentedDictionary> operationActionsByKind, - DiagnosticAnalyzer analyzer, - ISymbol containingSymbol, - SemanticModel model, + ExecutionData executionData, AnalyzerDiagnosticReporter diagReporter, Func isSupportedDiagnostic, - TextSpan? filterSpan, - bool isGeneratedCode, bool hasOperationBlockStartOrSymbolStartActions, CancellationToken cancellationToken) { Debug.Assert(operationActionsByKind != null); Debug.Assert(operationActionsByKind.Any()); - Debug.Assert(!isGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(analyzer)); - Debug.Assert(!IsAnalyzerSuppressedForTree(analyzer, model.SyntaxTree, cancellationToken)); + Debug.Assert(!executionData.IsGeneratedCode || !_shouldSkipAnalysisOnGeneratedCode(executionData.Analyzer)); + Debug.Assert(!IsAnalyzerSuppressedForTree(executionData.Analyzer, executionData.SemanticModel.SyntaxTree, cancellationToken)); foreach (var operation in operationsToAnalyze) { @@ -1129,7 +1148,7 @@ private void ExecuteOperationActions( if (operationActionsByKind.TryGetValue(operation.Kind, out var actionsForKind)) { RoslynDebug.Assert(!actionsForKind.IsEmpty, $"Unexpected empty action collection in {nameof(operationActionsByKind)}"); - if (ShouldExecuteOperation(operation, analyzer, cancellationToken)) + if (ShouldExecuteOperation(operation, executionData.Analyzer, cancellationToken)) { // If analyzer hasn't registered any OperationBlockStart or SymbolStart actions, then update // the filter span for local diagnostics to be the callback operation's full span. @@ -1140,7 +1159,7 @@ private void ExecuteOperationActions( foreach (var action in actionsForKind) { - ExecuteOperationAction(action, operation, containingSymbol, model, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, filterSpan, isGeneratedCode, cancellationToken); + ExecuteOperationAction(action, operation, executionData, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, cancellationToken); } } } @@ -1234,7 +1253,7 @@ internal static bool HandleAnalyzerException( Func? analyzerExceptionFilter, CancellationToken cancellationToken) { - if (!ExceptionFilter(exception, analyzerExceptionFilter, cancellationToken)) + if (!exceptionFilter(exception, analyzerExceptionFilter, cancellationToken)) { return false; } @@ -1252,7 +1271,7 @@ internal static bool HandleAnalyzerException( return true; - static bool ExceptionFilter(Exception ex, Func? analyzerExceptionFilter, CancellationToken cancellationToken) + static bool exceptionFilter(Exception ex, Func? analyzerExceptionFilter, CancellationToken cancellationToken) { if ((ex as OperationCanceledException)?.CancellationToken == cancellationToken) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs index fbbd8f4553645..2f1eafe06b16d 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs @@ -106,6 +106,9 @@ public bool Equals(AnalyzerReference? other) public override int GetHashCode() => Hash.Combine(RuntimeHelpers.GetHashCode(_assemblyLoader), FullPath.GetHashCode()); + public override string ToString() + => $"{nameof(AnalyzerFileReference)}({nameof(FullPath)} = {FullPath})"; + public override ImmutableArray GetAnalyzersForAllLanguages() { // This API returns duplicates of analyzers that support multiple languages. diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs index 573045bbf5733..4177c37847907 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs @@ -23,22 +23,26 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class CachingSemanticModelProvider : SemanticModelProvider { + // Provide access to CachingSemanticModelProvider through a singleton. The inner CWT is static + // to avoid leak potential -- see https://github.com/dotnet/runtime/issues/12255. + // CachingSemanticModelProvider.s_providerCache -> PerCompilationProvider -> Compilation -> CachingSemanticModelProvider + public static CachingSemanticModelProvider Instance { get; } = new CachingSemanticModelProvider(); + private static readonly ConditionalWeakTable.CreateValueCallback s_createProviderCallback = new ConditionalWeakTable.CreateValueCallback(compilation => new PerCompilationProvider(compilation)); - private readonly ConditionalWeakTable _providerCache; + private static readonly ConditionalWeakTable s_providerCache = new ConditionalWeakTable(); - public CachingSemanticModelProvider() + private CachingSemanticModelProvider() { - _providerCache = new ConditionalWeakTable(); } public override SemanticModel GetSemanticModel(SyntaxTree tree, Compilation compilation, SemanticModelOptions options = default) - => _providerCache.GetValue(compilation, s_createProviderCallback).GetSemanticModel(tree, options); + => s_providerCache.GetValue(compilation, s_createProviderCallback).GetSemanticModel(tree, options); internal void ClearCache(SyntaxTree tree, Compilation compilation) { - if (_providerCache.TryGetValue(compilation, out var provider)) + if (s_providerCache.TryGetValue(compilation, out var provider)) { provider.ClearCachedSemanticModel(tree); } @@ -46,7 +50,7 @@ internal void ClearCache(SyntaxTree tree, Compilation compilation) internal void ClearCache(Compilation compilation) { - _providerCache.Remove(compilation); + s_providerCache.Remove(compilation); } private sealed class PerCompilationProvider diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 45e7418d5887a..67c47debe0336 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -97,7 +97,7 @@ public CompilationWithAnalyzers(Compilation compilation, ImmutableArray()); _compilation = compilation; _analyzers = analyzers; @@ -723,7 +723,7 @@ private async Task ComputeAnalyzerDiagnosticsAsync(AnalysisScope? analysisScope, // subsequently discard this compilation. var compilation = analysisScope.IsSingleFileAnalysisForCompilerAnalyzer ? _compilation - : _compilation.WithSemanticModelProvider(new CachingSemanticModelProvider()).WithEventQueue(new AsyncQueue()); + : _compilation.WithSemanticModelProvider(CachingSemanticModelProvider.Instance).WithEventQueue(new AsyncQueue()); // Get the analyzer driver to execute analysis. using var driver = await CreateAndInitializeDriverAsync(compilation, _analysisOptions, analysisScope, _suppressors, categorizeDiagnostics: true, cancellationToken).ConfigureAwait(false); @@ -1188,7 +1188,7 @@ private static IEnumerable GetEffectiveDiagnosticsImpl(ImmutableArra if (compilation.SemanticModelProvider == null) { - compilation = compilation.WithSemanticModelProvider(new CachingSemanticModelProvider()); + compilation = compilation.WithSemanticModelProvider(CachingSemanticModelProvider.Instance); } var suppressMessageState = new SuppressMessageAttributeState(compilation); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index fb6dcccfdfd45..ba5874c3d1d0a 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -820,14 +820,20 @@ internal readonly ImmutableArray return _codeBlockStartActions.OfType>().ToImmutableArray(); } - internal readonly ImmutableArray> GetSyntaxNodeActions() where TLanguageKindEnum : struct + internal readonly void AddSyntaxNodeActions( + ArrayBuilder> builder) where TLanguageKindEnum : struct { - return _syntaxNodeActions.OfType>().ToImmutableArray(); + foreach (var action in _syntaxNodeActions) + { + if (action is SyntaxNodeAnalyzerAction stronglyTypedAction) + builder.Add(stronglyTypedAction); + } } - internal readonly ImmutableArray> GetSyntaxNodeActions(DiagnosticAnalyzer analyzer) where TLanguageKindEnum : struct + internal readonly void AddSyntaxNodeActions( + DiagnosticAnalyzer analyzer, + ArrayBuilder> builder) where TLanguageKindEnum : struct { - var builder = ArrayBuilder>.GetInstance(); foreach (var action in _syntaxNodeActions) { if (action.Analyzer == analyzer && @@ -836,8 +842,6 @@ internal readonly ImmutableArray> Ge builder.Add(syntaxNodeAction); } } - - return builder.ToImmutableAndFree(); } internal readonly ImmutableArray OperationBlockActions diff --git a/src/Compilers/Core/Portable/Emit/EmitOptions.cs b/src/Compilers/Core/Portable/Emit/EmitOptions.cs index 07e92a2f3371e..fefce27c268cc 100644 --- a/src/Compilers/Core/Portable/Emit/EmitOptions.cs +++ b/src/Compilers/Core/Portable/Emit/EmitOptions.cs @@ -120,6 +120,8 @@ public sealed class EmitOptions : IEquatable /// private bool _testOnly_AllowLocalStateTracing; + internal Func, string>? TestOnly_DataToHexViaXxHash128 { get; init; } + // 1.2 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public EmitOptions( bool metadataOnly, diff --git a/src/Compilers/Core/Portable/Emit/ErrorType.cs b/src/Compilers/Core/Portable/Emit/ErrorType.cs index f7d2ef047a65e..1c844f2f4484b 100644 --- a/src/Compilers/Core/Portable/Emit/ErrorType.cs +++ b/src/Compilers/Core/Portable/Emit/ErrorType.cs @@ -193,13 +193,13 @@ string Cci.INamedEntity.Name public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } /// diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs index f2ec63e12cd4f..114247c93730c 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMember.cs @@ -122,13 +122,13 @@ Cci.IDefinition Cci.IReference.AsDefinition(EmitContext context) public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs index b8c27026b7500..0f751bbb4cc5e 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedParameter.cs @@ -274,13 +274,13 @@ public override string ToString() public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs index d885927dfdd45..ea19ac97129ee 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs @@ -718,13 +718,13 @@ public override string ToString() public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedTypeParameter.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedTypeParameter.cs index 1898e994606ea..b7d19bd66f3cf 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedTypeParameter.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedTypeParameter.cs @@ -239,13 +239,13 @@ Cci.IMethodReference Cci.IGenericMethodParameterReference.DefiningMethod public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/Emit/NoPia/VtblGap.cs b/src/Compilers/Core/Portable/Emit/NoPia/VtblGap.cs index 696403f6dca06..12fe6d43652e7 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/VtblGap.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/VtblGap.cs @@ -259,13 +259,13 @@ Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs b/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs index 66cd96adace05..be2435b56dfd5 100644 --- a/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities { diff --git a/src/Compilers/Core/Portable/InternalUtilities/EmptyComparer.cs b/src/Compilers/Core/Portable/InternalUtilities/EmptyComparer.cs index 71e3a791ee95b..0f61efb45d893 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EmptyComparer.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EmptyComparer.cs @@ -3,8 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities { diff --git a/src/Compilers/Core/Portable/InternalUtilities/ExceptionExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ExceptionExtensions.cs new file mode 100644 index 0000000000000..92de8e4d05892 --- /dev/null +++ b/src/Compilers/Core/Portable/InternalUtilities/ExceptionExtensions.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; + +namespace Microsoft.CodeAnalysis; + +internal static class ExceptionExtensions +{ + /// + /// Determine if an exception was an , and that the provided token caused the cancellation. + /// + /// The exception to test. + /// Checked to see if the provided token was cancelled. + /// if the exception was an and the token was canceled. + internal static bool IsCurrentOperationBeingCancelled(this Exception exception, CancellationToken cancellationToken) + => exception is OperationCanceledException && cancellationToken.IsCancellationRequested; +} diff --git a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs index a620bf64f1de7..ba93aec54dd86 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs @@ -4,11 +4,9 @@ using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ErrorReporting { diff --git a/src/Compilers/Core/Portable/InternalUtilities/ReaderWriterLockSlimExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ReaderWriterLockSlimExtensions.cs index d75c522a02f6c..0516e0d67c7fc 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ReaderWriterLockSlimExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ReaderWriterLockSlimExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Threading; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities { diff --git a/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs b/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs index 58ea9a75a47f7..b0d035b8f5ce8 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ErrorReporting; namespace Roslyn.Utilities diff --git a/src/Compilers/Core/Portable/InternalUtilities/SemaphoreSlimExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/SemaphoreSlimExtensions.cs index e8e057a8b950d..27fa928fab65c 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/SemaphoreSlimExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/SemaphoreSlimExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities { diff --git a/src/Compilers/Core/Portable/InternalUtilities/SingleInitNullable.cs b/src/Compilers/Core/Portable/InternalUtilities/SingleInitNullable.cs index 1e4633cee50bb..24299ce099285 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/SingleInitNullable.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/SingleInitNullable.cs @@ -4,6 +4,7 @@ using System; using System.Threading; +using Microsoft.CodeAnalysis; namespace Roslyn.Utilities; diff --git a/src/Compilers/Core/Portable/InternalUtilities/VoidResult.cs b/src/Compilers/Core/Portable/InternalUtilities/VoidResult.cs deleted file mode 100644 index ea481f9017836..0000000000000 --- a/src/Compilers/Core/Portable/InternalUtilities/VoidResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace Roslyn.Utilities -{ - /// - /// Explicitly indicates result is void - /// - internal readonly struct VoidResult : IEquatable - { - public override bool Equals(object? obj) - => obj is VoidResult; - - public override int GetHashCode() - => 0; - - public bool Equals(VoidResult other) - => true; - } -} diff --git a/src/Compilers/Core/Portable/InternalUtilities/YieldAwaitableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/YieldAwaitableExtensions.cs deleted file mode 100644 index dd62cb31b5d23..0000000000000 --- a/src/Compilers/Core/Portable/InternalUtilities/YieldAwaitableExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace Roslyn.Utilities -{ - internal static class YieldAwaitableExtensions - { - /// - /// Implements ConfigureAwait(bool) for . The resulting behavior in asynchronous code - /// is the same as one would expect for . - /// - /// The awaitable provided by . - /// - /// An object used to await this yield. - public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable awaitable, bool continueOnCapturedContext) - { - return new ConfiguredYieldAwaitable(awaitable, continueOnCapturedContext); - } - } -} diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index 94c47e9a01e9e..cfa76200dd450 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -6,7 +6,11 @@ Microsoft.CodeAnalysis true $(NetRoslynSourceBuild);netstandard2.0 - $(DefineConstants);COMPILERCORE + $(DefineConstants);COMPILERCORE;MICROSOFT_CODEANALYSIS_CONTRACTS_NO_CONTRACT + + + $(DefineConstants);MICROSOFT_CODEANALYSIS_POOLEDOBJECTS_NO_POOLED_DISPOSER + true full true @@ -115,4 +119,5 @@ + diff --git a/src/Compilers/Core/Portable/PEWriter/InheritedTypeParameter.cs b/src/Compilers/Core/Portable/PEWriter/InheritedTypeParameter.cs index a988641b3ca32..a90fc8b2212b8 100644 --- a/src/Compilers/Core/Portable/PEWriter/InheritedTypeParameter.cs +++ b/src/Compilers/Core/Portable/PEWriter/InheritedTypeParameter.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Reflection.Metadata; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; namespace Microsoft.Cci @@ -307,13 +306,13 @@ public bool IsGenericTypeInstance public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/PEWriter/Members.cs b/src/Compilers/Core/Portable/PEWriter/Members.cs index c1c2cd6e5ecc6..c15988d673d5b 100644 --- a/src/Compilers/Core/Portable/PEWriter/Members.cs +++ b/src/Compilers/Core/Portable/PEWriter/Members.cs @@ -1047,6 +1047,11 @@ public static bool ShouldInclude(this ITypeDefinitionMember member, EmitContext } } + if (method != null && (context.Module.PEEntryPoint == method || context.Module.DebugEntryPoint == method)) + { + return true; + } + return false; } } diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataVisitor.cs b/src/Compilers/Core/Portable/PEWriter/MetadataVisitor.cs index ccb46b02b74f8..f92843a23a2b2 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataVisitor.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataVisitor.cs @@ -3,11 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; -using Roslyn.Utilities; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Emit; +using System.Diagnostics; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Emit.EditAndContinue; namespace Microsoft.Cci diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index da741a04c3d43..73db3eb903cdd 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -1869,7 +1869,7 @@ public PortablePdbBuilder GetPortablePdbBuilder(ImmutableArray typeSystemRo internal void GetEntryPoints(out MethodDefinitionHandle entryPointHandle, out MethodDefinitionHandle debugEntryPointHandle) { - if (IsFullMetadata && !MetadataOnly) + if (IsFullMetadata) { // PE entry point is set for executable programs IMethodReference entryPoint = module.PEEntryPoint; diff --git a/src/Compilers/Core/Portable/PEWriter/MethodDefinitionBase.cs b/src/Compilers/Core/Portable/PEWriter/MethodDefinitionBase.cs index 2ecd2e3d02edf..712124d888ce8 100644 --- a/src/Compilers/Core/Portable/PEWriter/MethodDefinitionBase.cs +++ b/src/Compilers/Core/Portable/PEWriter/MethodDefinitionBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Emit; diff --git a/src/Compilers/Core/Portable/PEWriter/ModifiedTypeReference.cs b/src/Compilers/Core/Portable/PEWriter/ModifiedTypeReference.cs index 5d3d9b805dbe9..5dfc45e136099 100644 --- a/src/Compilers/Core/Portable/PEWriter/ModifiedTypeReference.cs +++ b/src/Compilers/Core/Portable/PEWriter/ModifiedTypeReference.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Reflection.Metadata; +using Microsoft.CodeAnalysis; using Roslyn.Utilities; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; @@ -150,13 +151,13 @@ void IReference.Dispatch(MetadataVisitor visitor) public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/PEWriter/ParameterDefinitionBase.cs b/src/Compilers/Core/Portable/PEWriter/ParameterDefinitionBase.cs index 1eceff4aa72e7..886b50dc0e8be 100644 --- a/src/Compilers/Core/Portable/PEWriter/ParameterDefinitionBase.cs +++ b/src/Compilers/Core/Portable/PEWriter/ParameterDefinitionBase.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; @@ -36,12 +37,12 @@ internal abstract class ParameterDefinitionBase : Cci.IParameterDefinition public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs index 9db1f14698f3a..e6f49eafe857a 100644 --- a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs +++ b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Reflection.Metadata; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Roslyn.Utilities; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; @@ -329,13 +330,13 @@ IDefinition IReference.AsDefinition(EmitContext context) public sealed override bool Equals(object? obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/Compilers/Core/Portable/PEWriter/SequencePoint.cs b/src/Compilers/Core/Portable/PEWriter/SequencePoint.cs index fbe377f8b1775..57e996fd5a0e7 100644 --- a/src/Compilers/Core/Portable/PEWriter/SequencePoint.cs +++ b/src/Compilers/Core/Portable/PEWriter/SequencePoint.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; using Roslyn.Utilities; namespace Microsoft.Cci diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 5ed8ac837d3b4..da1af91a7e677 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -18,6 +18,7 @@ Microsoft.CodeAnalysis.IPropertySymbol.PartialImplementationPart.get -> Microsof Microsoft.CodeAnalysis.IPropertySymbol.IsPartialDefinition.get -> bool Microsoft.CodeAnalysis.ITypeParameterSymbol.AllowsRefLikeType.get -> bool Microsoft.CodeAnalysis.RuntimeCapability.ByRefLikeGenerics = 8 -> Microsoft.CodeAnalysis.RuntimeCapability +override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.ToString() -> string! static Microsoft.CodeAnalysis.GeneratorExtensions.AsIncrementalGenerator(this Microsoft.CodeAnalysis.ISourceGenerator! sourceGenerator) -> Microsoft.CodeAnalysis.IIncrementalGenerator! static Microsoft.CodeAnalysis.GeneratorExtensions.GetGeneratorType(this Microsoft.CodeAnalysis.IIncrementalGenerator! generator) -> System.Type! Microsoft.CodeAnalysis.Compilation.CreatePreprocessingSymbol(string! name) -> Microsoft.CodeAnalysis.IPreprocessingSymbol! diff --git a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs index 93d075bf02263..bcca9e8a5250d 100644 --- a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs +++ b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs @@ -267,7 +267,7 @@ public static int VBForToShiftBits(this SpecialType specialType) case SpecialType.System_Int64: return 63; default: - throw Roslyn.Utilities.ExceptionUtilities.UnexpectedValue(specialType); + throw ExceptionUtilities.UnexpectedValue(specialType); } } diff --git a/src/Compilers/Core/Portable/Text/CompositeText.cs b/src/Compilers/Core/Portable/Text/CompositeText.cs index b4fdc92beec36..41c20d8861fc6 100644 --- a/src/Compilers/Core/Portable/Text/CompositeText.cs +++ b/src/Compilers/Core/Portable/Text/CompositeText.cs @@ -208,7 +208,7 @@ private static void RemoveSplitLineBreaksAndEmptySegments(ArrayBuilder 1) { // Remove empty segments before checking for split line breaks - segments.RemoveWhere(static (s, _, _) => s.Length == 0, default(VoidResult)); + segments.RemoveWhere(static (s, _, _) => s.Length == 0, arg: 0); var splitLineBreakFound = false; for (int i = 1; i < segments.Count; i++) @@ -230,7 +230,7 @@ private static void RemoveSplitLineBreaksAndEmptySegments(ArrayBuilder s.Length == 0, default(VoidResult)); + segments.RemoveWhere(static (s, _, _) => s.Length == 0, arg: 0); } } } diff --git a/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj b/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj index 52bbbb4a27ccb..22d08e4182578 100644 --- a/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj +++ b/src/Compilers/Core/RebuildTest/Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj @@ -7,9 +7,6 @@ true $(NetRoslyn);net472 - - - diff --git a/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs b/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs index 8cd21b15c022b..f017a680afbeb 100644 --- a/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs +++ b/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs @@ -13,6 +13,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; diff --git a/src/Compilers/Test/Core/Mocks/TestMessageProvider.cs b/src/Compilers/Test/Core/Mocks/TestMessageProvider.cs index 008e2eb21e579..b3fa3ea6172e3 100644 --- a/src/Compilers/Test/Core/Mocks/TestMessageProvider.cs +++ b/src/Compilers/Test/Core/Mocks/TestMessageProvider.cs @@ -470,6 +470,8 @@ public override int ERR_InvalidDebugInfo public override int ERR_FunctionPointerTypesInAttributeNotSupported => throw new NotImplementedException(); + public override int ERR_DataSectionStringLiteralHashCollision => throw new NotImplementedException(); + public override int? WRN_ByValArraySizeConstRequired => throw new NotImplementedException(); } } diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index f15d2ed986d08..11d5b56217166 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -37,7 +37,7 @@ public static class Features public const string ChangeSignature = nameof(ChangeSignature); public const string ClassView = nameof(ClassView); public const string Classification = nameof(Classification); - public const string CodeActionsAddAccessibilityModifiers = "CodeActions.AddAccessibilityModifiers"; + public const string CodeActionsAddOrRemoveAccessibilityModifiers = "CodeActions.AddOrRemoveAccessibilityModifiers"; public const string CodeActionsAddAnonymousTypeMemberName = "CodeActions.AddAnonymousTypeMemberName"; public const string CodeActionsAddAwait = "CodeActions.AddAwait"; public const string CodeActionsAddBraces = "CodeActions.AddBraces"; diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index cb0257ff9b975..549049a430208 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -2487,6 +2487,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End If SynthesizedMetadataCompiler.ProcessSynthesizedMembers(Me, moduleBeingBuilt, cancellationToken) + + If moduleBeingBuilt.OutputKind.IsApplication() Then + Dim entryPoint = GetEntryPointAndDiagnostics(cancellationToken) + diagnostics.AddRange(entryPoint.Diagnostics) + If entryPoint.MethodSymbol IsNot Nothing AndAlso Not entryPoint.Diagnostics.HasAnyErrors() Then + moduleBeingBuilt.SetPEEntryPoint(entryPoint.MethodSymbol, diagnostics) + Else + Return False + End If + End If Else ' start generating PDB checksums if we need to emit PDBs If (emittingPdb OrElse moduleBuilder.EmitOptions.InstrumentationKinds.Contains(InstrumentationKind.TestCoverage)) AndAlso diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb index 30f4422a89728..81e2608acb954 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb @@ -123,12 +123,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public NotOverridable Overrides Function Equals(obj As Object) As Boolean ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function Public NotOverridable Overrides Function GetHashCode() As Integer ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb b/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb index 4f3ea0aed9844..dadb28702f577 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/ParameterTypeInformation.vb @@ -50,12 +50,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public Overrides Function Equals(obj As Object) As Boolean ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function Public Overrides Function GetHashCode() As Integer ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Emit/SymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/SymbolAdapter.vb index c7c9551f017da..24f1d61b76320 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SymbolAdapter.vb @@ -154,12 +154,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public NotOverridable Overrides Function Equals(obj As Object) As Boolean ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function Public NotOverridable Overrides Function GetHashCode() As Integer ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb b/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb index e53016af2ca34..0071a89ee6041 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/TypeMemberReference.vb @@ -42,12 +42,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public NotOverridable Overrides Function Equals(obj As Object) As Boolean ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function Public NotOverridable Overrides Function GetHashCode() As Integer ' It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Errors/ErrorFacts.vb b/src/Compilers/VisualBasic/Portable/Errors/ErrorFacts.vb index 0ceb29c88e20a..ca8fcd8f48918 100644 --- a/src/Compilers/VisualBasic/Portable/Errors/ErrorFacts.vb +++ b/src/Compilers/VisualBasic/Portable/Errors/ErrorFacts.vb @@ -21,6 +21,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ERRID.ERR_CannotGotoNonScopeBlocksWithClosure, ERRID.ERR_SymbolDefinedInAssembly ' Update src\Features\VisualBasic\Portable\Diagnostics\LanguageServer\VisualBasicLspBuildOnlyDiagnostics.vb + ' and TestIsBuildOnlyDiagnostic in src\Compilers\VisualBasic\Test\Semantic\Diagnostics\DiagnosticTests.vb ' whenever new values are added here. Return True Case ERRID.Void, diff --git a/src/Compilers/VisualBasic/Portable/Errors/MessageProvider.vb b/src/Compilers/VisualBasic/Portable/Errors/MessageProvider.vb index c600c952fafeb..0e573eaa217bd 100644 --- a/src/Compilers/VisualBasic/Portable/Errors/MessageProvider.vb +++ b/src/Compilers/VisualBasic/Portable/Errors/MessageProvider.vb @@ -594,6 +594,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Public Overrides ReadOnly Property ERR_DataSectionStringLiteralHashCollision As Integer + Get + Throw ExceptionUtilities.Unreachable + End Get + End Property + ' Generators Public Overrides ReadOnly Property WRN_GeneratorFailedDuringInitialization As Integer Get diff --git a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb index 3d6408f888fcf..89ce274086a30 100644 --- a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb @@ -4,6 +4,7 @@ Imports System.Collections.Concurrent Imports System.Collections.Immutable +Imports System.Threading Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols @@ -43,9 +44,9 @@ Namespace Microsoft.CodeAnalysis.Operations End If If _lazyPlaceholderToParentMap Is Nothing Then - Threading.Interlocked.CompareExchange(_lazyPlaceholderToParentMap, - New ConcurrentDictionary(Of BoundValuePlaceholderBase, BoundNode)(concurrencyLevel:=2, capacity:=10, comparer:=ReferenceEqualityComparer.Instance), - Nothing) + Interlocked.CompareExchange(_lazyPlaceholderToParentMap, + New ConcurrentDictionary(Of BoundValuePlaceholderBase, BoundNode)(concurrencyLevel:=2, capacity:=10, comparer:=ReferenceEqualityComparer.Instance), + Nothing) End If Dim knownParent = _lazyPlaceholderToParentMap.GetOrAdd(placeholderOpt, parent) @@ -445,7 +446,7 @@ Namespace Microsoft.CodeAnalysis.Operations If(boundCall.ReceiverOpt?.Kind <> BoundKind.MyClassReference, False) Dim boundReceiver As BoundExpression = If(boundCall.ReceiverOpt, boundCall.MethodGroupOpt?.ReceiverOpt) - Dim receiver as IOperation = CreateReceiverOperation(boundReceiver, targetMethod) + Dim receiver As IOperation = CreateReceiverOperation(boundReceiver, targetMethod) Dim arguments As ImmutableArray(Of IArgumentOperation) = DeriveArguments(boundCall) Dim syntax As SyntaxNode = boundCall.Syntax @@ -471,7 +472,7 @@ Namespace Microsoft.CodeAnalysis.Operations End Function Private Function CreateBoundArrayAccessOperation(boundArrayAccess As BoundArrayAccess) As IArrayElementReferenceOperation - Dim arrayReference as IOperation = Create(boundArrayAccess.Expression) + Dim arrayReference As IOperation = Create(boundArrayAccess.Expression) Dim indices = CreateFromArray(Of BoundExpression, IOperation)(boundArrayAccess.Indices) Dim syntax As SyntaxNode = boundArrayAccess.Syntax Dim type As ITypeSymbol = boundArrayAccess.Type @@ -819,7 +820,7 @@ Namespace Microsoft.CodeAnalysis.Operations Debug.Assert(boundObjectCreationExpression.ConstructorOpt IsNot Nothing OrElse boundObjectCreationExpression.Arguments.IsEmpty()) Dim constructor As IMethodSymbol = boundObjectCreationExpression.ConstructorOpt Dim initializer As IObjectOrCollectionInitializerOperation = DirectCast(Create(boundObjectCreationExpression.InitializerOpt), IObjectOrCollectionInitializerOperation) - Dim arguments as ImmutableArray(Of IArgumentOperation) = DeriveArguments(boundObjectCreationExpression) + Dim arguments As ImmutableArray(Of IArgumentOperation) = DeriveArguments(boundObjectCreationExpression) Dim syntax As SyntaxNode = boundObjectCreationExpression.Syntax Dim type As ITypeSymbol = boundObjectCreationExpression.Type @@ -881,7 +882,7 @@ Namespace Microsoft.CodeAnalysis.Operations Dim instance As IOperation = CreateReceiverOperation( If(boundPropertyAccess.ReceiverOpt, boundPropertyAccess.PropertyGroupOpt?.ReceiverOpt), [property]) - Dim arguments as ImmutableArray(Of IArgumentOperation) = DeriveArguments(boundPropertyAccess) + Dim arguments As ImmutableArray(Of IArgumentOperation) = DeriveArguments(boundPropertyAccess) Dim syntax As SyntaxNode = boundPropertyAccess.Syntax Dim type As ITypeSymbol = boundPropertyAccess.Type @@ -1058,9 +1059,9 @@ Namespace Microsoft.CodeAnalysis.Operations End Function Private Function CreateBoundIfStatementOperation(boundIfStatement As BoundIfStatement) As IConditionalOperation - Dim condition as IOperation = Create(boundIfStatement.Condition) - Dim whenTrue as IOperation = Create(boundIfStatement.Consequence) - Dim whenFalse as IOperation = Create(boundIfStatement.AlternativeOpt) + Dim condition As IOperation = Create(boundIfStatement.Condition) + Dim whenTrue As IOperation = Create(boundIfStatement.Consequence) + Dim whenFalse As IOperation = Create(boundIfStatement.AlternativeOpt) Dim syntax As SyntaxNode = boundIfStatement.Syntax Dim type As ITypeSymbol = Nothing Dim constantValue As ConstantValue = Nothing @@ -1281,7 +1282,7 @@ Namespace Microsoft.CodeAnalysis.Operations End Function Private Function CreateBoundCatchBlockOperation(boundCatchBlock As BoundCatchBlock) As ICatchClauseOperation - Dim exceptionDeclarationOrExpression as IOperation = CreateBoundCatchBlockExceptionDeclarationOrExpression(boundCatchBlock) + Dim exceptionDeclarationOrExpression As IOperation = CreateBoundCatchBlockExceptionDeclarationOrExpression(boundCatchBlock) Dim filter As IOperation = Create(boundCatchBlock.ExceptionFilterOpt) Dim handler As IBlockOperation = DirectCast(Create(boundCatchBlock.Body), IBlockOperation) Dim exceptionType As ITypeSymbol = If(boundCatchBlock.ExceptionSourceOpt?.Type, DirectCast(_semanticModel.Compilation, VisualBasicCompilation).GetWellKnownType(WellKnownType.System_Exception)) @@ -1417,8 +1418,8 @@ Namespace Microsoft.CodeAnalysis.Operations DirectCast(_semanticModel.Compilation.GetSpecialType(SpecialType.System_Boolean), TypeSymbol), SynthesizedLocalKind.LockTaken, syntaxOpt:=boundSyncLockStatement.LockExpression.Syntax)) - Dim lockedValue as IOperation = Create(boundSyncLockStatement.LockExpression) - Dim body as IOperation = Create(boundSyncLockStatement.Body) + Dim lockedValue As IOperation = Create(boundSyncLockStatement.LockExpression) + Dim body As IOperation = Create(boundSyncLockStatement.Body) Dim syntax As SyntaxNode = boundSyncLockStatement.Syntax Dim isImplicit As Boolean = boundSyncLockStatement.WasCompilerGenerated Return New LockOperation(lockedValue, body, lockTakenSymbol, _semanticModel, syntax, isImplicit) @@ -1664,7 +1665,7 @@ Namespace Microsoft.CodeAnalysis.Operations GetSpecialTypeMember(SpecialMember.System_Nullable_T_GetValueOrDefault), MethodSymbol) If method IsNot Nothing Then - Dim receiver as IOperation = CreateReceiverOperation(boundNullableIsTrueOperator.Operand, method) + Dim receiver As IOperation = CreateReceiverOperation(boundNullableIsTrueOperator.Operand, method) Return New InvocationOperation(method.AsMember(DirectCast(boundNullableIsTrueOperator.Operand.Type, NamedTypeSymbol)), constrainedToType:=Nothing, receiver, isVirtual:=False, diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEPropertySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEPropertySymbol.vb index 5c7571f2bc81b..0264bc6c8e9e2 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEPropertySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PEPropertySymbol.vb @@ -4,14 +4,14 @@ Imports System.Collections.Immutable Imports System.Globalization -Imports System.Threading Imports System.Reflection Imports System.Reflection.Metadata +Imports System.Reflection.Metadata.Ecma335 +Imports System.Runtime.CompilerServices +Imports System.Threading Imports Microsoft.Cci Imports Microsoft.CodeAnalysis.PooledObjects -Imports System.Reflection.Metadata.Ecma335 Imports Microsoft.CodeAnalysis.VisualBasic.Emit -Imports System.Runtime.CompilerServices Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE @@ -270,7 +270,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE ''' Friend Sub SetIsWithEvents(value As Boolean) Dim newValue = If(value, ThreeState.True, ThreeState.False) - Dim origValue = Threading.Interlocked.CompareExchange(Me._isWithEvents, newValue, ThreeState.Unknown) + Dim origValue = Interlocked.CompareExchange(Me._isWithEvents, newValue, ThreeState.Unknown) Debug.Assert(origValue = ThreeState.Unknown OrElse origValue = newValue, "Tried changing already known IsWithEvent value.") End Sub diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb index 6c0c7d59ddfda..3ff5fc6e14437 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Retargeting/RetargetingSymbolTranslator.vb @@ -3,15 +3,12 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Concurrent -Imports System.Collections.Generic Imports System.Collections.Immutable -Imports System.Collections.ObjectModel +Imports System.Threading Imports Microsoft.Cci Imports Microsoft.CodeAnalysis.PooledObjects -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting @@ -649,7 +646,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting Dim newTypeModifiers = RetargetModifiers(oldTypeModifiers, modifiersHaveChanged) Dim newRefModifiers = RetargetModifiers(oldRefModifiers, modifiersHaveChanged) - Threading.Interlocked.CompareExchange(lazyCustomModifiers, CustomModifiersTuple.Create(newTypeModifiers, newRefModifiers), Nothing) + Interlocked.CompareExchange(lazyCustomModifiers, CustomModifiersTuple.Create(newTypeModifiers, newRefModifiers), Nothing) End If Return lazyCustomModifiers diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol_ComClass.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol_ComClass.vb index 16d84b989c8db..eef1641c6bcca 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol_ComClass.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamedTypeSymbol_ComClass.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.Runtime.InteropServices +Imports System.Threading Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols @@ -763,7 +764,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property - Friend Overrides Sub GenerateDeclarationErrors(cancellationToken As Threading.CancellationToken) + Friend Overrides Sub GenerateDeclarationErrors(cancellationToken As CancellationToken) Throw ExceptionUtilities.Unreachable End Sub diff --git a/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb b/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb index 489f5ef45ae5c..0b6f776f7b276 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Attributes/AttributeTests.vb @@ -1875,6 +1875,7 @@ End Class Public Sub AttributeArgumentAsEnumFromMetadata() Dim metadata1 = VisualBasicCompilation.Create("bar.dll", + options:=TestOptions.DebugDll, references:={MscorlibRef}, syntaxTrees:={Parse("Public Enum Bar : Baz : End Enum")}).EmitToArray(New EmitOptions(metadataOnly:=True)) @@ -1882,6 +1883,7 @@ End Class Dim metadata2 = VisualBasicCompilation.Create( "goo.dll", + options:=TestOptions.DebugDll, references:={MscorlibRef, ref1}, syntaxTrees:={ VisualBasicSyntaxTree.ParseText( + Public Sub EmitMetadataOnly_Exe() + CompileAndVerify( + + +Module Program + Sub Main() + System.Console.WriteLine("a") + End Sub +End Module + + , + options:=TestOptions.ReleaseExe.WithMetadataImportOptions(MetadataImportOptions.All), + emitOptions:=EmitOptions.Default.WithEmitMetadataOnly(True), + symbolValidator:=Sub(m) + Assert.NotEqual(0, m.GetMetadata().Module.PEReaderOpt.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress) + Dim main = m.GlobalNamespace.GetMember(Of MethodSymbol)("Program.Main") + Assert.Equal(Accessibility.Public, main.DeclaredAccessibility) + End Sub + ).VerifyDiagnostics() + End Sub + + + Public Sub EmitMetadataOnly_Exe_AsyncMain() + Dim emitResult = CreateCompilation( + + +Imports System.Threading.Tasks +Module Program + Public Async Function Main() As Task + Await Task.Yield() + System.Console.WriteLine("a") + End Function +End Module + + , + options:=TestOptions.ReleaseExe, + assemblyName:="MyLib" + ).Emit(New MemoryStream(), options:=EmitOptions.Default.WithEmitMetadataOnly(True)) + Assert.False(emitResult.Success) + emitResult.Diagnostics.AssertTheseDiagnostics( +BC30737: No accessible 'Main' method with an appropriate signature was found in 'MyLib'. + ) + End Sub + + + Public Sub EmitMetadataOnly_Exe_AsyncMain_Void() + Dim emitResult = CreateCompilation( + + +Imports System.Threading.Tasks +Module Program + Public Async Sub Main() + Await Task.Yield() + System.Console.WriteLine("a") + End Sub +End Module + + , + options:=TestOptions.ReleaseExe, + assemblyName:="MyLib" + ).Emit(New MemoryStream(), options:=EmitOptions.Default.WithEmitMetadataOnly(True)) + Assert.False(emitResult.Success) + emitResult.Diagnostics.AssertTheseDiagnostics( +BC36934: The 'Main' method cannot be marked 'Async'. + Public Async Sub Main() + ~~~~ + ) + End Sub + + + Public Sub EmitMetadataOnly_Exe_NoMain() + Dim emitResult = CreateCompilation( + + +Module Program +End Module + + , + options:=TestOptions.ReleaseExe, + assemblyName:="MyLib" + ).Emit(New MemoryStream(), options:=EmitOptions.Default.WithEmitMetadataOnly(True)) + Assert.False(emitResult.Success) + emitResult.Diagnostics.AssertTheseDiagnostics( +BC30420: 'Sub Main' was not found in 'MyLib'. + ) + End Sub + + + Public Sub EmitMetadataOnly_Exe_FriendMain_ExcludePrivateMembers() + CompileAndVerify( + + +Module Program + Friend Sub Main() + End Sub +End Module + + , + options:=TestOptions.ReleaseExe.WithMetadataImportOptions(MetadataImportOptions.All), + emitOptions:=EmitOptions.Default.WithEmitMetadataOnly(True).WithIncludePrivateMembers(False), + symbolValidator:=Sub(m) + Assert.NotEqual(0, m.GetMetadata().Module.PEReaderOpt.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress) + Dim main = m.GlobalNamespace.GetMember(Of MethodSymbol)("Program.Main") + Assert.Equal(Accessibility.Internal, main.DeclaredAccessibility) + End Sub + ).VerifyDiagnostics() + End Sub + + + Public Sub ExcludePrivateMembers_FriendMain() + Using peStream As New MemoryStream() + Using metadataStream As New MemoryStream() + Dim comp = CreateCompilation( + + +Module Program + Friend Sub Main() + End Sub +End Module + + , + options:=TestOptions.ReleaseExe + ) + Dim emitResult = comp.Emit( + peStream:=peStream, + metadataPEStream:=metadataStream, + options:=EmitOptions.Default.WithIncludePrivateMembers(False)) + Assert.True(emitResult.Success) + emitResult.Diagnostics.Verify() + + Verify(peStream) + Verify(metadataStream) + + CompileAndVerify(comp).VerifyDiagnostics() + End Using + End Using + End Sub + + Private Shared Sub Verify(stream As Stream) + stream.Position = 0 + Assert.NotEqual(0, New PEHeaders(stream).CorHeader.EntryPointTokenOrRelativeVirtualAddress) + + stream.Position = 0 + Dim reference = AssemblyMetadata.CreateFromStream(stream).GetReference() + Dim comp = CreateCompilation("", references:={reference}, options:=TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)) + Dim main = comp.GetMember(Of MethodSymbol)("Program.Main") + Assert.Equal(Accessibility.Internal, main.DeclaredAccessibility) + End Sub + + + Public Sub ExcludePrivateMembers_DebugEntryPoint() + Using peStream As New MemoryStream() + Using metadataStream As New MemoryStream() + Dim comp = CreateCompilation( + + +Module Program + Private Sub M1() + End Sub + Private Sub M2() + End Sub +End Module + + ).VerifyDiagnostics() + Dim emitResult = comp.Emit( + peStream:=peStream, + metadataPEStream:=metadataStream, + debugEntryPoint:=comp.GetMember(Of MethodSymbol)("Program.M1"), + options:=EmitOptions.Default.WithIncludePrivateMembers(False)) + Assert.True(emitResult.Success) + emitResult.Diagnostics.Verify() + + ' M1 should be emitted (it's the debug entry-point), M2 shouldn't (private members are excluded). + metadataStream.Position = 0 + Dim reference = AssemblyMetadata.CreateFromStream(metadataStream).GetReference() + Dim comp2 = CreateCompilation("", references:={reference}, + options:=TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)) + Dim m1 = comp2.GetMember(Of MethodSymbol)("Program.M1") + Assert.Equal(Accessibility.Private, m1.DeclaredAccessibility) + Assert.Null(comp2.GetMember(Of MethodSymbol)("Program.M2")) + End Using + End Using + End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Emit/Semantics/StaticLocalsSemanticTests.vb b/src/Compilers/VisualBasic/Test/Emit/Semantics/StaticLocalsSemanticTests.vb index b0e00bafb735d..2f3b931ab752d 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Semantics/StaticLocalsSemanticTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Semantics/StaticLocalsSemanticTests.vb @@ -2,14 +2,12 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests.Emit Imports Roslyn.Test.Utilities -Imports Xunit Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests @@ -346,8 +344,8 @@ End Class Public Sub Semantic_StaticLocalDeclaration_LateBound() ' test late bind ' call ToString() on object defeat the purpose - Dim currCulture = Threading.Thread.CurrentThread.CurrentCulture - Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture + Dim currCulture = Thread.CurrentThread.CurrentCulture + Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture Try 'Declare static local which is late bound Dim compilationDef = CreateCompilationWithMscorlib40AndVBRuntime( @@ -391,7 +389,7 @@ After:5.5]]>) Catch ex As Exception Assert.Null(ex) Finally - Threading.Thread.CurrentThread.CurrentCulture = currCulture + Thread.CurrentThread.CurrentCulture = currCulture End Try End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/InterpolatedStringTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/InterpolatedStringTests.vb index da29a53e273c8..5eeaf3b59a046 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/InterpolatedStringTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/InterpolatedStringTests.vb @@ -1,6 +1,8 @@ ' Licensed to the .NET Foundation under one or more agreements. ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. + +Imports System.Threading Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxFacts @@ -555,7 +557,7 @@ BC42322: Runtime errors might occur when converting 'String' to 'IFormattable'. Public Sub InvariantCulture() - Dim previousCulture = Threading.Thread.CurrentThread.CurrentCulture + Dim previousCulture = Thread.CurrentThread.CurrentCulture Dim verifier = CompileAndVerify( @@ -584,7 +586,7 @@ End Module , expectedOutput:="1,51,51,51.5") - Assert.Equal(previousCulture, Threading.Thread.CurrentThread.CurrentCulture) + Assert.Equal(previousCulture, Thread.CurrentThread.CurrentCulture) End Sub diff --git a/src/Compilers/VisualBasic/Test/Symbol/DocumentationComments/DocCommentTests.vb b/src/Compilers/VisualBasic/Test/Symbol/DocumentationComments/DocCommentTests.vb index 1757a5bb79bf9..8d2f0944c2544 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/DocumentationComments/DocCommentTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/DocumentationComments/DocCommentTests.vb @@ -3,15 +3,15 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.IO +Imports System.Text +Imports System.Threading +Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports System.Xml.Linq -Imports System.Text -Imports System.IO -Imports Roslyn.Test.Utilities Imports Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation +Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Public Class DocCommentTests @@ -12129,8 +12129,8 @@ xmlDoc) If preferred Is Nothing Then ensureEnglishUICulture = False Else - saveUICulture = Threading.Thread.CurrentThread.CurrentUICulture - Threading.Thread.CurrentThread.CurrentUICulture = preferred + saveUICulture = Thread.CurrentThread.CurrentUICulture + Thread.CurrentThread.CurrentUICulture = preferred End If End If @@ -12138,7 +12138,7 @@ xmlDoc) diagnostics = compilation.GetDiagnostics(CompilationStage.Compile).ToArray() Finally If ensureEnglishUICulture Then - Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture + Thread.CurrentThread.CurrentUICulture = saveUICulture End If End Try @@ -12170,8 +12170,8 @@ xmlDoc) If preferred Is Nothing Then ensureEnglishUICulture = False Else - saveUICulture = Threading.Thread.CurrentThread.CurrentUICulture - Threading.Thread.CurrentThread.CurrentUICulture = preferred + saveUICulture = Thread.CurrentThread.CurrentUICulture + Thread.CurrentThread.CurrentUICulture = preferred End If End If @@ -12179,7 +12179,7 @@ xmlDoc) emitResult = compilation.Emit(output, xmlDocumentationStream:=xml) Finally If ensureEnglishUICulture Then - Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture + Thread.CurrentThread.CurrentUICulture = saveUICulture End If End Try diff --git a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.Package.csproj b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.Package.csproj index cbcddc860d25f..e77a382a6ca5a 100644 --- a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.Package.csproj +++ b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.Package.csproj @@ -20,10 +20,6 @@ $(NoWarn);NU5128 - - - - @@ -46,11 +42,13 @@ - <_File Remove="@(_File)"/> + <_File Remove="@(_File)" /> <_File Include="$(MSBuildProjectDirectory)\**\*.resx" TargetDir="contentFiles/$(_LanguageDirName)/$(TargetFramework)" BuildAction="EmbeddedResource" /> <_File Include="$(MSBuildProjectDirectory)\**\*.xlf" TargetDir="contentFiles/$(_LanguageDirName)/$(TargetFramework)" BuildAction="None" /> - + + + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/Syntax/CollectionBuilderAttribute.cs b/src/Dependencies/Contracts/CollectionBuilderAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/Syntax/CollectionBuilderAttribute.cs rename to src/Dependencies/Contracts/CollectionBuilderAttribute.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/CompilerFeatureRequiredAttribute.cs b/src/Dependencies/Contracts/CompilerFeatureRequiredAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/CompilerFeatureRequiredAttribute.cs rename to src/Dependencies/Contracts/CompilerFeatureRequiredAttribute.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.InterpolatedStringHandlers.cs b/src/Dependencies/Contracts/Contract.InterpolatedStringHandlers.cs similarity index 96% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.InterpolatedStringHandlers.cs rename to src/Dependencies/Contracts/Contract.InterpolatedStringHandlers.cs index 84469add46cbf..628aa3457f573 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.InterpolatedStringHandlers.cs +++ b/src/Dependencies/Contracts/Contract.InterpolatedStringHandlers.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if !MICROSOFT_CODEANALYSIS_CONTRACTS_NO_CONTRACT + using System.Runtime.CompilerServices; using System.Text; #pragma warning disable IDE0060 // Remove unused parameter - https://github.com/dotnet/roslyn/issues/58168 -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis; internal static partial class Contract { @@ -65,3 +67,5 @@ public ThrowIfNullInterpolatedStringHandler(int literalLength, int formattedCoun public string GetFormattedText() => _stringBuilder.ToString(); } } + +#endif diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs b/src/Dependencies/Contracts/Contract.cs similarity index 98% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs rename to src/Dependencies/Contracts/Contract.cs index db14883bd1652..489ffe5bea9de 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs +++ b/src/Dependencies/Contracts/Contract.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if !MICROSOFT_CODEANALYSIS_CONTRACTS_NO_CONTRACT + using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis; internal static partial class Contract { @@ -157,3 +159,5 @@ public static void Fail(string message = "Unexpected", [CallerLineNumber] int li throw new InvalidOperationException($"{message} - file {fileName} line {lineNumber}"); } } + +#endif diff --git a/src/Compilers/Core/Portable/InternalUtilities/ExceptionUtilities.cs b/src/Dependencies/Contracts/ExceptionUtilities.cs similarity index 98% rename from src/Compilers/Core/Portable/InternalUtilities/ExceptionUtilities.cs rename to src/Dependencies/Contracts/ExceptionUtilities.cs index e2c787f7f2eb4..983341521a3c3 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ExceptionUtilities.cs +++ b/src/Dependencies/Contracts/ExceptionUtilities.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using System.Threading; -namespace Roslyn.Utilities +namespace Microsoft.CodeAnalysis { internal static class ExceptionUtilities { diff --git a/src/Compilers/Core/Portable/InternalUtilities/ExperimentalAttribute.cs b/src/Dependencies/Contracts/ExperimentalAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/ExperimentalAttribute.cs rename to src/Dependencies/Contracts/ExperimentalAttribute.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/IReadOnlySet.cs b/src/Dependencies/Contracts/IReadOnlySet.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/IReadOnlySet.cs rename to src/Dependencies/Contracts/IReadOnlySet.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/InterpolatedStringHandlerArgumentAttribute.cs b/src/Dependencies/Contracts/InterpolatedStringHandlerArgumentAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/InterpolatedStringHandlerArgumentAttribute.cs rename to src/Dependencies/Contracts/InterpolatedStringHandlerArgumentAttribute.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/InterpolatedStringHandlerAttribute.cs b/src/Dependencies/Contracts/InterpolatedStringHandlerAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/InterpolatedStringHandlerAttribute.cs rename to src/Dependencies/Contracts/InterpolatedStringHandlerAttribute.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/IsExternalInit.cs b/src/Dependencies/Contracts/IsExternalInit.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/IsExternalInit.cs rename to src/Dependencies/Contracts/IsExternalInit.cs diff --git a/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.Package.csproj b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.Package.csproj new file mode 100644 index 0000000000000..d00764e8b32d7 --- /dev/null +++ b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.Package.csproj @@ -0,0 +1,26 @@ + + + + + $(NetRoslynSourceBuild);netstandard2.0 + false + false + none + false + true + + + true + true + Microsoft.CodeAnalysis.Contracts + false + + Package containing sources of Microsoft .NET Compiler Platform ("Roslyn") contract and polyfill types. + + + $(NoWarn);NU5128 + + + + + \ No newline at end of file diff --git a/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.projitems b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.projitems new file mode 100644 index 0000000000000..b98b83ea55567 --- /dev/null +++ b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.projitems @@ -0,0 +1,32 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + BD974609-C68B-4BE6-9682-EB132462B50D + + + Microsoft.CodeAnalysis.Contracts + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.shproj b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.shproj new file mode 100644 index 0000000000000..e6fb451a37453 --- /dev/null +++ b/src/Dependencies/Contracts/Microsoft.CodeAnalysis.Contracts.shproj @@ -0,0 +1,14 @@ + + + + + BD974609-C68B-4BE6-9682-EB132462B50D + 14.0 + + + + + + + + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/InternalUtilities/NonCopyableAttribute.cs b/src/Dependencies/Contracts/NonCopyableAttribute.cs similarity index 53% rename from src/Compilers/Core/Portable/InternalUtilities/NonCopyableAttribute.cs rename to src/Dependencies/Contracts/NonCopyableAttribute.cs index edaee07bae0cb..a5efff996a18a 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/NonCopyableAttribute.cs +++ b/src/Dependencies/Contracts/NonCopyableAttribute.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -namespace Roslyn.Utilities +namespace Microsoft.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.GenericParameter)] +internal sealed class NonCopyableAttribute : Attribute { - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.GenericParameter)] - internal sealed class NonCopyableAttribute : Attribute - { - } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/NonDefaultableAttribute.cs b/src/Dependencies/Contracts/NonDefaultableAttribute.cs similarity index 53% rename from src/Compilers/Core/Portable/InternalUtilities/NonDefaultableAttribute.cs rename to src/Dependencies/Contracts/NonDefaultableAttribute.cs index a0d14a00b4b5b..c89b6aed40f05 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/NonDefaultableAttribute.cs +++ b/src/Dependencies/Contracts/NonDefaultableAttribute.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -namespace Roslyn.Utilities +namespace Microsoft.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.GenericParameter)] +internal sealed class NonDefaultableAttribute : Attribute { - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.GenericParameter)] - internal sealed class NonDefaultableAttribute : Attribute - { - } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/NullableAttributes.cs b/src/Dependencies/Contracts/NullableAttributes.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/NullableAttributes.cs rename to src/Dependencies/Contracts/NullableAttributes.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/RequiredMemberAttribute.cs b/src/Dependencies/Contracts/RequiredMemberAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/RequiredMemberAttribute.cs rename to src/Dependencies/Contracts/RequiredMemberAttribute.cs diff --git a/src/Compilers/Core/Portable/InternalUtilities/SetsRequiredMembersAttribute.cs b/src/Dependencies/Contracts/SetsRequiredMembersAttribute.cs similarity index 100% rename from src/Compilers/Core/Portable/InternalUtilities/SetsRequiredMembersAttribute.cs rename to src/Dependencies/Contracts/SetsRequiredMembersAttribute.cs diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index c76055bead00d..35ab00e03a50b 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -16,6 +16,9 @@ namespace Microsoft.CodeAnalysis.PooledObjects [DebuggerDisplay("Count = {Count,nq}")] [DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))] internal sealed partial class ArrayBuilder : IReadOnlyCollection, IReadOnlyList, ICollection +#if !MICROSOFT_CODEANALYSIS_POOLEDOBJECTS_NO_POOLED_DISPOSER + , IPooled +#endif { /// /// See for an explanation of this constant value. @@ -720,5 +723,49 @@ public ImmutableArray SelectDistinct(Func selector) set.Free(); return result.ToImmutableAndFree(); } + +#if !MICROSOFT_CODEANALYSIS_POOLEDOBJECTS_NO_POOLED_DISPOSER + + private static readonly ObjectPool> s_keepLargeInstancesPool = CreatePool(); + + public static PooledDisposer> GetInstance(out ArrayBuilder instance) + => GetInstance(discardLargeInstances: true, out instance); + + public static PooledDisposer> GetInstance(int capacity, out ArrayBuilder instance) + { + instance = GetInstance(capacity); + return new PooledDisposer>(instance); + } + + public static PooledDisposer> GetInstance(int capacity, T fillWithValue, out ArrayBuilder instance) + { + instance = GetInstance(capacity, fillWithValue); + return new PooledDisposer>(instance); + } + + public static PooledDisposer> GetInstance(bool discardLargeInstances, out ArrayBuilder instance) + { + // If we're discarding large instances (the default behavior), then just use the normal pool. If we're not, use + // a specific pool so that *other* normal callers don't accidentally get it and discard it. + instance = discardLargeInstances ? GetInstance() : s_keepLargeInstancesPool.Allocate(); + return new PooledDisposer>(instance, discardLargeInstances); + } + + void IPooled.Free(bool discardLargeInstances) + { + // If we're discarding large instances, use the default behavior (which already does that). Otherwise, always + // clear and free the instance back to its originating pool. + if (discardLargeInstances) + { + Free(); + } + else + { + this.Clear(); + _pool?.Free(this); + } + } + +#endif } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/IPooled.cs b/src/Dependencies/PooledObjects/IPooled.cs similarity index 86% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/IPooled.cs rename to src/Dependencies/PooledObjects/IPooled.cs index 6a0d0d14d4128..0b687f3f3e697 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/IPooled.cs +++ b/src/Dependencies/PooledObjects/IPooled.cs @@ -6,5 +6,5 @@ namespace Microsoft.CodeAnalysis.PooledObjects; internal interface IPooled { - void Free(); + void Free(bool discardLargeInstances); } diff --git a/src/Dependencies/PooledObjects/Microsoft.CodeAnalysis.PooledObjects.projitems b/src/Dependencies/PooledObjects/Microsoft.CodeAnalysis.PooledObjects.projitems index 97a2e3f5ca08f..3b563ade211f9 100644 --- a/src/Dependencies/PooledObjects/Microsoft.CodeAnalysis.PooledObjects.projitems +++ b/src/Dependencies/PooledObjects/Microsoft.CodeAnalysis.PooledObjects.projitems @@ -12,9 +12,11 @@ + + diff --git a/src/Dependencies/PooledObjects/PooledDisposer.cs b/src/Dependencies/PooledObjects/PooledDisposer.cs new file mode 100644 index 0000000000000..e497c4dbd16e9 --- /dev/null +++ b/src/Dependencies/PooledObjects/PooledDisposer.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !MICROSOFT_CODEANALYSIS_POOLEDOBJECTS_NO_POOLED_DISPOSER +using System; + +namespace Microsoft.CodeAnalysis.PooledObjects; + +internal readonly partial struct PooledDisposer( + TPoolable instance, + bool discardLargeInstances = true) : IDisposable + where TPoolable : class, IPooled +{ + void IDisposable.Dispose() + => instance?.Free(discardLargeInstances); +} +#endif diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs b/src/Dependencies/Threading/AsyncBatchingWorkQueue`0.cs similarity index 91% rename from src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs rename to src/Dependencies/Threading/AsyncBatchingWorkQueue`0.cs index fd21607855f71..db94f6efbc8e7 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`0.cs +++ b/src/Dependencies/Threading/AsyncBatchingWorkQueue`0.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis.Threading; /// internal sealed class AsyncBatchingWorkQueue( @@ -22,5 +22,5 @@ private static Func, CancellationToken, Value => (items, ct) => processBatchAsync(ct); public void AddWork(bool cancelExistingWork = false) - => base.AddWork(default(VoidResult), cancelExistingWork); + => AddWork(default(VoidResult), cancelExistingWork); } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs b/src/Dependencies/Threading/AsyncBatchingWorkQueue`1.cs similarity index 97% rename from src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs rename to src/Dependencies/Threading/AsyncBatchingWorkQueue`1.cs index 98aa376ccf429..a14aebc098cb9 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`1.cs +++ b/src/Dependencies/Threading/AsyncBatchingWorkQueue`1.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis.Threading; /// internal class AsyncBatchingWorkQueue( diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs b/src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs similarity index 97% rename from src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs rename to src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs index db07db90e48bd..4844a6f60c807 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs +++ b/src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs @@ -12,7 +12,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis.Threading; /// /// A queue where items can be added to to be processed in batches after some delay has passed. When processing @@ -140,10 +140,16 @@ public void CancelExistingWork() public void AddWork(TItem item, bool cancelExistingWork = false) { - using var _ = ArrayBuilder.GetInstance(out var items); - items.Add(item); - - AddWork(items, cancelExistingWork); + var items = ArrayBuilder.GetInstance(); + try + { + items.Add(item); + AddWork(items, cancelExistingWork); + } + finally + { + items.Free(); + } } public void AddWork(IEnumerable items, bool cancelExistingWork = false) @@ -254,9 +260,7 @@ void AddItemsToBatch(IEnumerable items) var batchResultTask = _processBatchAsync(nextBatch, batchCancellationToken).Preserve(); await batchResultTask.NoThrowAwaitableInternal(false); if (batchResultTask.IsCompletedSuccessfully) - { return batchResultTask.Result; - } else if (batchResultTask.IsCanceled && !_entireQueueCancellationToken.IsCancellationRequested) { // Don't bubble up cancellation to the queue for the nested batch cancellation. Just because we decided @@ -265,8 +269,11 @@ void AddItemsToBatch(IEnumerable items) } else { + Contract.ThrowIfFalse(batchResultTask.IsCompleted); + // Realize the completed result to force the exception to be thrown. - batchResultTask.VerifyCompleted(); + batchResultTask.GetAwaiter().GetResult(); + throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Workspaces/Core/Portable/Utilities/CancellationSeries.cs b/src/Dependencies/Threading/CancellationSeries.cs similarity index 99% rename from src/Workspaces/Core/Portable/Utilities/CancellationSeries.cs rename to src/Dependencies/Threading/CancellationSeries.cs index bddcaf175195f..1a477841243e2 100644 --- a/src/Workspaces/Core/Portable/Utilities/CancellationSeries.cs +++ b/src/Dependencies/Threading/CancellationSeries.cs @@ -11,7 +11,7 @@ using System; using System.Threading; -namespace Roslyn.Utilities; +namespace Microsoft.CodeAnalysis.Threading; /// /// Produces a series of objects such that requesting a new token diff --git a/src/Compilers/Core/Portable/InternalUtilities/ConfiguredYieldAwaitable.cs b/src/Dependencies/Threading/ConfiguredYieldAwaitable.cs similarity index 98% rename from src/Compilers/Core/Portable/InternalUtilities/ConfiguredYieldAwaitable.cs rename to src/Dependencies/Threading/ConfiguredYieldAwaitable.cs index 5bfa632d7488f..80502b3fbc1fc 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ConfiguredYieldAwaitable.cs +++ b/src/Dependencies/Threading/ConfiguredYieldAwaitable.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Roslyn.Utilities +namespace Microsoft.CodeAnalysis.Threading { /// /// A custom awaiter that supports for diff --git a/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.Package.csproj b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.Package.csproj new file mode 100644 index 0000000000000..ab9f373d8fec6 --- /dev/null +++ b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.Package.csproj @@ -0,0 +1,33 @@ + + + + + netstandard2.0 + false + none + false + true + + + true + true + Microsoft.CodeAnalysis.Threading + false + + Package containing sources of Microsoft .NET Compiler Platform ("Roslyn") threading utilities. + + + $(NoWarn);NU5128 + + + + + + + + + + + + + diff --git a/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.projitems b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.projitems new file mode 100644 index 0000000000000..1e6df17045318 --- /dev/null +++ b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.projitems @@ -0,0 +1,30 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 967723E8-4FDD-447B-99F6-4F8C47CB5433 + + + Microsoft.CodeAnalysis.Threading + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.shproj b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.shproj new file mode 100644 index 0000000000000..d47a9e3d50b06 --- /dev/null +++ b/src/Dependencies/Threading/Microsoft.CodeAnalysis.Threading.shproj @@ -0,0 +1,14 @@ + + + + + 967723E8-4FDD-447B-99F6-4F8C47CB5433 + 14.0 + + + + + + + + \ No newline at end of file diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs b/src/Dependencies/Threading/TaskExtensions.cs similarity index 95% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs rename to src/Dependencies/Threading/TaskExtensions.cs index 9f4d7dc88b518..f35eb18a71e10 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs +++ b/src/Dependencies/Threading/TaskExtensions.cs @@ -3,13 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Shared.TestHooks; +namespace Microsoft.CodeAnalysis.Threading; internal static partial class TaskExtensions { @@ -50,7 +47,6 @@ public readonly struct NoThrowTaskAwaitable /// Whether the continuation should be scheduled on the current sync context. public NoThrowTaskAwaitable(Task task, bool captureContext) { - Contract.ThrowIfNull(task, nameof(task)); _task = task; _captureContext = captureContext; } @@ -87,7 +83,6 @@ public NoThrowTaskAwaiter GetAwaiter() /// if set to true [capture context]. public NoThrowTaskAwaiter(Task task, bool captureContext) { - Contract.ThrowIfNull(task, nameof(task)); _task = task; _captureContext = captureContext; } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/IAsyncToken.cs b/src/Dependencies/Threading/TestHooks/IAsyncToken.cs similarity index 100% rename from src/Workspaces/Core/Portable/Shared/TestHooks/IAsyncToken.cs rename to src/Dependencies/Threading/TestHooks/IAsyncToken.cs diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListener.cs b/src/Dependencies/Threading/TestHooks/IAsynchronousOperationListener.cs similarity index 100% rename from src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListener.cs rename to src/Dependencies/Threading/TestHooks/IAsynchronousOperationListener.cs diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs b/src/Dependencies/Threading/TestHooks/IAsynchronousOperationListenerProvider.cs similarity index 100% rename from src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs rename to src/Dependencies/Threading/TestHooks/IAsynchronousOperationListenerProvider.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/IExpeditableDelaySource.cs b/src/Dependencies/Threading/TestHooks/IExpeditableDelaySource.cs similarity index 100% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/IExpeditableDelaySource.cs rename to src/Dependencies/Threading/TestHooks/IExpeditableDelaySource.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/ValueTaskExtensions.cs b/src/Dependencies/Threading/ValueTaskExtensions.cs similarity index 98% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/ValueTaskExtensions.cs rename to src/Dependencies/Threading/ValueTaskExtensions.cs index 183f634b0bb43..366c3014edcda 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/ValueTaskExtensions.cs +++ b/src/Dependencies/Threading/ValueTaskExtensions.cs @@ -4,11 +4,9 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; using System.Threading.Tasks; -using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Shared.TestHooks; +namespace Microsoft.CodeAnalysis.Threading; internal static class ValueTaskExtensions { diff --git a/src/Dependencies/Threading/VoidResult.cs b/src/Dependencies/Threading/VoidResult.cs new file mode 100644 index 0000000000000..2e626ce7209d5 --- /dev/null +++ b/src/Dependencies/Threading/VoidResult.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Threading; + +/// +/// Explicitly indicates result is void +/// +internal readonly struct VoidResult : IEquatable +{ + public override bool Equals(object? obj) + => obj is VoidResult; + + public override int GetHashCode() + => 0; + + public bool Equals(VoidResult other) + => true; +} diff --git a/src/Dependencies/Threading/YieldAwaitableExtensions.cs b/src/Dependencies/Threading/YieldAwaitableExtensions.cs new file mode 100644 index 0000000000000..6cd6f6db5794b --- /dev/null +++ b/src/Dependencies/Threading/YieldAwaitableExtensions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Threading; + +internal static class YieldAwaitableExtensions +{ + /// + /// Implements ConfigureAwait(bool) for . The resulting behavior in asynchronous code + /// is the same as one would expect for . + /// + /// The awaitable provided by . + /// + /// An object used to await this yield. + public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable awaitable, bool continueOnCapturedContext) + { + return new ConfiguredYieldAwaitable(awaitable, continueOnCapturedContext); + } +} diff --git a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs index 083c1ca0978b4..f00c77eb4d622 100644 --- a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs +++ b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs @@ -36,7 +36,7 @@ internal class CSharpCodeCleanupService(ICodeFixService codeFixService, IDiagnos IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId, IDEDiagnosticIds.AddRequiredParenthesesDiagnosticId), new DiagnosticSet(AnalyzersResources.Add_accessibility_modifiers, - IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId), + IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId), new DiagnosticSet(FeaturesResources.Apply_coalesce_expression_preferences, IDEDiagnosticIds.UseCoalesceExpressionForTernaryConditionalCheckDiagnosticId), new DiagnosticSet(FeaturesResources.Apply_object_collection_initialization_preferences, diff --git a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs index 1fa853da29253..877e0a9c72a7c 100644 --- a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs +++ b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler.cs @@ -20,18 +20,14 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.RawStringLiteral; [Order(After = nameof(SplitStringLiteralCommandHandler))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal partial class RawStringLiteralCommandHandler( +internal sealed partial class RawStringLiteralCommandHandler( ITextUndoHistoryRegistry undoHistoryRegistry, - IGlobalOptionService globalOptions, IEditorOperationsFactoryService editorOperationsFactoryService, - EditorOptionsService editorOptionsService, - IIndentationManagerService indentationManager) + EditorOptionsService editorOptionsService) { private readonly ITextUndoHistoryRegistry _undoHistoryRegistry = undoHistoryRegistry; - private readonly IGlobalOptionService _globalOptions = globalOptions; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService = editorOperationsFactoryService; private readonly EditorOptionsService _editorOptionsService = editorOptionsService; - private readonly IIndentationManagerService _indentationManager = indentationManager; public string DisplayName => CSharpEditorResources.Split_raw_string; } diff --git a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs index 22c871fa7b1b8..6170f34428627 100644 --- a/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs +++ b/src/EditorFeatures/CSharp/RawStringLiteral/RawStringLiteralCommandHandler_Return.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Roslyn.Utilities; @@ -31,6 +30,8 @@ public CommandState GetCommandState(ReturnKeyCommandArgs args) /// public bool ExecuteCommand(ReturnKeyCommandArgs args, CommandExecutionContext context) { + var cancellationToken = context.OperationContext.UserCancellationToken; + var textView = args.TextView; var subjectBuffer = args.SubjectBuffer; var spans = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer); @@ -51,8 +52,6 @@ public bool ExecuteCommand(ReturnKeyCommandArgs args, CommandExecutionContext co if (position >= currentSnapshot.Length) return false; - var cancellationToken = context.OperationContext.UserCancellationToken; - var document = currentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) return false; @@ -77,6 +76,10 @@ bool ExecuteReturnCommandBeforeQuoteCharacter() quotesAfter++; } + // We must have at least one following quote, as we only got into ExecuteReturnCommandBeforeQuoteCharacter + // if there was a quote character in front of it. + Debug.Assert(quotesAfter > 0); + for (var i = position - 1; i >= 0; i--) { if (currentSnapshot[i] != '"') @@ -110,6 +113,14 @@ SyntaxKind.InterpolatedSingleLineRawStringStartToken or return false; } + if (!isEmpty) + { + // in the non empty case (e.g. `"""goo$$"""`) we have to make sure sure that the caret is before the + // final quotes, not the initial ones. + if (token.Span.End - quotesAfter != position) + return false; + } + return MakeEdit(parsedDocument, expression, isEmpty); } diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs index c1a4348299cd1..f011511041a70 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs @@ -1312,4 +1312,70 @@ void Goo() Namespace("RegularExpressions"), Keyword("var")); } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/77189")] + public async Task TestStringFieldUsedLater_ProperModifiers( + TestHost testHost, + [CombinatorialValues("const", "static readonly")] string modifiers) + { + await TestAsync( + $$""" + using System.Diagnostics.CodeAnalysis; + using System.Text.RegularExpressions; + + class Program + { + private {{modifiers}} string regexValue = [|@"$(\a\t\u0020)"|]; + + void Goo() + { + Bar(regexValue); + } + + void Bar([StringSyntax(StringSyntaxAttribute.Regex)] string p) + { + } + } + """ + EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeCSharp, + testHost, + Regex.Anchor("$"), + Regex.Grouping("("), + Regex.OtherEscape("\\"), + Regex.OtherEscape("a"), + Regex.OtherEscape("\\"), + Regex.OtherEscape("t"), + Regex.OtherEscape("\\"), + Regex.OtherEscape("u"), + Regex.OtherEscape("0020"), + Regex.Grouping(")")); + } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/77189")] + public async Task TestStringFieldUsedLater_ImproperModifiers( + TestHost testHost, + [CombinatorialValues("", "static", "readonly")] string modifiers) + { + await TestAsync( + $$""" + using System.Diagnostics.CodeAnalysis; + using System.Text.RegularExpressions; + + class Program + { + private {{modifiers}} string regexValue = [|@"$(\a\t\u0020)"|]; + + void Goo() + { + Bar(regexValue); + } + + void Bar([StringSyntax(StringSyntaxAttribute.Regex)] string p) + { + } + } + """ + EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeCSharp, + testHost); + } } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs index ed9be9de37571..aa38c36ea5655 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs @@ -4400,4 +4400,19 @@ public override int GetHashCode() LanguageVersion = LanguageVersion.Default, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/76916")] + public async Task TestMissingWithPrimaryConstructorAndNoFields() + { + await new VerifyCS.Test + { + TestCode = """ + class C(int a) + { + [||] + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index f5cad13cf7700..488b059558925 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -41,91 +41,99 @@ internal override OptionsCollection NonCompletionOptions [WpfFact] public async Task InheritedVirtualPublicMethod() { - await VerifyItemExistsAsync(@" -public class a -{ - public virtual void goo() { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual void goo() { } + } -public class b : a -{ - override $$ -}", "goo()"); + public class b : a + { + override $$ + } + """, "goo()"); } [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543799")] public async Task InheritedParameterDefaultValue1() { - await VerifyItemExistsAsync(@"public class a -{ - public virtual void goo(int x = 42) { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual void goo(int x = 42) { } + } -public class b : a -{ - override $$ -}", "goo(int x = 42)", "void a.goo([int x = 42])"); + public class b : a + { + override $$ + } + """, "goo(int x = 42)", "void a.goo([int x = 42])"); } [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543799")] public async Task InheritedParameterDefaultValue2() { - await VerifyItemExistsAsync(@"public class a -{ - public virtual void goo(int x, int y = 42) { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual void goo(int x, int y = 42) { } + } -public class b : a -{ - override $$ -}", "goo(int x, int y = 42)", "void a.goo(int x, [int y = 42])"); + public class b : a + { + override $$ + } + """, "goo(int x, int y = 42)", "void a.goo(int x, [int y = 42])"); } [WpfFact] public async Task InheritedAbstractPublicMethod() { - await VerifyItemExistsAsync(@" -public class a -{ - public abstract void goo(); -} + await VerifyItemExistsAsync(""" + public class a + { + public abstract void goo(); + } -public class b : a -{ - override $$ -}", "goo()"); + public class b : a + { + override $$ + } + """, "goo()"); } [WpfFact] public async Task NotPrivateInheritedMethod() { - await VerifyItemIsAbsentAsync(@" -public class a -{ - private virtual void goo() { } -} + await VerifyItemIsAbsentAsync(""" + public class a + { + private virtual void goo() { } + } -public class b : a -{ - override $$ -}", "goo()"); + public class b : a + { + override $$ + } + """, "goo()"); } [WpfFact] public async Task MatchReturnType() { - var markup = @" -public class a -{ - public virtual void goo() { } + var markup = """ + public class a + { + public virtual void goo() { } - public virtual string bar() {return null;} -} + public virtual string bar() {return null;} + } -public class b : a -{ - override void $$ -}"; + public class b : a + { + override void $$ + } + """; await VerifyItemIsAbsentAsync(markup, "bar()"); await VerifyItemExistsAsync(markup, "goo()"); } @@ -133,18 +141,19 @@ override void $$ [WpfFact] public async Task InvalidReturnType() { - var markup = @" -public class a -{ - public virtual void goo() { } + var markup = """ + public class a + { + public virtual void goo() { } - public virtual string bar() {return null;} -} + public virtual string bar() {return null;} + } -public class b : a -{ - override badtype $$ -}"; + public class b : a + { + override badtype $$ + } + """; await VerifyItemExistsAsync(markup, "goo()"); await VerifyItemExistsAsync(markup, "bar()"); @@ -153,247 +162,263 @@ override badtype $$ [WpfFact] public async Task NotAlreadyImplementedMethods() { - await VerifyItemIsAbsentAsync(@" -public class a -{ - protected virtual void goo() { } + await VerifyItemIsAbsentAsync(""" + public class a + { + protected virtual void goo() { } - protected virtual string bar() {return null;} -} + protected virtual string bar() {return null;} + } -public class b : a -{ - protected override void goo() { } + public class b : a + { + protected override void goo() { } - override $$ -}", "goo()"); + override $$ + } + """, "goo()"); } [WpfFact] public async Task NotSealed() { - await VerifyItemIsAbsentAsync(@" -public class a -{ - protected sealed void goo() { } -} + await VerifyItemIsAbsentAsync(""" + public class a + { + protected sealed void goo() { } + } -public class b : a -{ - public override $$ -}", "goo()"); + public class b : a + { + public override $$ + } + """, "goo()"); } [WpfFact] public async Task ShowEvent() { - await VerifyItemExistsAsync(@" -using System; -public class a -{ - public virtual event EventHandler goo; -} + await VerifyItemExistsAsync(""" + using System; + public class a + { + public virtual event EventHandler goo; + } -public class b : a -{ - public override $$ -}", "goo"); + public class b : a + { + public override $$ + } + """, "goo"); } [WpfFact] public async Task NotIfTokensAfterPosition() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual void goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual void goo() { } + } -public class b : a -{ - public override $$ void -}"); + public class b : a + { + public override $$ void + } + """); } [WpfFact] public async Task NotIfNameAfterPosition() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual void goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual void goo() { } + } -public class b : a -{ - public override void $$ bar -}"); + public class b : a + { + public override void $$ bar + } + """); } [WpfFact] public async Task NotIfStatic() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual void goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual void goo() { } + } -public class b : a -{ - public static override $$ -}"); + public class b : a + { + public static override $$ + } + """); } [WpfFact] public async Task AfterSingleLineMethodDeclaration() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual void goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual void goo() { } + } -public class b : a -{ - void bar() { } override $$ -}"); + public class b : a + { + void bar() { } override $$ + } + """); } [WpfFact] public async Task SuggestProperty() { - await VerifyItemExistsAsync(@" -public class a -{ - public virtual int goo { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual int goo { } + } -public class b : a -{ - override $$ -}", "goo"); + public class b : a + { + override $$ + } + """, "goo"); } [WpfFact] public async Task NotSuggestSealed() { - await VerifyItemIsAbsentAsync(@" -public class a -{ - public sealed int goo { } -} + await VerifyItemIsAbsentAsync(""" + public class a + { + public sealed int goo { } + } -public class b : a -{ - override $$ -}", "goo"); + public class b : a + { + override $$ + } + """, "goo"); } [WpfFact] public async Task GatherModifiers() { - await VerifyItemExistsAsync(@" -public class a -{ - public abstract extern unsafe int goo { } -} + await VerifyItemExistsAsync(""" + public class a + { + public abstract extern unsafe int goo { } + } -public class b : a -{ - override $$ -}", "goo"); + public class b : a + { + override $$ + } + """, "goo"); } [WpfFact] public async Task IgnorePartial() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual partial goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual partial goo() { } + } -public class b : a -{ - override partial $$ -}"); + public class b : a + { + override partial $$ + } + """); } [WpfFact] public async Task IgnoreSealed() { - await VerifyItemIsAbsentAsync(@" -public class a -{ - public virtual sealed int goo() { } -} + await VerifyItemIsAbsentAsync(""" + public class a + { + public virtual sealed int goo() { } + } -public class b : a -{ - override $$ -}", "goo()"); + public class b : a + { + override $$ + } + """, "goo()"); } [WpfFact] public async Task IgnoreIfTokenAfter() { - await VerifyNoItemsExistAsync(@" -public class a -{ - public virtual int goo() { } -} + await VerifyNoItemsExistAsync(""" + public class a + { + public virtual int goo() { } + } -public class b : a -{ - override $$ int -}"); + public class b : a + { + override $$ int + } + """); } [WpfFact] public async Task SuggestAfterUnsafeAbstractExtern() { - await VerifyItemExistsAsync(@" -public class a -{ - public virtual int goo() { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual int goo() { } + } -public class b : a -{ - unsafe abstract extern override $$ -}", "goo()"); + public class b : a + { + unsafe abstract extern override $$ + } + """, "goo()"); } [WpfFact] public async Task SuggestAfterSealed() { - await VerifyItemExistsAsync(@" -public class a -{ - public virtual int goo() { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual int goo() { } + } -public class b : a -{ - sealed override $$ -}", "goo()"); + public class b : a + { + sealed override $$ + } + """, "goo()"); } [WpfFact] public async Task NoAccessibility() { - var markup = @" -public class a -{ - public virtual int goo() { } - protected virtual int bar() { } -} + var markup = """ + public class a + { + public virtual int goo() { } + protected virtual int bar() { } + } -public class b : a -{ - override $$ -}"; + public class b : a + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "goo()"); await VerifyItemExistsAsync(markup, "bar()"); @@ -402,19 +427,20 @@ public class b : a [WpfFact] public async Task FilterAccessibility() { - var markup = @" -public class a -{ - public virtual int goo() { } - protected virtual int bar() { } - internal virtual int far() { } - private virtual int bor() { } -} + var markup = """ + public class a + { + public virtual int goo() { } + protected virtual int bar() { } + internal virtual int far() { } + private virtual int bor() { } + } -public class b : a -{ - override internal $$ -}"; + public class b : a + { + override internal $$ + } + """; await VerifyItemIsAbsentAsync(markup, "goo()"); await VerifyItemIsAbsentAsync(markup, "bar()"); @@ -422,50 +448,53 @@ override internal $$ await VerifyItemExistsAsync(markup, "far()"); - await VerifyItemExistsAsync(@" -public class a -{ - public virtual int goo() { } - protected virtual int bar() { } - internal virtual int far() { } - private virtual int bor() { } -} + await VerifyItemExistsAsync(""" + public class a + { + public virtual int goo() { } + protected virtual int bar() { } + internal virtual int far() { } + private virtual int bor() { } + } -public class b : a -{ - override protected $$ -}", "bar()"); + public class b : a + { + override protected $$ + } + """, "bar()"); } [WpfFact] public async Task FilterPublicInternal() { - var protectedinternal = @" -public class a -{ - protected internal virtual void goo() { } - public virtual void bar() { } -} + var protectedinternal = """ + public class a + { + protected internal virtual void goo() { } + public virtual void bar() { } + } -public class b : a -{ - protected internal override $$ -}"; + public class b : a + { + protected internal override $$ + } + """; await VerifyItemIsAbsentAsync(protectedinternal, "bar()"); await VerifyItemExistsAsync(protectedinternal, "goo()"); - var internalprotected = @" -public class a -{ - protected internal virtual void goo() { } - public virtual void bar() { } -} + var internalprotected = """ + public class a + { + protected internal virtual void goo() { } + public virtual void bar() { } + } -public class b : a -{ - internal protected override $$ -}"; + public class b : a + { + internal protected override $$ + } + """; await VerifyItemIsAbsentAsync(internalprotected, "bar()"); await VerifyItemExistsAsync(internalprotected, "goo()"); @@ -474,55 +503,59 @@ internal protected override $$ [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/64821")] public async Task FilterAccessibility1() { - var test1 = @" -public class a -{ - private protected virtual void goo() { } -} + var test1 = """ + public class a + { + private protected virtual void goo() { } + } -public class b : a -{ - private override $$ -}"; + public class b : a + { + private override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - private protected virtual void goo() { } -} + test1 = """ + public class a + { + private protected virtual void goo() { } + } -public class b : a -{ - protected override $$ -}"; + public class b : a + { + protected override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - private protected virtual void goo() { } -} + test1 = """ + public class a + { + private protected virtual void goo() { } + } -public class b : a -{ - private protected override $$ -}"; + public class b : a + { + private protected override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - private protected virtual void goo() { } -} - -public class b : a -{ - protected private override $$ -}"; + test1 = """ + public class a + { + private protected virtual void goo() { } + } + + public class b : a + { + protected private override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); } @@ -530,55 +563,59 @@ protected private override $$ [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/64821")] public async Task FilterAccessibility2() { - var test1 = @" -public class a -{ - protected internal virtual void goo() { } -} + var test1 = """ + public class a + { + protected internal virtual void goo() { } + } -public class b : a -{ - protected override $$ -}"; + public class b : a + { + protected override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - protected internal virtual void goo() { } -} + test1 = """ + public class a + { + protected internal virtual void goo() { } + } -public class b : a -{ - internal override $$ -}"; + public class b : a + { + internal override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - protected internal virtual void goo() { } -} + test1 = """ + public class a + { + protected internal virtual void goo() { } + } -public class b : a -{ - protected internal override $$ -}"; + public class b : a + { + protected internal override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); - test1 = @" -public class a -{ - protected internal virtual void goo() { } -} + test1 = """ + public class a + { + protected internal virtual void goo() { } + } -public class b : a -{ - internal protected override $$ -}"; + public class b : a + { + internal protected override $$ + } + """; await VerifyItemExistsAsync(test1, "goo()"); } @@ -586,11 +623,12 @@ internal protected override $$ [WpfFact] public async Task VerifySignatureFormat() { - var markup = @" -public class a -{ - override $$ -}"; + var markup = """ + public class a + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Equals(object obj)"); } @@ -598,16 +636,17 @@ public class a [WpfFact] public async Task PrivateNoFilter() { - var markup = @" -public class c -{ - public virtual void goo() { } -} + var markup = """ + public class c + { + public virtual void goo() { } + } -public class a : c -{ - private override $$ -}"; + public class a : c + { + private override $$ + } + """; await VerifyNoItemsExistAsync(markup); } @@ -631,20 +670,22 @@ public async Task NotOfferedOverrideAlone() [WpfFact] public async Task IntermediateClassOverriddenMember() { - var markup = @"abstract class Base -{ - public abstract void Goo(); -} + var markup = """ + abstract class Base + { + public abstract void Goo(); + } -class Derived : Base -{ - public override void Goo() { } -} + class Derived : Base + { + public override void Goo() { } + } -class SomeClass : Derived -{ - override $$ -}"; + class SomeClass : Derived + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo()", "void Derived.Goo()"); } @@ -652,20 +693,22 @@ class SomeClass : Derived [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543748")] public async Task NotOfferedBaseClassMember() { - var markup = @"abstract class Base -{ - public abstract void Goo(); -} + var markup = """ + abstract class Base + { + public abstract void Goo(); + } -class Derived : Base -{ - public override void Goo() { } -} + class Derived : Base + { + public override void Goo() { } + } -class SomeClass : Derived -{ - override $$ -}"; + class SomeClass : Derived + { + override $$ + } + """; await VerifyItemIsAbsentAsync(markup, "Goo()", "void Base.Goo()"); } @@ -673,15 +716,17 @@ class SomeClass : Derived [WpfFact] public async Task NotOfferedOnNonVirtual() { - var markup = @"class Base -{ - public void Goo(); -} + var markup = """ + class Base + { + public void Goo(); + } -class SomeClass : Base -{ - override $$ -}"; + class SomeClass : Base + { + override $$ + } + """; await VerifyItemIsAbsentAsync(markup, "Goo()", "void Base.Goo()"); } @@ -689,15 +734,17 @@ class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForGenericInDerivedClass1() { - var markup = @"public abstract class Base -{ - public abstract void Goo(T t); -} + var markup = """ + public abstract class Base + { + public abstract void Goo(T t); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(X t)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t)"); } @@ -705,15 +752,17 @@ public class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForGenericInDerivedClass2() { - var markup = @"public abstract class Base -{ - public abstract void Goo(T t); -} + var markup = """ + public abstract class Base + { + public abstract void Goo(T t); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(Y t)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t)"); } @@ -721,15 +770,17 @@ public class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForGenericInDerivedClass3() { - var markup = @"public abstract class Base -{ - public abstract void Goo(T t, S s); -} + var markup = """ + public abstract class Base + { + public abstract void Goo(T t, S s); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(Y t, Z s)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t, S s)"); } @@ -737,15 +788,17 @@ public class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForNonGenericInDerivedClass1() { - var markup = @"public abstract class Base -{ - public abstract void Goo(T t); -} + var markup = """ + public abstract class Base + { + public abstract void Goo(T t); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(int t)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t)"); } @@ -753,15 +806,17 @@ public class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForNonGenericInDerivedClass2() { - var markup = @"public abstract class Base -{ - public abstract void Goo(T t); -} + var markup = """ + public abstract class Base + { + public abstract void Goo(T t); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(int t)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t)"); } @@ -769,17 +824,19 @@ public class SomeClass : Base [WpfFact] public async Task GenericTypeNameSubstitutedForNonGenericInDerivedClass3() { - var markup = @"using System; + var markup = """ + using System; -public abstract class Base -{ - public abstract void Goo(T t, S s); -} + public abstract class Base + { + public abstract void Goo(T t, S s); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(int t, Exception s)"); await VerifyItemIsAbsentAsync(markup, "Goo(T t, S s)"); } @@ -787,17 +844,19 @@ public class SomeClass : Base [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543756")] public async Task ParameterTypeSimplified() { - var markup = @"using System; + var markup = """ + using System; -public abstract class Base -{ - public abstract void Goo(System.Exception e); -} + public abstract class Base + { + public abstract void Goo(System.Exception e); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(Exception e)"); } @@ -805,32 +864,36 @@ public class SomeClass : Base [WpfFact] public async Task NullableAnnotationsIncluded() { - var markup = @"#nullable enable + var markup = """ + #nullable enable -public abstract class Base -{ - public abstract void Goo(string? s); -} + public abstract class Base + { + public abstract void Goo(string? s); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Goo(string? s)"); } [WpfFact] public async Task EscapedMethodNameInIntelliSenseList() { - var markup = @"public abstract class Base -{ - public abstract void @class(); -} + var markup = """ + public abstract class Base + { + public abstract void @class(); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; MarkupTestFile.GetPosition(markup, out var code, out int position); await BaseVerifyWorkerAsync(code, position, "@class()", "void Base.@class()", SourceCodeKind.Regular, false, deletedCharTrigger: null, false, null, null, null, null, null, null); @@ -840,15 +903,17 @@ public class SomeClass : Base [WpfFact] public async Task EscapedPropertyNameInIntelliSenseList() { - var markup = @"public abstract class Base -{ - public virtual int @class { get; set; } -} + var markup = """ + public abstract class Base + { + public virtual int @class { get; set; } + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; MarkupTestFile.GetPosition(markup, out var code, out int position); await BaseVerifyWorkerAsync(code, position, "@class", "int Base.@class { get; set; }", SourceCodeKind.Regular, false, deletedCharTrigger: null, false, null, null, null, null, null, null); @@ -858,15 +923,17 @@ public class SomeClass : Base [WpfFact] public async Task EscapedParameterNameInIntelliSenseList() { - var markup = @"public abstract class Base -{ - public abstract void goo(int @class); -} + var markup = """ + public abstract class Base + { + public abstract void goo(int @class); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "goo(int @class)", "void Base.goo(int @class)"); } @@ -874,15 +941,17 @@ public class SomeClass : Base [WpfFact] public async Task RefParameter() { - var markup = @"public abstract class Base -{ - public abstract void goo(int x, ref string y); -} + var markup = """ + public abstract class Base + { + public abstract void goo(int x, ref string y); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "goo(int x, ref string y)", "void Base.goo(int x, ref string y)"); } @@ -890,15 +959,17 @@ public class SomeClass : Base [WpfFact] public async Task OutParameter() { - var markup = @"public abstract class Base -{ - public abstract void goo(int x, out string y); -} + var markup = """ + public abstract class Base + { + public abstract void goo(int x, out string y); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "goo(int x, out string y)", "void Base.goo(int x, out string y)"); } @@ -906,757 +977,1142 @@ public class SomeClass : Base [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529714")] public async Task GenericMethodTypeParametersNotRenamed() { - var markup = @"abstract class CGoo -{ - public virtual X Something(X arg) - { - return default(X); - } -} -class Derived : CGoo -{ - override $$ -}"; + var markup = """ + abstract class CGoo + { + public virtual X Something(X arg) + { + return default(X); + } + } + class Derived : CGoo + { + override $$ + } + """; await VerifyItemExistsAsync(markup, "Something(X arg)"); } - #endregion - - #region "Commit tests" - [WpfFact] - public async Task CommitInEmptyClass() + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute1() { - var markupBeforeCommit = @"class c -{ - override $$ -}"; + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + [That] + public int Disregard = 34; + } + } + """; - var expectedCodeAfterCommit = @"class c -{ - public override bool Equals(object obj) - { - return base.Equals(obj);$$ - } -}"; + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + [That] + public int Disregard = 34; + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529714")] - public async Task CommitGenericMethodTypeParametersNotRenamed() - { - var markupBeforeCommit = @"abstract class CGoo -{ - public virtual X Something(X arg) - { - return default(X); - } -} -class Derived : CGoo -{ - override $$ -}"; - - var expectedCodeAfterCommit = @"abstract class CGoo -{ - public virtual X Something(X arg) - { - return default(X); - } -} -class Derived : CGoo -{ - public override X Something(X arg) - { - return base.Something(arg);$$ - } -}"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Something(X arg)", expectedCodeAfterCommit); - } - - [WpfFact] - public async Task CommitMethodBeforeMethod() + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute2() { - var markupBeforeCommit = @"class c -{ - override $$ + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + // This is a comment + [That] + public int Disregard = 34; + } + } + """; - public void goo() { } -}"; + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + // This is a comment + [That] + public int Disregard = 34; + } + } + """; - var expectedCodeAfterCommit = @"class c -{ - public override bool Equals(object obj) - { - return base.Equals(obj);$$ + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } - public void goo() { } -}"; + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute3() + { + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + [That] public int Disregard = 34; + } + } + """; + + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + [That] public int Disregard = 34; + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } - [WpfFact] - public async Task CommitMethodAfterMethod() + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute4() { - var markupBeforeCommit = @"class c -{ - public void goo() { } + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + [That] + // Comment after attribute + public int Disregard = 34; + } + } + """; - override $$ -}"; + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + [That] + // Comment after attribute + public int Disregard = 34; + } + } + """; - var expectedCodeAfterCommit = @"class c -{ - public void goo() { } + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); + } - public override bool Equals(object obj) + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute5() { - return base.Equals(obj);$$ - } -}"; + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + [That, System.Obsolete("")] + public int Disregard = 34; + } + } + """; + + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + [That, System.Obsolete("")] + public int Disregard = 34; + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543798")] - public async Task CommitOptionalParameterValuesAreGenerated() + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute6() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + // Comment before the override. + override Eq$$ + // Comment after the override. -abstract public class Base -{ - public abstract void goo(int x = 42); -} + [That] + public int Disregard = 34; + } + } + """; -public class Derived : Base -{ - override $$ -}"; + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + // Comment before the override. + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + // Comment after the override. - var expectedCodeAfterCommit = @"using System; + [That] + public int Disregard = 34; + } + } + """; -abstract public class Base -{ - public abstract void goo(int x = 42); -} + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); + } -public class Derived : Base -{ - public override void goo(int x = 42) + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77193")] + public async Task CommitBeforeAttribute7() { - throw new NotImplementedException();$$ - } -}"; + var markupBeforeCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + override Eq$$ + + [That] /* inline comment */ public int Disregard = 34; + } + } + """; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int x = 42)", expectedCodeAfterCommit); + var expectedCodeAfterCommit = """ + namespace InteliSenseIssue + { + [AttributeUsage(AttributeTargets.All)] + public class ThatAttribute : Attribute {} + + internal class Program + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + + [That] /* inline comment */ public int Disregard = 34; + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } + #endregion - [WpfFact] - public async Task CommitAttributesAreNotGenerated() - { - var markupBeforeCommit = @"using System; + #region "Commit tests" -public class Base -{ - [Obsolete] - public virtual void goo() + [WpfFact] + public async Task CommitInEmptyClass() { - } -} - -public class Derived : Base -{ - override $$ -}"; + var markupBeforeCommit = """ + class c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + class c + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + } + """; -public class Base -{ - [Obsolete] - public virtual void goo() - { + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } -} -public class Derived : Base -{ - public override void goo() + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529714")] + public async Task CommitGenericMethodTypeParametersNotRenamed() { - base.goo();$$ - } -}"; + var markupBeforeCommit = """ + abstract class CGoo + { + public virtual X Something(X arg) + { + return default(X); + } + } + class Derived : CGoo + { + override $$ + } + """; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + var expectedCodeAfterCommit = """ + abstract class CGoo + { + public virtual X Something(X arg) + { + return default(X); + } + } + class Derived : CGoo + { + public override X Something(X arg) + { + return base.Something(arg);$$ + } + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Something(X arg)", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInaccessibleParameterAttributesAreNotGenerated() + public async Task CommitMethodBeforeMethod() { - var markupBeforeCommit = @"using System; - -public class Class1 -{ - private class MyPrivate : Attribute { } - public class MyPublic : Attribute { } - public virtual void M([MyPrivate, MyPublic] int i) { } -} - -public class Class2 : Class1 -{ - public override void $$ -}"; + var markupBeforeCommit = """ + class c + { + override $$ - var expectedCodeAfterCommit = @"using System; + public void goo() { } + } + """; -public class Class1 -{ - private class MyPrivate : Attribute { } - public class MyPublic : Attribute { } - public virtual void M([MyPrivate, MyPublic] int i) { } -} + var expectedCodeAfterCommit = """ + class c + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } -public class Class2 : Class1 -{ - public override void M([MyPublic] int i) - { - base.M(i);$$ - } -}"; + public void goo() { } + } + """; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "M(int i)", expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitVoidMethod() + public async Task CommitMethodAfterMethod() { - var markupBeforeCommit = @"class c -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class c + { + public void goo() { } -class d : c -{ - override $$ -}"; + override $$ + } + """; - var expectedCodeAfterCommit = @"class c -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class c + { + public void goo() { } -class d : c -{ - public override void goo() - { - base.goo();$$ - } -}"; + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + } + """; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Equals(object obj)", expectedCodeAfterCommit); } - [WpfFact] - public async Task CommitVoidMethodWithParams() + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543798")] + public async Task CommitOptionalParameterValuesAreGenerated() { - var markupBeforeCommit = @"class c -{ - public virtual void goo(int bar, int quux) { } -} + var markupBeforeCommit = """ + using System; -class d : c -{ - override $$ -}"; + abstract public class Base + { + public abstract void goo(int x = 42); + } - var expectedCodeAfterCommit = @"class c -{ - public virtual void goo(int bar, int quux) { } -} + public class Derived : Base + { + override $$ + } + """; -class d : c -{ - public override void goo(int bar, int quux) - { - base.goo(bar, quux);$$ - } -}"; + var expectedCodeAfterCommit = """ + using System; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int bar, int quux)", expectedCodeAfterCommit); + abstract public class Base + { + public abstract void goo(int x = 42); + } + + public class Derived : Base + { + public override void goo(int x = 42) + { + throw new NotImplementedException();$$ + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int x = 42)", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitNonVoidMethod() + public async Task CommitAttributesAreNotGenerated() { - var markupBeforeCommit = @"class c -{ - public virtual int goo() { } -} + var markupBeforeCommit = """ + using System; -class d : c -{ - override $$ -}"; + public class Base + { + [Obsolete] + public virtual void goo() + { + } + } - var expectedCodeAfterCommit = @"class c -{ - public virtual int goo() { } -} + public class Derived : Base + { + override $$ + } + """; -class d : c -{ - public override int goo() - { - return base.goo();$$ - } -}"; + var expectedCodeAfterCommit = """ + using System; + + public class Base + { + [Obsolete] + public virtual void goo() + { + } + } + + public class Derived : Base + { + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitNonVoidMethodWithParams() + public async Task CommitInaccessibleParameterAttributesAreNotGenerated() { - var markupBeforeCommit = @"class c -{ - public virtual int goo(int bar, int quux) { } -} + var markupBeforeCommit = """ + using System; -class d : c -{ - override $$ -}"; + public class Class1 + { + private class MyPrivate : Attribute { } + public class MyPublic : Attribute { } + public virtual void M([MyPrivate, MyPublic] int i) { } + } - var expectedCodeAfterCommit = @"class c -{ - public virtual int goo(int bar, int quux) { } -} + public class Class2 : Class1 + { + public override void $$ + } + """; -class d : c -{ - public override int goo(int bar, int quux) - { - return base.goo(bar, quux);$$ - } -}"; + var expectedCodeAfterCommit = """ + using System; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int bar, int quux)", expectedCodeAfterCommit); + public class Class1 + { + private class MyPrivate : Attribute { } + public class MyPublic : Attribute { } + public virtual void M([MyPrivate, MyPublic] int i) { } + } + + public class Class2 : Class1 + { + public override void M([MyPublic] int i) + { + base.M(i);$$ + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "M(int i)", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitProtectedMethod() + public async Task CommitVoidMethod() { - var markupBeforeCommit = @"class c -{ - protected virtual void goo() { } -} + var markupBeforeCommit = """ + class c + { + public virtual void goo() { } + } -class d : c -{ - override $$ -}"; - var expectedCodeAfterCommit = @"class c -{ - protected virtual void goo() { } -} + class d : c + { + override $$ + } + """; -class d : c -{ - protected override void goo() - { - base.goo();$$ - } -}"; + var expectedCodeAfterCommit = """ + class c + { + public virtual void goo() { } + } + + class d : c + { + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInternalMethod() + public async Task CommitVoidMethodWithParams() { - var markupBeforeCommit = @"class c -{ - internal virtual void goo() { } -} + var markupBeforeCommit = """ + class c + { + public virtual void goo(int bar, int quux) { } + } -class d : c -{ - override $$ -}"; + class d : c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"class c -{ - internal virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class c + { + public virtual void goo(int bar, int quux) { } + } -class d : c -{ - internal override void goo() - { - base.goo();$$ - } -}"; + class d : c + { + public override void goo(int bar, int quux) + { + base.goo(bar, quux);$$ + } + } + """; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int bar, int quux)", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitProtectedInternalMethod() + public async Task CommitNonVoidMethod() { - var markupBeforeCommit = @"public class c -{ - protected internal virtual void goo() { } -} + var markupBeforeCommit = """ + class c + { + public virtual int goo() { } + } -class d : c -{ - override $$ -}"; + class d : c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"public class c -{ - protected internal virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class c + { + public virtual int goo() { } + } -class d : c -{ - protected internal override void goo() - { - base.goo();$$ - } -}"; + class d : c + { + public override int goo() + { + return base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitAbstractMethodThrows() + public async Task CommitNonVoidMethodWithParams() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + class c + { + public virtual int goo(int bar, int quux) { } + } -abstract class c -{ - public abstract void goo(); -} + class d : c + { + override $$ + } + """; -class d : c -{ - override $$ -}"; + var expectedCodeAfterCommit = """ + class c + { + public virtual int goo(int bar, int quux) { } + } - var expectedCodeAfterCommit = @"using System; + class d : c + { + public override int goo(int bar, int quux) + { + return base.goo(bar, quux);$$ + } + } + """; -abstract class c -{ - public abstract void goo(); -} + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int bar, int quux)", expectedCodeAfterCommit); + } -class d : c -{ - public override void goo() + [WpfFact] + public async Task CommitProtectedMethod() { - throw new NotImplementedException();$$ - } -}"; + var markupBeforeCommit = """ + class c + { + protected virtual void goo() { } + } + + class d : c + { + override $$ + } + """; + var expectedCodeAfterCommit = """ + class c + { + protected virtual void goo() { } + } + + class d : c + { + protected override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitOverrideAsAbstract() + public async Task CommitInternalMethod() { - var markupBeforeCommit = @"class c -{ - public virtual void goo() { }; -} + var markupBeforeCommit = """ + class c + { + internal virtual void goo() { } + } -class d : c -{ - abstract override $$ -}"; + class d : c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"class c -{ - public virtual void goo() { }; -} + var expectedCodeAfterCommit = """ + class c + { + internal virtual void goo() { } + } -class d : c -{ - public abstract override void goo();$$ -}"; + class d : c + { + internal override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitOverrideAsUnsafeSealed() + public async Task CommitProtectedInternalMethod() { - var markupBeforeCommit = @"class c -{ - public virtual void goo() { }; -} + var markupBeforeCommit = """ + public class c + { + protected internal virtual void goo() { } + } -class d : c -{ - unsafe sealed override $$ -}"; + class d : c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"class c -{ - public virtual void goo() { }; -} + var expectedCodeAfterCommit = """ + public class c + { + protected internal virtual void goo() { } + } -class d : c -{ - public sealed override unsafe void goo() - { - base.goo();$$ - } -}"; + class d : c + { + protected internal override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInsertProperty() + public async Task CommitAbstractMethodThrows() { - var markupBeforeCommit = @"public class c -{ - public virtual int goo { get; set; } -} + var markupBeforeCommit = """ + using System; -public class d : c -{ - override $$ -}"; + abstract class c + { + public abstract void goo(); + } - var expectedCodeAfterCommit = @"public class c -{ - public virtual int goo { get; set; } -} + class d : c + { + override $$ + } + """; -public class d : c -{ - public override int goo - { - get - { - return base.goo;$$ - } + var expectedCodeAfterCommit = """ + using System; - set - { - base.goo = value; - } - } -}"; + abstract class c + { + public abstract void goo(); + } - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); + class d : c + { + public override void goo() + { + throw new NotImplementedException();$$ + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInsertPropertyAfterMethod() + public async Task CommitOverrideAsAbstract() { - var markupBeforeCommit = @"public class c -{ - public virtual int goo { get; set; } -} + var markupBeforeCommit = """ + class c + { + public virtual void goo() { }; + } -public class d : c -{ - public void a() { } - override $$ -}"; + class d : c + { + abstract override $$ + } + """; - var expectedCodeAfterCommit = @"public class c -{ - public virtual int goo { get; set; } -} + var expectedCodeAfterCommit = """ + class c + { + public virtual void goo() { }; + } -public class d : c -{ - public void a() { } - public override int goo - { - get - { - return base.goo;$$ - } + class d : c + { + public abstract override void goo();$$ + } + """; - set - { - base.goo = value; - } + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } -}"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); + [WpfFact] + public async Task CommitOverrideAsUnsafeSealed() + { + var markupBeforeCommit = """ + class c + { + public virtual void goo() { }; + } + + class d : c + { + unsafe sealed override $$ + } + """; + + var expectedCodeAfterCommit = """ + class c + { + public virtual void goo() { }; + } + + class d : c + { + public sealed override unsafe void goo() + { + base.goo();$$ + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInsertPropertyBeforeMethod() + public async Task CommitInsertProperty() { - var markupBeforeCommit = @"public class c -{ - public virtual int goo { get; set; } -} + var markupBeforeCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - override $$ - public void a() { } -}"; + public class d : c + { + override $$ + } + """; - var expectedCodeAfterCommit = @"public class c -{ - public virtual int goo { get; set; } -} + var expectedCodeAfterCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - public override int goo - { - get - { - return base.goo;$$ - } + public class d : c + { + public override int goo + { + get + { + return base.goo;$$ + } - set - { - base.goo = value; - } - } - public void a() { } -}"; + set + { + base.goo = value; + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitPropertyInaccessibleGet() + public async Task CommitInsertPropertyAfterMethod() { - var markupBeforeCommit = @"public class c -{ - public virtual int goo { private get; set; } -} + var markupBeforeCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - override $$ -}"; + public class d : c + { + public void a() { } + override $$ + } + """; - var expectedCodeAfterCommit = @"public class c -{ - public virtual int goo { private get; set; } -} + var expectedCodeAfterCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - public override int goo - { - set - { - base.goo = value;$$ - } - } -}"; + public class d : c + { + public void a() { } + public override int goo + { + get + { + return base.goo;$$ + } + + set + { + base.goo = value; + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitPropertyInaccessibleSet() + public async Task CommitInsertPropertyBeforeMethod() { - var markupBeforeCommit = @"public class c -{ - public virtual int goo { private set; get; } -} + var markupBeforeCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - override $$ -}"; + public class d : c + { + override $$ + public void a() { } + } + """; - var expectedCodeAfterCommit = @"public class c -{ - public virtual int goo { private set; get; } -} + var expectedCodeAfterCommit = """ + public class c + { + public virtual int goo { get; set; } + } -public class d : c -{ - public override int goo - { - get - { - return base.goo;$$ - } - } -}"; + public class d : c + { + public override int goo + { + get + { + return base.goo;$$ + } + + set + { + base.goo = value; + } + } + public void a() { } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitInsertPropertyInaccessibleParameterAttributesAreNotGenerated() + public async Task CommitPropertyInaccessibleGet() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + public class c + { + public virtual int goo { private get; set; } + } -namespace ClassLibrary1 -{ - public class Class1 - { - private class MyPrivate : Attribute { } + public class d : c + { + override $$ + } + """; - public class MyPublic : Attribute { } + var expectedCodeAfterCommit = """ + public class c + { + public virtual int goo { private get; set; } + } - public virtual int this[[MyPrivate, MyPublic]int i] - { - get { return 0; } - set { } - } + public class d : c + { + public override int goo + { + set + { + base.goo = value;$$ + } + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } - public class Class2 : Class1 + [WpfFact] + public async Task CommitPropertyInaccessibleSet() { - public override int $$ - } -}"; + var markupBeforeCommit = """ + public class c + { + public virtual int goo { private set; get; } + } - var expectedCodeAfterCommit = @"using System; + public class d : c + { + override $$ + } + """; -namespace ClassLibrary1 -{ - public class Class1 - { - private class MyPrivate : Attribute { } + var expectedCodeAfterCommit = """ + public class c + { + public virtual int goo { private set; get; } + } - public class MyPublic : Attribute { } + public class d : c + { + public override int goo + { + get + { + return base.goo;$$ + } + } + } + """; - public virtual int this[[MyPrivate, MyPublic]int i] - { - get { return 0; } - set { } - } + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } - public class Class2 : Class1 + [WpfFact] + public async Task CommitInsertPropertyInaccessibleParameterAttributesAreNotGenerated() { - public override int this[[MyPublic] int i] - { - get + var markupBeforeCommit = """ + using System; + + namespace ClassLibrary1 { - return base[i];$$ + public class Class1 + { + private class MyPrivate : Attribute { } + + public class MyPublic : Attribute { } + + public virtual int this[[MyPrivate, MyPublic]int i] + { + get { return 0; } + set { } + } + } + + public class Class2 : Class1 + { + public override int $$ + } } + """; + + var expectedCodeAfterCommit = """ + using System; - set + namespace ClassLibrary1 { - base[i] = value; + public class Class1 + { + private class MyPrivate : Attribute { } + + public class MyPublic : Attribute { } + + public virtual int this[[MyPrivate, MyPublic]int i] + { + get { return 0; } + set { } + } + } + + public class Class2 : Class1 + { + public override int this[[MyPublic] int i] + { + get + { + return base[i];$$ + } + + set + { + base[i] = value; + } + } + } } - } - } -}"; + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "this[int i]", expectedCodeAfterCommit); } @@ -1664,27 +2120,31 @@ public override int this[[MyPublic] int i] [WpfFact] public async Task CommitAccessibleEvent() { - var markupBeforeCommit = @"using System; -public class a -{ - public virtual event EventHandler goo; -} + var markupBeforeCommit = """ + using System; + public class a + { + public virtual event EventHandler goo; + } -public class b : a -{ - override $$ -}"; + public class b : a + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; -public class a -{ - public virtual event EventHandler goo; -} + var expectedCodeAfterCommit = """ + using System; + public class a + { + public virtual event EventHandler goo; + } -public class b : a -{ - public override event EventHandler goo;$$ -}"; + public class b : a + { + public override event EventHandler goo;$$ + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } @@ -1692,31 +2152,35 @@ public class b : a [WpfFact] public async Task CommitEventAfterMethod() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public class a -{ - public virtual event EventHandler goo; -} + public class a + { + public virtual event EventHandler goo; + } -public class b : a -{ - void bar() { } - override $$ -}"; + public class b : a + { + void bar() { } + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public class a -{ - public virtual event EventHandler goo; -} + public class a + { + public virtual event EventHandler goo; + } -public class b : a -{ - void bar() { } - public override event EventHandler goo;$$ -}"; + public class b : a + { + void bar() { } + public override event EventHandler goo;$$ + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } @@ -1724,32 +2188,36 @@ void bar() { } [WpfFact] public async Task CommitGenericMethod() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public class a -{ - public virtual void goo() { } -} + public class a + { + public virtual void goo() { } + } -public class b : a -{ - override $$ -}"; + public class b : a + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public class a -{ - public virtual void goo() { } -} + public class a + { + public virtual void goo() { } + } -public class b : a -{ - public override void goo() - { - base.goo();$$ - } -}"; + public class b : a + { + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } @@ -1757,34 +2225,36 @@ public override void goo() [WpfFact] public async Task CommitMethodWithNullableAttributes() { - var markupBeforeCommit = @" -#nullable enable + var markupBeforeCommit = """ + #nullable enable -class C -{ - public virtual string? Goo(string? s) { } -} + class C + { + public virtual string? Goo(string? s) { } + } -class D : C -{ - override $$ -}"; + class D : C + { + override $$ + } + """; - var expectedCodeAfterCommit = @" -#nullable enable + var expectedCodeAfterCommit = """ + #nullable enable -class C -{ - public virtual string? Goo(string? s) { } -} + class C + { + public virtual string? Goo(string? s) { } + } -class D : C -{ - public override string? Goo(string? s) - { - return base.Goo(s);$$ - } -}"; + class D : C + { + public override string? Goo(string? s) + { + return base.Goo(s);$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo(string? s)", expectedCodeAfterCommit); } @@ -1792,38 +2262,40 @@ class D : C [WpfFact] public async Task CommitMethodInNullableDisableContext() { - var markupBeforeCommit = @" -#nullable enable + var markupBeforeCommit = """ + #nullable enable -class C -{ - public virtual string? Goo(string? s) { } -} + class C + { + public virtual string? Goo(string? s) { } + } -#nullable disable + #nullable disable -class D : C -{ - override $$ -}"; + class D : C + { + override $$ + } + """; - var expectedCodeAfterCommit = @" -#nullable enable + var expectedCodeAfterCommit = """ + #nullable enable -class C -{ - public virtual string? Goo(string? s) { } -} + class C + { + public virtual string? Goo(string? s) { } + } -#nullable disable + #nullable disable -class D : C -{ - public override string Goo(string s) - { - return base.Goo(s);$$ - } -}"; + class D : C + { + public override string Goo(string s) + { + return base.Goo(s);$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo(string? s)", expectedCodeAfterCommit); } @@ -1831,40 +2303,42 @@ public override string Goo(string s) [WpfFact] public async Task CommitToStringIsExplicitlyNonNullReturning() { - var markupBeforeCommit = @" -#nullable enable + var markupBeforeCommit = """ + #nullable enable -namespace System -{ - public class Object - { - public virtual string? ToString() { } - } -} + namespace System + { + public class Object + { + public virtual string? ToString() { } + } + } -class D : System.Object -{ - override $$ -}"; + class D : System.Object + { + override $$ + } + """; - var expectedCodeAfterCommit = @" -#nullable enable + var expectedCodeAfterCommit = """ + #nullable enable -namespace System -{ - public class Object - { - public virtual string? ToString() { } - } -} + namespace System + { + public class Object + { + public virtual string? ToString() { } + } + } -class D : System.Object -{ - public override string ToString() - { - return base.ToString();$$ - } -}"; + class D : System.Object + { + public override string ToString() + { + return base.ToString();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "ToString()", expectedCodeAfterCommit); } @@ -1872,58 +2346,62 @@ public override string ToString() [WpfFact] public async Task CommitInsertIndexer() { - var markupBeforeCommit = @"public class MyIndexer -{ - private T[] arr = new T[100]; - public virtual T this[int i] - { - get - { - return arr[i]; - } - set - { - arr[i] = value; - } - } -} + var markupBeforeCommit = """ + public class MyIndexer + { + private T[] arr = new T[100]; + public virtual T this[int i] + { + get + { + return arr[i]; + } + set + { + arr[i] = value; + } + } + } -class d : MyIndexer -{ - override $$ -}"; + class d : MyIndexer + { + override $$ + } + """; - var expectedCodeAfterCommit = @"public class MyIndexer -{ - private T[] arr = new T[100]; - public virtual T this[int i] - { - get - { - return arr[i]; - } - set - { - arr[i] = value; - } - } -} + var expectedCodeAfterCommit = """ + public class MyIndexer + { + private T[] arr = new T[100]; + public virtual T this[int i] + { + get + { + return arr[i]; + } + set + { + arr[i] = value; + } + } + } -class d : MyIndexer -{ - public override T this[int i] - { - get - { - return base[i];$$ - } + class d : MyIndexer + { + public override T this[int i] + { + get + { + return base[i];$$ + } - set - { - base[i] = value; - } - } -}"; + set + { + base[i] = value; + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "this[int i]", expectedCodeAfterCommit); } @@ -1931,38 +2409,42 @@ public override T this[int i] [WpfFact] public async Task CommitAbstractIndexer() { - var markupBeforeCommit = @"public class MyIndexer -{ - private T[] arr = new T[100]; - public abstract T this[int i] { get; set; } -} + var markupBeforeCommit = """ + public class MyIndexer + { + private T[] arr = new T[100]; + public abstract T this[int i] { get; set; } + } -class d : MyIndexer -{ - override $$ -}"; + class d : MyIndexer + { + override $$ + } + """; - var expectedCodeAfterCommit = @"public class MyIndexer -{ - private T[] arr = new T[100]; - public abstract T this[int i] { get; set; } -} + var expectedCodeAfterCommit = """ + public class MyIndexer + { + private T[] arr = new T[100]; + public abstract T this[int i] { get; set; } + } -class d : MyIndexer -{ - public override T this[int i] - { - get - { - throw new System.NotImplementedException();$$ - } + class d : MyIndexer + { + public override T this[int i] + { + get + { + throw new System.NotImplementedException();$$ + } - set - { - throw new System.NotImplementedException(); - } - } -}"; + set + { + throw new System.NotImplementedException(); + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "this[int i]", expectedCodeAfterCommit); } @@ -1975,28 +2457,32 @@ public override T this[int i] [WpfFact] public async Task CommitFormats() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -override $$ -}"; + class Derived : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ - public override void goo() - { - base.goo();$$ - } -}"; + class Derived : Base + { + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } @@ -2004,32 +2490,36 @@ public override void goo() [WpfFact] public async Task CommitSimplifiesParameterTypes() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(System.Exception e); -} + public abstract class Base + { + public abstract void goo(System.Exception e); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(System.Exception e); -} + public abstract class Base + { + public abstract void goo(System.Exception e); + } -public class SomeClass : Base -{ - public override void goo(Exception e) - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override void goo(Exception e) + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(Exception e)", expectedCodeAfterCommit); } @@ -2037,32 +2527,36 @@ public override void goo(Exception e) [WpfFact] public async Task CommitSimplifiesReturnType() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract System.ArgumentException goo(System.Exception e); -} + public abstract class Base + { + public abstract System.ArgumentException goo(System.Exception e); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract System.ArgumentException goo(System.Exception e); -} + public abstract class Base + { + public abstract System.ArgumentException goo(System.Exception e); + } -public class SomeClass : Base -{ - public override ArgumentException goo(Exception e) - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override ArgumentException goo(Exception e) + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(Exception e)", expectedCodeAfterCommit); } @@ -2070,31 +2564,35 @@ public override ArgumentException goo(Exception e) [WpfFact] public async Task CommitEscapedMethodName() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract void @class(); -} + public abstract class Base + { + public abstract void @class(); + } -public class SomeClass : Base -{ - override $$ -}"; - var expectedCodeAfterCommit = @"using System; + public class SomeClass : Base + { + override $$ + } + """; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract void @class(); -} + public abstract class Base + { + public abstract void @class(); + } -public class SomeClass : Base -{ - public override void @class() - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override void @class() + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "@class()", expectedCodeAfterCommit); } @@ -2102,36 +2600,40 @@ public override void @class() [WpfFact] public async Task CommitEscapedPropertyName() { - var markupBeforeCommit = @"public abstract class Base -{ - public virtual int @class { get; set; } -} + var markupBeforeCommit = """ + public abstract class Base + { + public virtual int @class { get; set; } + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"public abstract class Base -{ - public virtual int @class { get; set; } -} + var expectedCodeAfterCommit = """ + public abstract class Base + { + public virtual int @class { get; set; } + } -public class SomeClass : Base -{ - public override int @class - { - get - { - return base.@class;$$ - } + public class SomeClass : Base + { + public override int @class + { + get + { + return base.@class;$$ + } - set - { - base.@class = value; - } - } -}"; + set + { + base.@class = value; + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "@class", expectedCodeAfterCommit); } @@ -2139,32 +2641,36 @@ public override int @class [WpfFact] public async Task CommitEscapedParameterName() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int @class); -} + public abstract class Base + { + public abstract void goo(int @class); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int @class); -} + public abstract class Base + { + public abstract void goo(int @class); + } -public class SomeClass : Base -{ - public override void goo(int @class) - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override void goo(int @class) + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int @class)", expectedCodeAfterCommit); } @@ -2172,32 +2678,36 @@ public override void goo(int @class) [WpfFact] public async Task CommitRefParameter() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int x, ref string y); -} + public abstract class Base + { + public abstract void goo(int x, ref string y); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int x, ref string y); -} + public abstract class Base + { + public abstract void goo(int x, ref string y); + } -public class SomeClass : Base -{ - public override void goo(int x, ref string y) - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override void goo(int x, ref string y) + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int x, ref string y)", expectedCodeAfterCommit); } @@ -2205,32 +2715,36 @@ public override void goo(int x, ref string y) [WpfFact] public async Task CommitOutParameter() { - var markupBeforeCommit = @"using System; + var markupBeforeCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int x, out string y); -} + public abstract class Base + { + public abstract void goo(int x, out string y); + } -public class SomeClass : Base -{ - override $$ -}"; + public class SomeClass : Base + { + override $$ + } + """; - var expectedCodeAfterCommit = @"using System; + var expectedCodeAfterCommit = """ + using System; -public abstract class Base -{ - public abstract void goo(int x, out string y); -} + public abstract class Base + { + public abstract void goo(int x, out string y); + } -public class SomeClass : Base -{ - public override void goo(int x, out string y) - { - throw new NotImplementedException();$$ - } -}"; + public class SomeClass : Base + { + public override void goo(int x, out string y) + { + throw new NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(int x, out string y)", expectedCodeAfterCommit); } @@ -2239,33 +2753,37 @@ public override void goo(int x, out string y) public async Task TestUnsafe1() { var markupBeforeCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - override $$ -}"; + public class B : A + { + override $$ + } + """; var expectedCodeAfterCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - public override void F() - { - base.F();$$ - } -}"; + public class B : A + { + public override void F() + { + base.F();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "F()", expectedCodeAfterCommit); } @@ -2274,33 +2792,37 @@ public override void F() public async Task TestUnsafe2() { var markupBeforeCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - override unsafe $$ -}"; + public class B : A + { + override unsafe $$ + } + """; var expectedCodeAfterCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - public override unsafe void F() - { - base.F();$$ - } -}"; + public class B : A + { + public override unsafe void F() + { + base.F();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "F()", expectedCodeAfterCommit); } @@ -2309,33 +2831,37 @@ public override unsafe void F() public async Task TestUnsafe3() { var markupBeforeCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - unsafe override $$ -}"; + public class B : A + { + unsafe override $$ + } + """; var expectedCodeAfterCommit = -@"public class A -{ - public unsafe virtual void F() - { - } -} + """ + public class A + { + public unsafe virtual void F() + { + } + } -public class B : A -{ - public override unsafe void F() - { - base.F();$$ - } -}"; + public class B : A + { + public override unsafe void F() + { + base.F();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "F()", expectedCodeAfterCommit); } @@ -2344,33 +2870,37 @@ public override unsafe void F() public async Task TestUnsafe4() { var markupBeforeCommit = -@"public class A -{ - public virtual void F(int* i) - { - } -} + """ + public class A + { + public virtual void F(int* i) + { + } + } -public class B : A -{ - override $$ -}"; + public class B : A + { + override $$ + } + """; var expectedCodeAfterCommit = -@"public class A -{ - public virtual void F(int* i) - { - } -} + """ + public class A + { + public virtual void F(int* i) + { + } + } -public class B : A -{ - public override unsafe void F(int* i) - { - base.F(i);$$ - } -}"; + public class B : A + { + public override unsafe void F(int* i) + { + base.F(i);$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "F(int* i)", expectedCodeAfterCommit); } @@ -2379,38 +2909,42 @@ public override unsafe void F(int* i) public async Task TestPrivateVirtualProperty() { var markupBeforeCommit = -@"public class B -{ - public virtual int Goo - { - get; private set; - } + """ + public class B + { + public virtual int Goo + { + get; private set; + } - class C : B - { - override $$ - } -}"; + class C : B + { + override $$ + } + } + """; var expectedCodeAfterCommit = -@"public class B -{ - public virtual int Goo - { - get; private set; - } - - class C : B - { - public override int Goo - { - get + """ + public class B { - return base.Goo;$$ + public virtual int Goo + { + get; private set; + } + + class C : B + { + public override int Goo + { + get + { + return base.Goo;$$ + } + } + } } - } - } -}"; + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo", expectedCodeAfterCommit); } @@ -2418,47 +2952,51 @@ public override int Goo [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/636706")] public async Task CrossLanguageParameterizedPropertyOverride() { - var vbFile = @"Public Class Goo - Public Overridable Property Bar(bay As Integer) As Integer - Get - Return 23 - End Get - Set(value As Integer) - - End Set - End Property -End Class -"; - var csharpFile = @"class Program : Goo -{ - override $$ -} -"; - var csharpFileAfterCommit = @"class Program : Goo -{ - public override int get_Bar(int bay) - { - return base.get_Bar(bay);$$ - } - public override void set_Bar(int bay, int value) - { - base.set_Bar(bay, value); - } -} -"; - var xmlString = string.Format(@" - - - VBProject - {1} - - - -{3} - - - -", LanguageNames.CSharp, csharpFile, LanguageNames.VisualBasic, vbFile); + var vbFile = """ + Public Class Goo + Public Overridable Property Bar(bay As Integer) As Integer + Get + Return 23 + End Get + Set(value As Integer) + + End Set + End Property + End Class + """; + var csharpFile = """ + class Program : Goo + { + override $$ + } + """; + var csharpFileAfterCommit = """ + class Program : Goo + { + public override int get_Bar(int bay) + { + return base.get_Bar(bay);$$ + } + public override void set_Bar(int bay, int value) + { + base.set_Bar(bay, value); + } + } + """; + var xmlString = string.Format(""" + + + VBProject + {1} + + + + {3} + + + + + """, LanguageNames.CSharp, csharpFile, LanguageNames.VisualBasic, vbFile); using var testWorkspace = EditorTestWorkspace.Create(xmlString, composition: GetComposition()); var testDocument = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument"); @@ -2470,248 +3008,478 @@ public override void set_Bar(int bay, int value) var document = solution.GetRequiredDocument(documentId); var triggerInfo = CompletionTrigger.Invoke; - var service = GetCompletionService(document.Project); - var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); - var completionItem = completionList.ItemsList.First(i => CompareItems(i.DisplayText, "Bar[int bay]")); + var service = GetCompletionService(document.Project); + var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); + var completionItem = completionList.ItemsList.First(i => CompareItems(i.DisplayText, "Bar[int bay]")); + + if (service.GetProvider(completionItem, document.Project) is ICustomCommitCompletionProvider customCommitCompletionProvider) + { + var textView = testDocument.GetTextView(); + customCommitCompletionProvider.Commit(completionItem, document, textView, textView.TextBuffer, textView.TextSnapshot, '\t'); + var actualCodeAfterCommit = textView.TextBuffer.CurrentSnapshot.AsText().ToString(); + var caretPosition = textView.Caret.Position.BufferPosition.Position; + MarkupTestFile.GetPosition(csharpFileAfterCommit, out var actualExpectedCode, out int expectedCaretPosition); + + Assert.Equal(actualExpectedCode, actualCodeAfterCommit); + Assert.Equal(expectedCaretPosition, caretPosition); + } + } + + #endregion + + #region "Commit: With Trivia" + + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] + public async Task CommitSurroundingTriviaDirective() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + #if true + override $$ + #endif + } + """; + + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + #if true + public override void goo() + { + base.goo();$$ + } + #endif + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + } + + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] + public async Task CommitBeforeTriviaDirective() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + override $$ + #if true + #endif + } + """; + + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + public override void goo() + { + base.goo();$$ + } + #if true + #endif + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + } + + [WpfFact] + public async Task CommitAfterTriviaDirective() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + #if true + #endif + override $$ + } + """; + + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + #if true + #endif + public override void goo() + { + base.goo();$$ + } + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + } + + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] + public async Task CommitBeforeComment1() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + override $$ + /* comment */ + } + """; + + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + public override void goo() + { + base.goo();$$ + } + /* comment */ + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + } + + [WpfFact] + public async Task CommitBeforeComment2() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + override $$/* comment */ + } + """; - if (service.GetProvider(completionItem, document.Project) is ICustomCommitCompletionProvider customCommitCompletionProvider) - { - var textView = testDocument.GetTextView(); - customCommitCompletionProvider.Commit(completionItem, document, textView, textView.TextBuffer, textView.TextSnapshot, '\t'); - var actualCodeAfterCommit = textView.TextBuffer.CurrentSnapshot.AsText().ToString(); - var caretPosition = textView.Caret.Position.BufferPosition.Position; - MarkupTestFile.GetPosition(csharpFileAfterCommit, out var actualExpectedCode, out int expectedCaretPosition); + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } - Assert.Equal(actualExpectedCode, actualCodeAfterCommit); - Assert.Equal(expectedCaretPosition, caretPosition); - } + class Derived : Base + { + public override void goo() + { + base.goo();$$ + } /* comment */ + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } - #endregion + [WpfFact] + public async Task CommitBeforeComment3() + { + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } - #region "Commit: With Trivia" + class Derived : Base + { + override go$$/* comment */ + } + """; - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] - public async Task CommitSurroundingTriviaDirective() + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } + + class Derived : Base + { + public override void goo() + { + base.goo();$$ + } /* comment */ + } + """; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); + } + + [WpfFact] + public async Task CommitAfterComment1() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -#if true -override $$ -#endif -}"; + class Derived : Base + { + /* comment */ + override $$ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -#if true - public override void goo() - { - base.goo();$$ - } -#endif -}"; + class Derived : Base + { + /* comment */ + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] - public async Task CommitBeforeTriviaDirective() + [WpfFact] + public async Task CommitAfterComment2() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -override $$ - #if true - #endif -}"; + class Derived : Base + { + /* comment */ + // another comment + override $$ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ - public override void goo() - { - base.goo();$$ - } -#if true -#endif -}"; + class Derived : Base + { + /* comment */ + // another comment + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitAfterTriviaDirective() + public async Task CommitAfterComment3() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -#if true -#endif -override $$ -}"; + class Derived : Base + { + /* comment */ override $$ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -#if true -#endif - public override void goo() - { - base.goo();$$ - } -}"; + class Derived : Base + { + /* comment */ + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529199")] - public async Task CommitBeforeComment() + [WpfFact] + public async Task CommitAfterComment4() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ -override $$ - /* comment */ -}"; + class Derived : Base + { + /* comment */ override go$$ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ - public override void goo() - { - base.goo();$$ - } - /* comment */ -}"; + class Derived : Base + { + /* comment */ + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] - public async Task CommitAfterComment() + public async Task CommitBeforeAndAfterComment() { - var markupBeforeCommit = @"class Base -{ - public virtual void goo() { } -} + var markupBeforeCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ - /* comment */ -override $$ -}"; + class Derived : Base + { + // Comment + override $$ + /* comment */ + } + """; - var expectedCodeAfterCommit = @"class Base -{ - public virtual void goo() { } -} + var expectedCodeAfterCommit = """ + class Base + { + public virtual void goo() { } + } -class Derived : Base -{ - /* comment */ - public override void goo() - { - base.goo();$$ - } -}"; + class Derived : Base + { + // Comment + public override void goo() + { + base.goo();$$ + } + /* comment */ + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] public async Task DoNotFormatFile() { - var markupBeforeCommit = @"class Program -{ -int zip; - public virtual void goo() - { - - } -} + var markupBeforeCommit = """ + class Program + { + int zip; + public virtual void goo() + { -class C : Program -{ -int bar; - override $$ -}"; + } + } - var expectedCodeAfterCommit = @"class Program -{ -int zip; - public virtual void goo() - { - - } -} + class C : Program + { + int bar; + override $$ + } + """; -class C : Program -{ -int bar; - public override void goo() - { - base.goo();$$ - } -}"; + var expectedCodeAfterCommit = """ + class Program + { + int zip; + public virtual void goo() + { + + } + } + + class C : Program + { + int bar; + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/736742")] public async Task AcrossPartialTypes1() { - var file1 = @"partial class c -{ -} -"; - var file2 = @"partial class c -{ - override $$ -} -"; - var csharpFileAfterCommit = @"partial class c -{ - public override bool Equals(object obj) - { - return base.Equals(obj);$$ - } -} -"; - var xmlString = string.Format(@" - - - {1} - {2} - -", LanguageNames.CSharp, file1, file2); + var file1 = """ + partial class c + { + } + """; + var file2 = """ + partial class c + { + override $$ + } + """; + var csharpFileAfterCommit = """ + partial class c + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + } + """; + var xmlString = string.Format(""" + + + {1} + {2} + + + """, LanguageNames.CSharp, file1, file2); using var testWorkspace = EditorTestWorkspace.Create(xmlString, composition: GetComposition()); var testDocument = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument2"); @@ -2741,32 +3509,36 @@ public override bool Equals(object obj) } [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/736742")] - public async Task AcrossPartialTypes2() - { - var file1 = @"partial class c -{ -} -"; - var file2 = @"partial class c -{ - override $$ -} -"; - var csharpFileAfterCommit = @"partial class c -{ - public override bool Equals(object obj) + public async Task AcrossPartialTypes2() { - return base.Equals(obj);$$ - } -} -"; - var xmlString = string.Format(@" - - - {1} - {2} - -", LanguageNames.CSharp, file2, file1); + var file1 = """ + partial class c + { + } + """; + var file2 = """ + partial class c + { + override $$ + } + """; + var csharpFileAfterCommit = """ + partial class c + { + public override bool Equals(object obj) + { + return base.Equals(obj);$$ + } + } + """; + var xmlString = string.Format(""" + + + {1} + {2} + + + """, LanguageNames.CSharp, file2, file1); using var testWorkspace = EditorTestWorkspace.Create(xmlString, composition: GetComposition()); var testDocument = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument"); @@ -2835,19 +3607,21 @@ public override required int Prop [InlineData("override required")] public async Task CommitRequiredKeywordPreserved(string ordering) { - var markupBeforeCommit = $@" - - class Base -{{ - public virtual required int Prop {{ get; }} -}} + var markupBeforeCommit = $$""" + + + class Base + { + public virtual required int Prop { get; } + } -class Derived : Base -{{ - {ordering} $$ -}} - -"; + class Derived : Base + { + {{ordering}} $$ + } + + + """; var expectedCodeAfterCommit = """ class Base @@ -2874,19 +3648,21 @@ public override required int Prop [InlineData("override required")] public async Task CommitRequiredKeywordPreservedWhenBaseIsNotRequired(string ordering) { - var markupBeforeCommit = $@" - - class Base -{{ - public virtual int Prop {{ get; }} -}} + var markupBeforeCommit = $$""" + + + class Base + { + public virtual int Prop { get; } + } -class Derived : Base -{{ - {ordering} $$ -}} - -"; + class Derived : Base + { + {{ordering}} $$ + } + + + """; var expectedCodeAfterCommit = """ class Base @@ -2987,17 +3763,19 @@ public override int this[int i] [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545678")] public async Task EditorBrowsable_IgnoredWhenOverridingMethods() { - var markup = @" -class D : B -{ - override $$ -}"; - var referencedCode = @" -public class B -{ - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public virtual void Goo() {} -}"; + var markup = """ + class D : B + { + override $$ + } + """; + var referencedCode = """ + public class B + { + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public virtual void Goo() {} + } + """; await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -3013,48 +3791,54 @@ await VerifyItemInEditorBrowsableContextsAsync( [WpfFact] public async Task DuplicateMember() { - var markupBeforeCommit = @"class Program -{ - public virtual void goo() {} - public virtual void goo() {} -} + var markupBeforeCommit = """ + class Program + { + public virtual void goo() {} + public virtual void goo() {} + } -class C : Program -{ - override $$ -}"; + class C : Program + { + override $$ + } + """; - var expectedCodeAfterCommit = @"class Program -{ - public virtual void goo() {} - public virtual void goo() {} -} + var expectedCodeAfterCommit = """ + class Program + { + public virtual void goo() {} + public virtual void goo() {} + } -class C : Program -{ - public override void goo() - { - base.goo();$$ - } -}"; + class C : Program + { + public override void goo() + { + base.goo();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } [WpfFact] public async Task LeaveTrailingTriviaAlone() { - var text = @" -namespace ConsoleApplication46 -{ - class Program - { - static void Main(string[] args) - { - } + var text = """ - override $$ - } -}"; + namespace ConsoleApplication46 + { + class Program + { + static void Main(string[] args) + { + } + + override $$ + } + } + """; using var workspace = EditorTestWorkspace.Create(LanguageNames.CSharp, new CSharpCompilationOptions(OutputKind.ConsoleApplication), new CSharpParseOptions(), [text], composition: GetComposition()); var provider = new OverrideCompletionProvider(); var testDocument = workspace.Documents.Single(); @@ -3077,36 +3861,40 @@ static void Main(string[] args) [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/8257")] public async Task NotImplementedQualifiedWhenSystemUsingNotPresent_Property() { - var markupBeforeCommit = @"abstract class C -{ - public abstract int goo { get; set; }; -} + var markupBeforeCommit = """ + abstract class C + { + public abstract int goo { get; set; }; + } -class Program : C -{ - override $$ -}"; + class Program : C + { + override $$ + } + """; - var expectedCodeAfterCommit = @"abstract class C -{ - public abstract int goo { get; set; }; -} + var expectedCodeAfterCommit = """ + abstract class C + { + public abstract int goo { get; set; }; + } -class Program : C -{ - public override int goo - { - get - { - throw new System.NotImplementedException();$$ - } + class Program : C + { + public override int goo + { + get + { + throw new System.NotImplementedException();$$ + } - set - { - throw new System.NotImplementedException(); - } - } -}"; + set + { + throw new System.NotImplementedException(); + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo", expectedCodeAfterCommit); } @@ -3114,28 +3902,32 @@ public override int goo [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/8257")] public async Task NotImplementedQualifiedWhenSystemUsingNotPresent_Method() { - var markupBeforeCommit = @"abstract class C -{ - public abstract void goo(); -} + var markupBeforeCommit = """ + abstract class C + { + public abstract void goo(); + } -class Program : C -{ - override $$ -}"; + class Program : C + { + override $$ + } + """; - var expectedCodeAfterCommit = @"abstract class C -{ - public abstract void goo(); -} + var expectedCodeAfterCommit = """ + abstract class C + { + public abstract void goo(); + } -class Program : C -{ - public override void goo() - { - throw new System.NotImplementedException();$$ - } -}"; + class Program : C + { + public override void goo() + { + throw new System.NotImplementedException();$$ + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit); } @@ -3143,38 +3935,40 @@ public override void goo() [Fact] public async Task FilterOutMethodsWithNonRoundTrippableSymbolKeys() { - var text = XElement.Parse(@" - - - - - - -namespace ClassLibrary2 -{ - public class Missing {} -} - - - - P2 - -namespace ClassLibrary7 -{ - public class Class1 - { - public virtual void Goo(ClassLibrary2.Missing m) {} - public virtual void Bar() {} - } -} - - -"); + var text = XElement.Parse(""" + + + + + + + + namespace ClassLibrary2 + { + public class Missing {} + } + + + + P2 + + namespace ClassLibrary7 + { + public class Class1 + { + public virtual void Goo(ClassLibrary2.Missing m) {} + public virtual void Bar() {} + } + } + + + + """); // P3 has a project ref to Project P2 and uses the type "Missing" from P2 // as the return type of a virtual method. @@ -3207,34 +4001,38 @@ public virtual void Bar() {} [WpfFact] public async Task TestInParameter() { - var source = XElement.Parse(@" - - - - -"); + var markup = """ + public class SomeClass : Base + { + override $$ + } + """; + var source = XElement.Parse($""" + + + + + + + """); using var workspace = EditorTestWorkspace.Create(source, composition: GetComposition()); - var before = @" -public abstract class Base -{ - public abstract void M(in int x); -}"; + var before = """ + public abstract class Base + { + public abstract void M(in int x); + } + """; - var after = @" -public class SomeClass : Base -{ - public override void M(in int x) - { - throw new System.NotImplementedException(); - } -} -"; + var after = """ + public class SomeClass : Base + { + public override void M(in int x) + { + throw new System.NotImplementedException(); + } + } + """; var origComp = await workspace.CurrentSolution.Projects.Single().GetRequiredCompilationAsync(CancellationToken.None); var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); @@ -3271,61 +4069,63 @@ public override void M(in int x) [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/39909")] public async Task CommitAddsMissingImports() { - var markupBeforeCommit = @" -namespace NS1 -{ - using NS2; + var markupBeforeCommit = """ + namespace NS1 + { + using NS2; - public class Goo - { - public virtual bool Bar(Baz baz) => true; - } -} + public class Goo + { + public virtual bool Bar(Baz baz) => true; + } + } -namespace NS2 -{ - public class Baz {} -} + namespace NS2 + { + public class Baz {} + } -namespace NS3 -{ - using NS1; + namespace NS3 + { + using NS1; - class D : Goo - { - override $$ - } -}"; + class D : Goo + { + override $$ + } + } + """; - var expectedCodeAfterCommit = @" -namespace NS1 -{ - using NS2; + var expectedCodeAfterCommit = """ + namespace NS1 + { + using NS2; - public class Goo - { - public virtual bool Bar(Baz baz) => true; - } -} + public class Goo + { + public virtual bool Bar(Baz baz) => true; + } + } -namespace NS2 -{ - public class Baz {} -} + namespace NS2 + { + public class Baz {} + } -namespace NS3 -{ - using NS1; - using NS2; + namespace NS3 + { + using NS1; + using NS2; - class D : Goo - { - public override bool Bar(Baz baz) - { - return base.Bar(baz);$$ - } - } -}"; + class D : Goo + { + public override bool Bar(Baz baz) + { + return base.Bar(baz);$$ + } + } + } + """; await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Bar(NS2.Baz baz)", expectedCodeAfterCommit); } @@ -3333,21 +4133,25 @@ public override bool Bar(Baz baz) [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47941")] public async Task OverrideInRecordWithoutExplicitOverriddenMember() { - await VerifyItemExistsAsync(@"record Program -{ - override $$ -}", "ToString()"); + await VerifyItemExistsAsync(""" + record Program + { + override $$ + } + """, "ToString()"); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47941")] public async Task OverrideInRecordWithExplicitOverriddenMember() { - await VerifyItemIsAbsentAsync(@"record Program -{ - public override string ToString() => ""; + await VerifyItemIsAbsentAsync(""" + record Program + { + public override string ToString() => "; - override $$ -}", "ToString()"); + override $$ + } + """, "ToString()"); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/47973")] @@ -3360,60 +4164,66 @@ public async Task NoCloneInOverriddenRecord() var cloneMemberName = (string)typeof(WellKnownMemberNames).GetField("CloneMethodName", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); Assert.Equal("$", cloneMemberName); - await VerifyItemIsAbsentAsync(@" -record Base(); + await VerifyItemIsAbsentAsync(""" + record Base(); -record Program : Base -{ - override $$ -}", cloneMemberName); + record Program : Base + { + override $$ + } + """, cloneMemberName); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/48640")] public async Task ObjectEqualsInClass() { - await VerifyItemExistsAsync(@" -class Program -{ - override $$ -}", "Equals(object obj)"); + await VerifyItemExistsAsync(""" + class Program + { + override $$ + } + """, "Equals(object obj)"); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/48640")] public async Task NoObjectEqualsInOverriddenRecord1() { - await VerifyItemIsAbsentAsync(@" -record Program -{ - override $$ -}", "Equals(object obj)"); + await VerifyItemIsAbsentAsync(""" + record Program + { + override $$ + } + """, "Equals(object obj)"); - await VerifyItemExistsAsync(@" -record Program -{ - override $$ -}", "ToString()"); + await VerifyItemExistsAsync(""" + record Program + { + override $$ + } + """, "ToString()"); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/48640")] public async Task NoObjectEqualsInOverriddenRecord() { - await VerifyItemIsAbsentAsync(@" -record Base(); + await VerifyItemIsAbsentAsync(""" + record Base(); -record Program : Base -{ - override $$ -}", "Equals(object obj)"); + record Program : Base + { + override $$ + } + """, "Equals(object obj)"); - await VerifyItemExistsAsync(@" -record Base(); + await VerifyItemExistsAsync(""" + record Base(); -record Program : Base -{ - override $$ -}", "ToString()"); + record Program : Base + { + override $$ + } + """, "ToString()"); } [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/64887")] diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 898774278f37c..199cf2081e86b 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; @@ -775,6 +776,7 @@ private static async Task TestNuGetAndVsixAnalyzerCoreAsync( if (nugetAnalyzerReferences.Count > 0) { project = project.WithAnalyzerReferences([new AnalyzerImageReference([.. nugetAnalyzerReferences])]); + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences); } var document = project.Documents.Single(); diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 8b2080aeaf537..636fe5c32e1f9 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -12,6 +13,7 @@ using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Moq; @@ -354,6 +356,151 @@ public class D Assert.True(searchGeneratedDocumentsAsyncCalled); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77051")] + public async Task DocumentScopeRelatedDocuments_Inheritance() + { + using var workspace = EditorTestWorkspace.Create(""" + + + + public class C : Base + { + // Starting search here. + void Goo1() { } + } + + + public class Base + { + // Should find this. + void Goo2() { } + } + public class Other + { + // Should not find this. + void Goo3() { } + } + + + + """, composition: FirstActiveAndVisibleComposition); + + var hostMock = new Mock(MockBehavior.Strict); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); + + var project = workspace.CurrentSolution.Projects.Single(); + var searchService = project.GetRequiredLanguageService(); + + hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(() => searchService); + + var callback = new TestNavigateToSearchCallback(); + + var searcher = NavigateToSearcher.Create( + workspace.CurrentSolution, + callback, + "Goo", + kinds: searchService.KindsProvided, + hostMock.Object); + + await searcher.SearchAsync(NavigateToSearchScope.Document, CancellationToken.None); + + Assert.Equal(2, callback.Results.Count); + + var firstDocument = project.Documents.Single(d => d.FilePath!.Contains("file1")); + var secondDocument = project.Documents.Single(d => d.FilePath!.Contains("file2")); + + var firstDocumentResult = Assert.Single(callback.Results, r => r.NavigableItem.Document.Id == firstDocument.Id); + var secondDocumentResult = Assert.Single(callback.Results, r => r.NavigableItem.Document.Id == secondDocument.Id); + + Assert.Equal("Goo1", firstDocumentResult.Name); + Assert.Equal("Goo2", secondDocumentResult.Name); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77051")] + public async Task DocumentScopeRelatedDocuments_Partial() + { + using var workspace = EditorTestWorkspace.Create(""" + + + + public partial class C + { + // Starting search here. + void Goo1() { } + } + + + public class Base + { + // Should not find this. + void Goo2() { } + } + public partial class C + { + // Should find this. + void Goo3() { } + } + + + + """, composition: FirstActiveAndVisibleComposition); + + var hostMock = new Mock(MockBehavior.Strict); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); + + var project = workspace.CurrentSolution.Projects.Single(); + var searchService = project.GetRequiredLanguageService(); + + hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(() => searchService); + + var callback = new TestNavigateToSearchCallback(); + + var searcher = NavigateToSearcher.Create( + workspace.CurrentSolution, + callback, + "Goo", + kinds: searchService.KindsProvided, + hostMock.Object); + + await searcher.SearchAsync(NavigateToSearchScope.Document, CancellationToken.None); + + Assert.Equal(2, callback.Results.Count); + + var firstDocument = project.Documents.Single(d => d.FilePath!.Contains("file1")); + var secondDocument = project.Documents.Single(d => d.FilePath!.Contains("file2")); + + var firstDocumentResult = Assert.Single(callback.Results, r => r.NavigableItem.Document.Id == firstDocument.Id); + var secondDocumentResult = Assert.Single(callback.Results, r => r.NavigableItem.Document.Id == secondDocument.Id); + + Assert.Equal("Goo1", firstDocumentResult.Name); + Assert.Equal("Goo3", secondDocumentResult.Name); + } + + private sealed class TestNavigateToSearchCallback : INavigateToSearchCallback + { + public readonly ConcurrentBag Results = []; + + public void Done(bool isFullyLoaded) + { + } + + public void ReportIncomplete() + { + } + + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) + { + foreach (var result in results) + this.Results.Add(result); + + return Task.CompletedTask; + } + + public void ReportProgress(int current, int maximum) + { + } + } + private sealed class MockAdvancedNavigateToSearchService : IAdvancedNavigateToSearchService { public IImmutableSet KindsProvided => AbstractNavigateToSearchService.AllKinds; diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index f4c7b4efab924..8e6fd350f3e08 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -7998,6 +7998,44 @@ void N() NullabilityAnalysis(string.Format(FeaturesResources._0_is_not_null_here, "s"))); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public async Task NullableBackingFieldThatIsMaybeNull() + { + await TestWithOptionsAsync(TestOptions.RegularPreview, + """ + #nullable enable + + class X + { + string? P + { + get => $$field; + } + } + """, + MainDescription($"({FeaturesResources.field}) string? X.P.field"), + NullabilityAnalysis(string.Format(FeaturesResources._0_may_be_null_here, "P.field"))); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public async Task NullableBackingFieldThatIsNotNull() + { + await TestWithOptionsAsync(TestOptions.RegularPreview, + """ + #nullable enable + + class X + { + string P + { + get => $$field; + } = "a"; + } + """, + MainDescription($"({FeaturesResources.field}) string X.P.field"), + NullabilityAnalysis(string.Format(FeaturesResources._0_is_not_null_here, "P.field"))); + } + [Fact] public async Task NullablePropertyThatIsMaybeNull() { diff --git a/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs index c91609f2d7a65..7a65e619c7374 100644 --- a/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/RawStringLiteral/RawStringLiteralCommandHandlerTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RawStringLiteral; [UseExportProvider] -public class RawStringLiteralCommandHandlerTests +public sealed class RawStringLiteralCommandHandlerTests { internal sealed class RawStringLiteralTestState : AbstractCommandHandlerTestState { @@ -520,6 +520,48 @@ public void TestReturnWithinEndQuotesInMultilineRawString() testState.SendReturn(handled: false); } + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/76773")] + public void TestReturnPriorToStartingQuotes1() + { + using var testState = RawStringLiteralTestState.CreateTestState( + """" + var v = Goo($$""" + bar); + """ + """"); + + // Should not handle this as we're not inside the raw string. + testState.SendReturn(handled: false); + } + + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/76773")] + public void TestReturnPriorToStartingQuotes2() + { + using var testState = RawStringLiteralTestState.CreateTestState( + """" + var v = Goo("$$"" + bar); + """ + """"); + + // Should not handle this as we're not inside the raw string. + testState.SendReturn(handled: false); + } + + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/76773")] + public void TestReturnPriorToStartingQuotes3() + { + using var testState = RawStringLiteralTestState.CreateTestState( + """" + var v = Goo(""$$" + bar); + """ + """"); + + // Should not handle this as we're not inside the raw string. + testState.SendReturn(handled: false); + } + #endregion #region generate initial empty raw string diff --git a/src/EditorFeatures/CSharpTest/Structure/CommentStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/CommentStructureTests.cs index 9071be18f4622..3fa9bc368bd14 100644 --- a/src/EditorFeatures/CSharpTest/Structure/CommentStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/CommentStructureTests.cs @@ -47,7 +47,7 @@ internal override async Task> GetBlockSpansWorkerAsync return CreateCommentBlockSpan(token.TrailingTrivia); } - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 27f3d213052c5..e4a42458c6bfa 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -479,7 +479,11 @@ internal async Task TestGetCompilationOnCrossLanguageDependentProjectChanged( var solutionX = workspace.CurrentSolution; var document1 = new EditorTestHostDocument(@"public class C { }"); - var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1", + compilationOptions: solutionX.Services + .GetRequiredLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)); var document2 = new EditorTestHostDocument(""" Public Class D @@ -533,7 +537,11 @@ public async Task TestDependentSemanticVersionChangesWhenNotOriginallyAccessed() var solutionX = workspace.CurrentSolution; var document1 = new EditorTestHostDocument(@"public class C { }"); - var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1", + compilationOptions: solutionX.Services + .GetRequiredLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)); var document2 = new EditorTestHostDocument(""" Public Class D @@ -601,7 +609,11 @@ internal async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInPr var solutionX = workspace.CurrentSolution; var document1 = new EditorTestHostDocument(@"public class C { }"); - var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1", + compilationOptions: solutionX.Services + .GetRequiredLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)); var document2 = new EditorTestHostDocument(""" Public Class D diff --git a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/Json/CSharpJsonParserTests_NstTests.cs b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/Json/CSharpJsonParserTests_NstTests.cs index ff8ddadcd534f..d6af20208ea5e 100644 --- a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/Json/CSharpJsonParserTests_NstTests.cs +++ b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/Json/CSharpJsonParserTests_NstTests.cs @@ -16,7 +16,7 @@ private void TestNST( { var (_, tree, allChars) = JustParseTree(stringText, JsonOptions.Strict, conversionFailureOk: false); Assert.NotNull(tree); - Roslyn.Utilities.Contract.ThrowIfNull(tree); + Contract.ThrowIfNull(tree); var actualTree = TreeToText(tree!).Replace("\"", "\"\""); Assert.Equal(expected.Replace("\"", "\"\""), actualTree); diff --git a/src/EditorFeatures/Core.Wpf/BackgroundWorkIndicator/BackgroundWorkIndicatorContext.cs b/src/EditorFeatures/Core.Wpf/BackgroundWorkIndicator/BackgroundWorkIndicatorContext.cs index 037808e696ec0..8e4e985af0ed3 100644 --- a/src/EditorFeatures/Core.Wpf/BackgroundWorkIndicator/BackgroundWorkIndicatorContext.cs +++ b/src/EditorFeatures/Core.Wpf/BackgroundWorkIndicator/BackgroundWorkIndicatorContext.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Editor; diff --git a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs index 26e47c2026d5c..7fa4b6d4cc0de 100644 --- a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; diff --git a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs index 75f3ca53e80f3..47b10e864bfcf 100644 --- a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; diff --git a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs index dac6664277e6d..427695b244831 100644 --- a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs +++ b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs @@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; diff --git a/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs b/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs index 923049a4c1d8b..c53f4d5102135 100644 --- a/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs +++ b/src/EditorFeatures/Core/Editor/GoToAdjacentMemberCommandHandler.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; @@ -103,12 +104,11 @@ private bool ExecuteCommandImpl(EditorCommandArgs args, bool gotoNextMember, Com /// internal static int? GetTargetPosition(ISyntaxFactsService service, SyntaxNode root, int caretPosition, bool next) { - using var pooledMembers = service.GetMethodLevelMembers(root); - var members = pooledMembers.Object; + // Specifies false for discardLargeInstances as these objects commonly exceed the default ArrayBuilder capacity threshold. + using var _ = ArrayBuilder.GetInstance(discardLargeInstances: false, out var members); + service.AddMethodLevelMembers(root, members); if (members.Count == 0) - { return null; - } var starts = members.Select(m => MemberStart(m)).ToArray(); var index = Array.BinarySearch(starts, caretPosition); diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index 2260badd1ab46..72c59049b7fd8 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs index f5604f7827400..5939394e80816 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater; using Microsoft.CodeAnalysis.EditorConfig; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using RoslynEnumerableExtensions = Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions.EnumerableExtensions; @@ -19,8 +20,13 @@ internal sealed class AnalyzerSettingsProvider : SettingsProviderBase +internal sealed class AnalyzerSettingsProviderFactory( + Workspace workspace, + IDiagnosticAnalyzerService analyzerService, + IGlobalOptionService globalOptionService) : IWorkspaceSettingsProviderFactory { - private readonly Workspace _workspace = workspace; - private readonly IDiagnosticAnalyzerService _analyzerService = analyzerService; - public ISettingsProvider GetForFile(string filePath) { - var updater = new AnalyzerSettingsUpdater(_workspace, filePath); - return new AnalyzerSettingsProvider(filePath, updater, _workspace, _analyzerService); + var updater = new AnalyzerSettingsUpdater(workspace, filePath); + return new AnalyzerSettingsProvider(filePath, updater, workspace, analyzerService, globalOptionService); } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs index b098b95760e16..7945e987215b1 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs @@ -8,16 +8,17 @@ using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyzer; [ExportWorkspaceServiceFactory(typeof(IWorkspaceSettingsProviderFactory)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class AnalyzerSettingsWorkspaceServiceFactory(IDiagnosticAnalyzerService analyzerService) : IWorkspaceServiceFactory +internal sealed class AnalyzerSettingsWorkspaceServiceFactory( + IDiagnosticAnalyzerService analyzerService, + IGlobalOptionService globalOptionService) : IWorkspaceServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService = analyzerService; - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, _analyzerService); + => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, analyzerService, globalOptionService); } diff --git a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs index a93e35b1372d2..b8ce01e14c998 100644 --- a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs +++ b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs @@ -103,7 +103,7 @@ public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) if (service == null) return false; - Roslyn.Utilities.Contract.ThrowIfNull(document); + Contract.ThrowIfNull(document); // cancel any prior find-refs that might be in progress. _cancellationTokenSource.Cancel(); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs index 2908b93d0f156..631cd5208e197 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Core.Imaging; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; using Microsoft.VisualStudio.Text; diff --git a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs index 8b4ee5b63197d..47aef36e9a97c 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs @@ -26,6 +26,7 @@ namespace Microsoft.CodeAnalysis.Interactive; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Threading; using RelativePathResolver = Scripting::Microsoft.CodeAnalysis.RelativePathResolver; internal sealed class InteractiveSession : IDisposable diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index 2ffdb061e7ab2..ae74faa7622ea 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -34,7 +34,6 @@ - diff --git a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs index f988ab4ffc1d4..dfb89e2b7740c 100644 --- a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Workspaces; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 6f1b14d5dc446..70dabe4a7aa68 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -3,16 +3,15 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Telemetry; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -33,13 +32,6 @@ internal sealed class SolutionChecksumUpdater private readonly IDocumentTrackingService _documentTrackingService; - /// - /// Queue to push out text changes in a batched fashion when we hear about them. Because these should be short - /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't - /// start queuing more requests into this until we become unpaused. - /// - private readonly AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)> _textChangeQueue; - /// /// Queue for kicking off the work to synchronize the primary workspace's solution. /// @@ -53,6 +45,13 @@ internal sealed class SolutionChecksumUpdater private readonly object _gate = new(); private bool _isSynchronizeWorkspacePaused; + private readonly CancellationToken _shutdownToken; + + private const string SynchronizeTextChangesStatusSucceededMetricName = "SucceededCount"; + private const string SynchronizeTextChangesStatusFailedMetricName = "FailedCount"; + private const string SynchronizeTextChangesStatusSucceededKeyName = nameof(SolutionChecksumUpdater) + "." + SynchronizeTextChangesStatusSucceededMetricName; + private const string SynchronizeTextChangesStatusFailedKeyName = nameof(SolutionChecksumUpdater) + "." + SynchronizeTextChangesStatusFailedMetricName; + public SolutionChecksumUpdater( Workspace workspace, IAsynchronousOperationListenerProvider listenerProvider, @@ -65,20 +64,14 @@ public SolutionChecksumUpdater( _workspace = workspace; _documentTrackingService = workspace.Services.GetRequiredService(); + _shutdownToken = shutdownToken; + _synchronizeWorkspaceQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.NearImmediate, SynchronizePrimaryWorkspaceAsync, listener, shutdownToken); - // Text changes and active doc info are tiny messages. So attempt to send them immediately. Just batching - // things up if we get a flurry of notifications. - _textChangeQueue = new AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)>( - TimeSpan.Zero, - SynchronizeTextChangesAsync, - listener, - shutdownToken); - _synchronizeActiveDocumentQueue = new AsyncBatchingWorkQueue( TimeSpan.Zero, SynchronizeActiveDocumentAsync, @@ -87,6 +80,7 @@ public SolutionChecksumUpdater( // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspace.WorkspaceChangedImmediate += OnWorkspaceChangedImmediate; _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; if (_globalOperationService != null) @@ -107,6 +101,7 @@ public void Shutdown() _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; _workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspace.WorkspaceChangedImmediate -= OnWorkspaceChangedImmediate; if (_globalOperationService != null) { @@ -143,14 +138,6 @@ private void ResumeSynchronizingPrimaryWorkspace() private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) { - if (e.Kind == WorkspaceChangeKind.DocumentChanged) - { - var oldDocument = e.OldSolution.GetDocument(e.DocumentId); - var newDocument = e.NewSolution.GetDocument(e.DocumentId); - if (oldDocument != null && newDocument != null) - _textChangeQueue.AddWork((oldDocument, newDocument)); - } - // Check if we're currently paused. If so ignore this notification. We don't want to any work in response // to whatever the workspace is doing. lock (_gate) @@ -160,6 +147,20 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) } } + private void OnWorkspaceChangedImmediate(object? sender, WorkspaceChangeEventArgs e) + { + if (e.Kind == WorkspaceChangeKind.DocumentChanged) + { + var documentId = e.DocumentId!; + var oldDocument = e.OldSolution.GetRequiredDocument(documentId); + var newDocument = e.NewSolution.GetRequiredDocument(documentId); + + // Fire-and-forget to dispatch notification of this document change event to the remote side + // and return to the caller as quickly as possible. + _ = DispatchSynchronizeTextChangesAsync(oldDocument, newDocument).ReportNonFatalErrorAsync(); + } + } + private void OnActiveDocumentChanged(object? sender, DocumentId? e) => _synchronizeActiveDocumentQueue.AddWork(); @@ -202,57 +203,81 @@ await client.TryInvokeAsync( cancellationToken).ConfigureAwait(false); } - private async ValueTask SynchronizeTextChangesAsync( - ImmutableSegmentedList<(Document oldDocument, Document newDocument)> values, - CancellationToken cancellationToken) + private async Task DispatchSynchronizeTextChangesAsync( + Document oldDocument, + Document newDocument) { - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); - if (client == null) + // Explicitly force a yield point here to ensure this method returns to the caller immediately and that + // all work is done off the calling thread. + await Task.Yield().ConfigureAwait(false); + + // Inform the remote asset synchronization service as quickly as possible + // about the text changes between oldDocument and newDocument. By doing this, we can + // reduce the likelihood of the remote side encountering an unknown checksum and + // requiring a synchronization of the full document contents. + var wasSynchronized = await DispatchSynchronizeTextChangesHelperAsync().ConfigureAwait(false); + if (wasSynchronized == null) return; - // this pushes text changes to the remote side if it can. this is purely perf optimization. whether this - // pushing text change worked or not doesn't affect feature's functionality. - // - // this basically see whether it can cheaply find out text changes between 2 snapshots, if it can, it will - // send out that text changes to remote side. - // - // the remote side, once got the text change, will again see whether it can use that text change information - // without any high cost and create new snapshot from it. - // - // otherwise, it will do the normal behavior of getting full text from VS side. this optimization saves - // times we need to do full text synchronization for typing scenario. - using var _ = ArrayBuilder<(DocumentId id, Checksum textChecksum, ImmutableArray changes, Checksum newTextChecksum)>.GetInstance(out var builder); - - foreach (var (oldDocument, newDocument) in values) + // Update aggregated telemetry with success status of sending the synchronization data. + var metricName = wasSynchronized.Value ? SynchronizeTextChangesStatusSucceededMetricName : SynchronizeTextChangesStatusFailedMetricName; + var keyName = wasSynchronized.Value ? SynchronizeTextChangesStatusSucceededKeyName : SynchronizeTextChangesStatusFailedKeyName; + TelemetryLogging.LogAggregatedCounter(FunctionId.ChecksumUpdater_SynchronizeTextChangesStatus, KeyValueLogMessage.Create(m => { + m[TelemetryLogging.KeyName] = keyName; + m[TelemetryLogging.KeyValue] = 1L; + m[TelemetryLogging.KeyMetricName] = metricName; + })); + + return; + + async Task DispatchSynchronizeTextChangesHelperAsync() + { + var client = await RemoteHostClient.TryGetClientAsync(_workspace, _shutdownToken).ConfigureAwait(false); + if (client == null) + { + // null return value indicates that we were unable to synchronize the text changes, but to not log + // telemetry against that inability as turning off OOP is not a failure. + return null; + } + + // this pushes text changes to the remote side if it can. this is purely perf optimization. whether this + // pushing text change worked or not doesn't affect feature's functionality. + // + // this basically see whether it can cheaply find out text changes between 2 snapshots, if it can, it will + // send out that text changes to remote side. + // + // the remote side, once got the text change, will again see whether it can use that text change information + // without any high cost and create new snapshot from it. + // + // otherwise, it will do the normal behavior of getting full text from VS side. this optimization saves + // times we need to do full text synchronization for typing scenario. if (!oldDocument.TryGetText(out var oldText) || !newDocument.TryGetText(out var newText)) { // we only support case where text already exist - continue; + return false; } // Avoid allocating text before seeing if we can bail out. var changeRanges = newText.GetChangeRanges(oldText).AsImmutable(); if (changeRanges.Length == 0) - continue; + return true; // no benefit here. pulling from remote host is more efficient if (changeRanges is [{ Span.Length: var singleChangeLength }] && singleChangeLength == oldText.Length) - continue; + return true; - var state = await oldDocument.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - var newState = await newDocument.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var state = await oldDocument.State.GetStateChecksumsAsync(_shutdownToken).ConfigureAwait(false); + var newState = await newDocument.State.GetStateChecksumsAsync(_shutdownToken).ConfigureAwait(false); var textChanges = newText.GetTextChanges(oldText).AsImmutable(); - builder.Add((oldDocument.Id, state.Text, textChanges, newState.Text)); - } - if (builder.Count == 0) - return; + await client.TryInvokeAsync( + (service, cancellationToken) => service.SynchronizeTextChangesAsync(oldDocument.Id, state.Text, textChanges, newState.Text, cancellationToken), + _shutdownToken).ConfigureAwait(false); - await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeTextChangesAsync(builder.ToImmutableAndClear(), cancellationToken), - cancellationToken).ConfigureAwait(false); + return true; + } } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs index 2c627b9a63a9e..d7301c29e8156 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs @@ -4,6 +4,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; diff --git a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs index 123abe40b4e5d..5fe501dff9197 100644 --- a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs +++ b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LegacySolutionEvents; diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index 18d853a4388a0..3a7ed489a5f72 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index a071145057f31..c9eb12fb47e54 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Utilities; using Microsoft.CodeAnalysis.Workspaces; using Microsoft.VisualStudio.Text; diff --git a/src/EditorFeatures/Core/Tagging/TaggerMainThreadManager.cs b/src/EditorFeatures/Core/Tagging/TaggerMainThreadManager.cs index 5378232757c2d..c7606e884ed3c 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerMainThreadManager.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerMainThreadManager.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; using TaggerUIData = (bool isVisible, Microsoft.VisualStudio.Text.SnapshotPoint? caretPosition, Roslyn.Utilities.OneOrMany spansToTag); diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf index cda4910f11c0d..02ee0c7190b58 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Bylo dosaženo limitu chatu. [Upgradujte nyní] nebo počkejte na resetování limitu. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf index 223527ee2ae83..251a1a6ba30ba 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Das Chatlimit wurde erreicht, [upgrade now] oder warten Sie, bis das Limit zurückgesetzt wurde. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf index 8f96fcc9c21d7..c655afaa7ef4a 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Se alcanzó el límite de chats, [upgrade now] o esperar a que se restablezca el límite. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf index cd4dc9731af30..961b6268a1cba 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + La limite de conversation a été atteinte, [upgrade now] ou attendez la réinitialisation de la limite. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf index 264651a81a1d2..b6a038b54c877 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + È stato raggiunto il limite di chat, [upgrade now] o attendere la reimpostazione del limite. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf index 162b9ff3a5da9..acf184340e87a 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + チャットの制限に達しました。[今すぐアップグレード] するか、制限がリセットされるまでお待ちください。 The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf index 967a8c3d65f76..f55e5980a48bc 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + 채팅 제한에 도달했습니다. [upgrade now] 또는 제한이 재설정될 때까지 기다리세요. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf index 13c31e460e558..0e512ff2be43d 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Osiągnięto limit czatu, [upgrade now] lub poczekaj na zresetowanie limitu. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf index f0b76297e818e..40b3167932d02 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Limite de chat atingido, [upgrade now] ou aguarde até que o limite seja redefinido. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf index 882882e284155..6d2105d4343be 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Достигнут предел использования чата. [Повысьте статус сейчас] или дождитесь сброса ограничения. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf index 6aff6dc59e379..be6fcde4ffa57 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + Sohbet sınırına ulaşıldı, [upgrade now] veya sınırın sıfırlanması için bekleyin. The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf index 0bed7187047f0..a985dc67447b9 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + 已达到聊天限制,[upgrade now] 或等待重置限制。 The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf index 7daf637ef2194..9ebef803db3be 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf @@ -19,7 +19,7 @@ Chat limit reached, [upgrade now] or wait for the limit to reset. - Chat limit reached, [upgrade now] or wait for the limit to reset. + 已達到聊天限制,[upgrade now] 或等候重設限制。 The text surrounded by "[" and "]" characters will be hyperlinked. Please ensure the localized text still has "[" and "]" characters. diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs index 827de1a7e388e..536fad3c1140f 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs @@ -85,7 +85,7 @@ public async Task GetParameterConfigurationAsync() return changeSignatureAnalyzedSucceedContext.ParameterConfiguration; } - throw Roslyn.Utilities.ExceptionUtilities.UnexpectedValue(((CannotChangeSignatureAnalyzedContext)context).CannotChangeSignatureReason.ToString()); + throw ExceptionUtilities.UnexpectedValue(((CannotChangeSignatureAnalyzedContext)context).CannotChangeSignatureReason.ToString()); } public void Dispose() diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs b/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs deleted file mode 100644 index 6f33ec3b27cf8..0000000000000 --- a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.ServiceBroker; -using IServiceProvider = System.IServiceProvider; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp; - -using AnalyzeDocumentAsyncDelegateType = Func>>; -using GetAvailablePromptTitlesAsyncDelegateType = Func>>; -using GetCachedDiagnosticsAsyncDelegateType = Func>>; -using IsAvailableAsyncDelegateType = Func>; -using StartRefinementSessionAsyncDelegateType = Func; -using GetOnTheFlyDocsAsyncDelegateType = Func, string, CancellationToken, Task<(string responseString, bool isQuotaExceeded)>>; -using IsAnyExclusionAsyncDelegateType = Func>; -using IsFileExcludedAsyncDelegateType = Func>; - -internal sealed partial class CSharpCopilotCodeAnalysisService -{ - // A temporary helper to get access to the implementation of IExternalCSharpCopilotCodeAnalysisService, until it can be MEF exported. - private sealed class ReflectionWrapper : IExternalCSharpCopilotCodeAnalysisService - { - private const string CopilotRoslynDllName = "Microsoft.VisualStudio.Copilot.Roslyn, Version=0.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - private const string InternalCSharpCopilotAnalyzerTypeFullName = "Microsoft.VisualStudio.Copilot.Roslyn.Analyzer.InternalCSharpCopilotAnalyzer"; - - private const string IsAvailableAsyncMethodName = "IsAvailableAsync"; - private const string GetAvailablePromptTitlesAsyncMethodName = "GetAvailablePromptTitlesAsync"; - private const string AnalyzeDocumentAsyncMethodName = "AnalyzeDocumentAsync"; - private const string GetCachedDiagnosticsAsyncMethodName = "GetCachedDiagnosticsAsync"; - private const string StartRefinementSessionAsyncMethodName = "StartRefinementSessionAsync"; - private const string GetOnTheFlyDocsAsyncMethodName = "GetOnTheFlyDocsAsync"; - private const string IsFileExcludedAsyncMethodName = "IsFileExcludedAsync"; - - // Create and cache closed delegate to ensure we use a singleton object and with better performance. - private readonly Type? _analyzerType; - private readonly object? _analyzerInstance; - private readonly Lazy _lazyIsAvailableAsyncDelegate; - private readonly Lazy _lazyGetAvailablePromptTitlesAsyncDelegate; - private readonly Lazy _lazyAnalyzeDocumentAsyncDelegate; - private readonly Lazy _lazyGetCachedDiagnosticsAsyncDelegate; - private readonly Lazy _lazyStartRefinementSessionAsyncDelegate; - private readonly Lazy _lazyGetOnTheFlyDocsAsyncDelegate; - private readonly Lazy _lazyIsFileExcludedAsyncDelegate; - - public ReflectionWrapper(IServiceProvider serviceProvider, IVsService brokeredServiceContainer) - { - try - { - var assembly = Assembly.Load(CopilotRoslynDllName); - var analyzerType = assembly.GetType(InternalCSharpCopilotAnalyzerTypeFullName); - if (analyzerType is not null) - { - var analyzerInstance = Activator.CreateInstance(analyzerType, serviceProvider, brokeredServiceContainer); - if (analyzerInstance is not null) - { - _analyzerType = analyzerType; - _analyzerInstance = analyzerInstance; - } - } - } - catch - { - // Catch all here since failure is expected if user has no copilot chat or an older version of it installed. - } - - _lazyIsAvailableAsyncDelegate = new(CreateIsAvailableAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyGetAvailablePromptTitlesAsyncDelegate = new(CreateGetAvailablePromptTitlesAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyAnalyzeDocumentAsyncDelegate = new(CreateAnalyzeDocumentAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyGetCachedDiagnosticsAsyncDelegate = new(CreateGetCachedDiagnosticsAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyStartRefinementSessionAsyncDelegate = new(CreateStartRefinementSessionAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyGetOnTheFlyDocsAsyncDelegate = new(CreateGetOnTheFlyDocsAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - _lazyIsFileExcludedAsyncDelegate = new(CreateIsFileExcludedAsyncDelegate, LazyThreadSafetyMode.PublicationOnly); - } - - private T? CreateDelegate(string methodName, Type[] types) where T : Delegate - { - try - { - if (_analyzerInstance is null || _analyzerType is null) - return null; - - if (_analyzerType.GetMethod(methodName, types) is MethodInfo methodInfo) - return (T)Delegate.CreateDelegate(typeof(T), _analyzerInstance, methodInfo); - } - catch - { - // Catch all here since failure is expected if user has no copilot chat or an older version of it installed - } - - return null; - } - - private IsAvailableAsyncDelegateType? CreateIsAvailableAsyncDelegate() - => CreateDelegate(IsAvailableAsyncMethodName, [typeof(CancellationToken)]); - - private GetAvailablePromptTitlesAsyncDelegateType? CreateGetAvailablePromptTitlesAsyncDelegate() - => CreateDelegate(GetAvailablePromptTitlesAsyncMethodName, [typeof(Document), typeof(CancellationToken)]); - - private AnalyzeDocumentAsyncDelegateType? CreateAnalyzeDocumentAsyncDelegate() - => CreateDelegate(AnalyzeDocumentAsyncMethodName, [typeof(Document), typeof(TextSpan?), typeof(string), typeof(CancellationToken)]); - - private GetCachedDiagnosticsAsyncDelegateType? CreateGetCachedDiagnosticsAsyncDelegate() - => CreateDelegate(GetCachedDiagnosticsAsyncMethodName, [typeof(Document), typeof(string), typeof(CancellationToken)]); - - private StartRefinementSessionAsyncDelegateType? CreateStartRefinementSessionAsyncDelegate() - => CreateDelegate(StartRefinementSessionAsyncMethodName, [typeof(Document), typeof(Document), typeof(Diagnostic), typeof(CancellationToken)]); - - private GetOnTheFlyDocsAsyncDelegateType? CreateGetOnTheFlyDocsAsyncDelegate() - => CreateDelegate(GetOnTheFlyDocsAsyncMethodName, [typeof(string), typeof(ImmutableArray), typeof(string), typeof(CancellationToken)]); - - private IsFileExcludedAsyncDelegateType? CreateIsFileExcludedAsyncDelegate() - => CreateDelegate(IsFileExcludedAsyncMethodName, [typeof(string), typeof(CancellationToken)]); - - public async Task IsAvailableAsync(CancellationToken cancellationToken) - { - if (_lazyIsAvailableAsyncDelegate.Value is null) - return false; - - return await _lazyIsAvailableAsyncDelegate.Value(cancellationToken).ConfigureAwait(false); - } - - public async Task> GetAvailablePromptTitlesAsync(Document document, CancellationToken cancellationToken) - { - if (_lazyGetAvailablePromptTitlesAsyncDelegate.Value is null) - return []; - - return await _lazyGetAvailablePromptTitlesAsyncDelegate.Value(document, cancellationToken).ConfigureAwait(false); - } - - public async Task> AnalyzeDocumentAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken) - { - if (_lazyAnalyzeDocumentAsyncDelegate.Value is null) - return []; - - return await _lazyAnalyzeDocumentAsyncDelegate.Value(document, span, promptTitle, cancellationToken).ConfigureAwait(false); - } - - public async Task> GetCachedDiagnosticsAsync(Document document, string promptTitle, CancellationToken cancellationToken) - { - if (_lazyGetCachedDiagnosticsAsyncDelegate.Value is null) - return []; - - return await _lazyGetCachedDiagnosticsAsyncDelegate.Value(document, promptTitle, cancellationToken).ConfigureAwait(false); - } - - public Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) - { - if (_lazyStartRefinementSessionAsyncDelegate.Value is null) - return Task.CompletedTask; - - return _lazyStartRefinementSessionAsyncDelegate.Value(oldDocument, newDocument, primaryDiagnostic, cancellationToken); - } - - public async Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) - { - if (_lazyGetOnTheFlyDocsAsyncDelegate.Value is null) - return (string.Empty, false); - - return await _lazyGetOnTheFlyDocsAsyncDelegate.Value(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); - } - - public async Task IsFileExcludedAsync(string filePath, CancellationToken cancellationToken) - { - if (_lazyIsFileExcludedAsyncDelegate.Value is null) - return false; - - return await _lazyIsFileExcludedAsyncDelegate.Value(filePath, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 1b777a72a585c..149dd06940b05 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -12,13 +12,13 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; using Microsoft.CodeAnalysis.ErrorLogger; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -43,7 +43,7 @@ public async Task TestGetFirstDiagnosticWithFixAsync() "; using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = Assert.IsType(workspace.GetService()); + var diagnosticService = workspace.GetService(); var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); @@ -78,7 +78,7 @@ public async Task TestGetFixesAsyncWithDuplicateDiagnostics() var tuple = ServiceSetup(codeFix); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); // Verify that we do not crash when computing fixes. _ = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), CancellationToken.None); @@ -105,7 +105,7 @@ public async Task TestGetFixesAsyncHasNoDuplicateConfigurationActions() var tuple = ServiceSetup(codeFix, includeConfigurationFixProviders: true); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); // Verify registered configuration code actions do not have duplicates. var fixCollections = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), CancellationToken.None); @@ -136,7 +136,7 @@ public async Task TestGetFixesAsyncForFixableAndNonFixableAnalyzersAsync() var tuple = ServiceSetup(codeFix, includeConfigurationFixProviders: true); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); // Verify only analyzerWithFix is executed when GetFixesAsync is invoked with 'CodeActionRequestPriority.Normal'. _ = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), @@ -172,7 +172,7 @@ public async Task TestGetFixesAsyncForDocumentDiagnosticAnalyzerAsync() var tuple = ServiceSetup(codeFix, includeConfigurationFixProviders: false); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); // Verify both analyzers are executed when GetFixesAsync is invoked with 'CodeActionRequestPriority.Normal'. _ = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), @@ -202,7 +202,7 @@ public async Task TestGetFixesAsyncForGeneratorDiagnosticAsync() var tuple = ServiceSetup(codeFix, includeConfigurationFixProviders: false); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); Assert.False(codeFix.Called); var fixCollectionSet = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), @@ -288,7 +288,7 @@ private static async Task> GetAddedFixesAsync( var errorReported = false; errorReportingService.OnError = message => errorReported = true; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager); var reference = new MockAnalyzerReference(codefix, [diagnosticAnalyzer]); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); document = project.Documents.Single(); @@ -315,7 +315,7 @@ private static async Task GetFirstDiagnosticWithFixWithExceptionValidationAsync( var errorReported = false; errorReportingService.OnError = message => errorReported = true; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager); + GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager); var unused = await tuple.codeFixService.GetMostSevereFixAsync( document, TextSpan.FromBounds(0, 0), new DefaultCodeActionRequestPriorityProvider(), CancellationToken.None); Assert.True(extensionManager.IsDisabled(codefix)); @@ -323,7 +323,7 @@ private static async Task GetFirstDiagnosticWithFixWithExceptionValidationAsync( Assert.True(errorReported); } - private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( CodeFixProvider codefix, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -331,7 +331,7 @@ private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyze string code = "class Program { }") => ServiceSetup([codefix], includeConfigurationFixProviders, throwExceptionInFixerCreation, additionalDocument, code); - private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( ImmutableArray codefixers, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -355,7 +355,7 @@ private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyze var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = Assert.IsType(workspace.GetService()); + var diagnosticService = workspace.GetService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => new TestErrorLogger())); var errorLogger = logger.First().Value; @@ -373,25 +373,13 @@ private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyze } private static void GetDocumentAndExtensionManager( - DiagnosticAnalyzerService diagnosticService, EditorTestWorkspace workspace, out TextDocument document, out EditorLayerExtensionManager.ExtensionManager extensionManager, MockAnalyzerReference? analyzerReference = null, - TextDocumentKind documentKind = TextDocumentKind.Document) - => GetDocumentAndExtensionManager(diagnosticService, workspace, out document, out extensionManager, out _, analyzerReference, documentKind); - - private static void GetDocumentAndExtensionManager( - DiagnosticAnalyzerService diagnosticService, - EditorTestWorkspace workspace, - out TextDocument document, - out EditorLayerExtensionManager.ExtensionManager extensionManager, - out DiagnosticIncrementalAnalyzer diagnosticIncrementalAnalyzer, - MockAnalyzerReference? analyzerReference = null, TextDocumentKind documentKind = TextDocumentKind.Document) { // register diagnostic engine to solution crawler - diagnosticIncrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace)!; var reference = analyzerReference ?? new MockAnalyzerReference(); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); @@ -442,7 +430,8 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) } } - private class MockAnalyzerReference : AnalyzerReference, ICodeFixProviderFactory + private class MockAnalyzerReference + : AnalyzerReference, ICodeFixProviderFactory, SerializerService.TestAccessor.IAnalyzerReferenceWithGuid { public readonly ImmutableArray Fixers; public readonly ImmutableArray Analyzers; @@ -503,6 +492,8 @@ public override object Id } } + public Guid Guid { get; } = Guid.NewGuid(); + public override ImmutableArray GetAnalyzers(string language) => Analyzers; @@ -766,7 +757,7 @@ private static async Task> GetNuGetAndVsixCode using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = Assert.IsType(workspace.GetService()); + var diagnosticService = workspace.GetService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( @@ -879,7 +870,7 @@ public async Task TestAdditionalDocumentCodeFixAsync() // Verify available code fixes for .txt additional document var tuple = ServiceSetup(fixers, additionalDocument: new EditorTestHostDocument("Additional Document", filePath: "test.txt")); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var txtDocument, out var extensionManager, analyzerReference, documentKind: TextDocumentKind.AdditionalDocument); + GetDocumentAndExtensionManager(workspace, out var txtDocument, out var extensionManager, analyzerReference, documentKind: TextDocumentKind.AdditionalDocument); var txtDocumentCodeFixes = await tuple.codeFixService.GetFixesAsync(txtDocument, TextSpan.FromBounds(0, 1), CancellationToken.None); Assert.Equal(2, txtDocumentCodeFixes.Length); var txtDocumentCodeFixTitles = txtDocumentCodeFixes.Select(s => s.Fixes.Single().Action.Title).ToImmutableArray(); @@ -896,7 +887,7 @@ public async Task TestAdditionalDocumentCodeFixAsync() // Verify available code fixes for .log additional document tuple = ServiceSetup(fixers, additionalDocument: new EditorTestHostDocument("Additional Document", filePath: "test.log")); using var workspace2 = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace2, out var logDocument, out extensionManager, analyzerReference, documentKind: TextDocumentKind.AdditionalDocument); + GetDocumentAndExtensionManager(workspace2, out var logDocument, out extensionManager, analyzerReference, documentKind: TextDocumentKind.AdditionalDocument); var logDocumentCodeFixes = await tuple.codeFixService.GetFixesAsync(logDocument, TextSpan.FromBounds(0, 1), CancellationToken.None); var logDocumentCodeFix = Assert.Single(logDocumentCodeFixes); var logDocumentCodeFixTitle = logDocumentCodeFix.Fixes.Single().Action.Title; @@ -999,6 +990,15 @@ public async Task TestGetFixesWithDeprioritizedAnalyzerAsync( bool editOnFixLine, bool addNewLineWithEdit) { + // Disable these cases due to: + // https://github.com/dotnet/roslyn/issues/77036 + if (actionKind is DeprioritizedAnalyzer.ActionKind.SemanticModel or DeprioritizedAnalyzer.ActionKind.SymbolStartEnd && + diagnosticOnFixLineInPriorSnapshot && + !addNewLineWithEdit) + { + return; + } + // This test validates analyzer de-prioritization logic in diagnostic service for lightbulb code path. // Basically, we have a certain set of heuristics (detailed in the next comment below), under which an analyzer // which is deemed to be an expensive analyzer is moved down from 'Normal' priority code fix bucket to @@ -1033,8 +1033,9 @@ void M() var tuple = ServiceSetup(codeFix, code: code); using var workspace = tuple.workspace; - GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, - out var extensionManager, out var diagnosticIncrementalAnalyzer, analyzerReference); + var analyzerService = tuple.analyzerService; + GetDocumentAndExtensionManager(workspace, out var document, + out var extensionManager, analyzerReference); var sourceDocument = (Document)document; var root = await sourceDocument.GetRequiredSyntaxRootAsync(CancellationToken.None); @@ -1046,8 +1047,9 @@ void M() // We enable full solution analysis so the 'AnalyzeDocumentAsync' doesn't skip analysis based on whether the document is active/open. workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); - await diagnosticIncrementalAnalyzer.ForceAnalyzeProjectAsync(sourceDocument.Project, CancellationToken.None); - await VerifyCachedDiagnosticsAsync(sourceDocument, expectedCachedDiagnostic: diagnosticOnFixLineInPriorSnapshot, testSpan, diagnosticIncrementalAnalyzer); + var diagnostics = await analyzerService.ForceAnalyzeProjectAsync(sourceDocument.Project, CancellationToken.None); + await VerifyCachedDiagnosticsAsync( + sourceDocument, expectedCachedDiagnostic: diagnosticOnFixLineInPriorSnapshot, testSpan, diagnostics); // Compute and apply code edit if (editOnFixLine) @@ -1076,10 +1078,10 @@ void M() ? root.DescendantNodes().OfType().First().Span : root.DescendantNodes().OfType().First().Span; - await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, + await analyzerService.GetDiagnosticsForIdsAsync( + sourceDocument.Project, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); - await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); + // await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); var lowPriorityAnalyzerData = new SuggestedActionPriorityProvider.LowPriorityAnalyzersAndDiagnosticIds(); var priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Default, lowPriorityAnalyzerData); @@ -1139,10 +1141,12 @@ static bool GetExpectDeprioritization( return addNewLineWithEdit; } - static async Task VerifyCachedDiagnosticsAsync(Document sourceDocument, bool expectedCachedDiagnostic, TextSpan testSpan, DiagnosticIncrementalAnalyzer diagnosticIncrementalAnalyzer) + static async Task VerifyCachedDiagnosticsAsync( + Document sourceDocument, + bool expectedCachedDiagnostic, + TextSpan testSpan, + ImmutableArray cachedDiagnostics) { - var cachedDiagnostics = await diagnosticIncrementalAnalyzer.GetCachedDiagnosticsAsync(sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); cachedDiagnostics = cachedDiagnostics.WhereAsArray(d => !d.IsSuppressed); if (!expectedCachedDiagnostic) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 000c997aa4adf..db5836abe05f2 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -63,12 +64,11 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var document = GetDocumentFromIncompleteProject(workspace); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); - var analyzer = service.CreateIncrementalAnalyzer(workspace); + var service = exportProvider.GetExportedValue(); var globalOptions = exportProvider.GetExportedValue(); - var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, + var diagnostics = await service.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution.Projects.Single(), documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } @@ -178,8 +178,7 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab Assert.True(applied); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); - var analyzer = service.CreateIncrementalAnalyzer(workspace); + var service = exportProvider.GetExportedValue(); // listen to events var syntaxDiagnostic = false; @@ -189,7 +188,7 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab // open document workspace.OpenDocument(document.Id); - var diagnostics = await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); foreach (var diagnostic in diagnostics) { @@ -220,14 +219,12 @@ private static async Task TestAnalyzerAsync( { var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); - - var analyzer = service.CreateIncrementalAnalyzer(workspace); + var service = exportProvider.GetExportedValue(); var syntax = false; var semantic = false; - var diagnostics = await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); (syntax, semantic) = resultSetter(syntax, semantic, diagnostics); @@ -266,8 +263,7 @@ public async Task TestHostAnalyzerOrderingAsync() var service = Assert.IsType(exportProvider.GetExportedValue()); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - var analyzers = await incrementalAnalyzer.GetAnalyzersTestOnlyAsync(project, CancellationToken.None).ConfigureAwait(false); + var analyzers = await service.GetTestAccessor().GetAnalyzersAsync(project, CancellationToken.None).ConfigureAwait(false); var analyzersArray = analyzers.ToArray(); AssertEx.Equal( @@ -314,10 +310,9 @@ public async Task TestHostAnalyzerErrorNotLeaking() filePath: "test.cs")])); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); + var service = exportProvider.GetExportedValue(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); Assert.NotEmpty(diagnostics); } @@ -400,10 +395,9 @@ private static AdhocWorkspace CreateWorkspaceWithProjectAndAnalyzer(DiagnosticAn private static async Task TestFullSolutionAnalysisForProjectAsync(AdhocWorkspace workspace, Project project, bool expectAnalyzerExecuted) { var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); + var service = exportProvider.GetExportedValue(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(project.Solution.Workspace); - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); if (expectAnalyzerExecuted) { @@ -442,18 +436,18 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project; } + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences); var applied = workspace.TryApplyChanges(project.Solution); Assert.True(applied); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = Assert.IsType(exportProvider.GetExportedValue()); + var service = exportProvider.GetExportedValue(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); var expectedCount = testMultiple ? 4 : 1; @@ -511,11 +505,9 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS var project = workspace.CurrentSolution.Projects.Single(); var document = project.Documents.Single(); - var service = Assert.IsType(workspace.GetService()); + var service = workspace.GetService(); var globalOptions = workspace.GetService(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - switch (analysisScope) { case BackgroundAnalysisScope.None: @@ -536,7 +528,7 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS throw ExceptionUtilities.UnexpectedValue(analysisScope); } - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); var diagnostic = diagnostics.SingleOrDefault(); if (includeAnalyzer) @@ -630,12 +622,10 @@ void M() else Assert.IsType(document); - var service = Assert.IsType(workspace.GetService()); + var service = workspace.GetService(); var text = await document.GetTextAsync(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - switch (analysisScope) { case BackgroundAnalysisScope.None: @@ -661,7 +651,7 @@ void M() break; } - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); diagnostics = [.. diagnostics .Where(d => d.Id == IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId) @@ -870,10 +860,9 @@ internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis, workspace.OpenDocument(document.Id); } - var service = Assert.IsType(workspace.GetService()); + var service = workspace.GetService(); - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs b/src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs similarity index 65% rename from src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs rename to src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs index 043c8670f2733..ddc84b2b89489 100644 --- a/src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs +++ b/src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs @@ -3,33 +3,39 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.Composition; using System.IO; using System.Linq; +using System.ServiceModel.Syndication; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.LanguageServer.Protocol; +using Microsoft.CommonLanguageServerProtocol.Framework; using Nerdbank.Streams; +using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using StreamJsonRpc; using Xunit; using Xunit.Abstractions; -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; +namespace Microsoft.CodeAnalysis.Editor.UnitTests.LanguageServer; + public class VSTypeScriptHandlerTests : AbstractLanguageServerProtocolTests { public VSTypeScriptHandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - protected override TestComposition Composition => base.Composition.AddParts(typeof(TypeScriptHandlerFactory)); + protected override TestComposition Composition => EditorTestCompositions.LanguageServerProtocolEditorFeatures + .AddParts(typeof(TypeScriptHandlerFactory)) + .AddParts(typeof(TestWorkspaceRegistrationService)); [Fact] public async Task TestExternalAccessTypeScriptHandlerInvoked() @@ -88,43 +94,50 @@ public async Task TestGetSimplifierOptionsOnTypeScriptDocument() Assert.Same(SimplifierOptions.CommonDefaults, simplifierOptions); } - private async Task CreateTsTestLspServerAsync(string workspaceXml, InitializationOptions? options = null) + private async Task CreateTsTestLspServerAsync(string workspaceXml, InitializationOptions? options = null) { - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var testWorkspace = CreateWorkspace(options, mutatingLspWorkspace: false, workspaceKind: null); testWorkspace.InitializeDocuments(XElement.Parse(workspaceXml), openDocuments: false); - // Ensure workspace operations are completed so we don't get unexpected workspace changes while running. - await WaitForWorkspaceOperationsAsync(testWorkspace); - var languageServerTarget = CreateLanguageServer(serverStream, serverStream, testWorkspace); - - return await TestLspServer.CreateAsync(testWorkspace, new ClientCapabilities(), languageServerTarget, clientStream); + return await VSTypeScriptTestLspServer.CreateAsync(testWorkspace, new InitializationOptions(), TestOutputLspLogger); } - private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace) + private class VSTypeScriptTestLspServer : AbstractTestLspServer { - var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var servicesProvider = workspace.ExportProvider.GetExportedValue(); - - var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter)) + public VSTypeScriptTestLspServer(LspTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions options, AbstractLspLogger logger) : base(testWorkspace, locations, options, logger) { - ExceptionStrategy = ExceptionProcessing.ISerializable, - }; - - var logger = NoOpLspLogger.Instance; + } - var languageServer = new RoslynLanguageServer( - servicesProvider, jsonRpc, messageFormatter.JsonSerializerOptions, - capabilitiesProvider, - logger, - workspace.Services.HostServices, - [InternalLanguageNames.TypeScript], - WellKnownLspServerKinds.RoslynTypeScriptLspServer); + protected override RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) + { + var capabilitiesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + var servicesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + + var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + var languageServer = new RoslynLanguageServer( + servicesProvider, jsonRpc, messageFormatter.JsonSerializerOptions, + capabilitiesProvider, + logger, + TestWorkspace.Services.HostServices, + [InternalLanguageNames.TypeScript], + WellKnownLspServerKinds.RoslynTypeScriptLspServer); + + jsonRpc.StartListening(); + return languageServer; + } - jsonRpc.StartListening(); - return languageServer; + public static async Task CreateAsync(LspTestWorkspace testWorkspace, InitializationOptions options, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new VSTypeScriptTestLspServer(testWorkspace, locations, options, logger); + await server.InitializeAsync(); + return server; + } } internal record TSRequest(Uri Document, string Project); diff --git a/src/EditorFeatures/Test/SymbolFinder/DependentTypeFinderTests.cs b/src/EditorFeatures/Test/SymbolFinder/DependentTypeFinderTests.cs index 7bc0ab4d97aeb..87f8dc0ae04d1 100644 --- a/src/EditorFeatures/Test/SymbolFinder/DependentTypeFinderTests.cs +++ b/src/EditorFeatures/Test/SymbolFinder/DependentTypeFinderTests.cs @@ -12,6 +12,7 @@ using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; @@ -36,7 +37,11 @@ private static Solution AddProjectWithMetadataReferences(Solution solution, stri projectName, languageName, metadataReferences: [metadataReference], - projectReferences: projectReferences.Select(p => new ProjectReference(p))); + projectReferences: projectReferences.Select(p => new ProjectReference(p)), + compilationOptions: solution.Services + .GetRequiredLanguageService(languageName) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)); return solution.AddProject(pi).AddDocument(did, $"{projectName}.{suffix}", SourceText.From(code)); } diff --git a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs index 010ed854b6fa1..697d94b619477 100644 --- a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs +++ b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Threading; using Xunit; namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index f2daa492cf9c6..347f4bb79afdb 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -54,8 +54,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( diagnosticService, @@ -126,8 +125,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( diagnosticService, diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 6f7c36018d869..bbb8f0f4a8012 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -261,9 +261,13 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Next Dim diagnosticProvider = GetDiagnosticProvider(workspace) - Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, - includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result + Dim actualDiagnostics = New List(Of DiagnosticData) + + For Each project In workspace.CurrentSolution.Projects + actualDiagnostics.AddRange(diagnosticProvider.GetDiagnosticsForIdsAsync( + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result) + Next If diagnostics Is Nothing Then Assert.Empty(actualDiagnostics) @@ -279,14 +283,14 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests End Using End Sub - Private Shared Function GetDiagnosticProvider(workspace As EditorTestWorkspace) As DiagnosticAnalyzerService + Private Shared Function GetDiagnosticProvider(workspace As EditorTestWorkspace) As IDiagnosticAnalyzerService Dim compilerAnalyzersMap = DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap().Add( NoCompilationConstants.LanguageName, ImmutableArray.Create(Of DiagnosticAnalyzer)(New NoCompilationDocumentDiagnosticAnalyzer())) Dim analyzerReference = New TestAnalyzerReferenceByLanguage(compilerAnalyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim analyzerService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim analyzerService = workspace.GetService(Of IDiagnosticAnalyzerService)() Return analyzerService End Function diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index aa4ecdbe70240..9913c17cfea6b 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -13,6 +13,7 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Diagnostics.CSharp Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Serialization Imports Microsoft.CodeAnalysis.SolutionCrawler Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics @@ -85,7 +86,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim hostAnalyzers = solution.SolutionState.Analyzers Dim project = solution.Projects(0) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -95,7 +96,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(workspaceDiagnosticAnalyzer.DiagDescriptor.Id, descriptors(0).Id) Dim document = project.Documents.Single() - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Equal(1, diagnostics.Length) @@ -105,6 +105,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference1 = New AnalyzerImageReference(projectAnalyzers1, display:=NameOf(projectAnalyzers1)) Dim projectAnalyzerReferences1 = ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference1) project = project.WithAnalyzerReferences(projectAnalyzerReferences1) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Verify available diagnostic descriptors/analyzers descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -120,6 +121,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzers2 = ImmutableArray.Create(Of DiagnosticAnalyzer)(projectDiagnosticAnalyzer2) Dim projectAnalyzerReference2 = New AnalyzerImageReference(projectAnalyzers2, display:=NameOf(projectAnalyzers2)) project = project.AddAnalyzerReference(projectAnalyzerReference2) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Verify available diagnostic descriptors/analyzers descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -190,7 +192,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Add project analyzer reference with no analyzers. Dim projectAnalyzersEmpty = ImmutableArray(Of DiagnosticAnalyzer).Empty @@ -234,7 +236,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference = New AnalyzerImageReference( ImmutableArray.Create(Of DiagnosticAnalyzer)(New TestDiagnosticAnalyzer1(1)), display:=referenceName) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() project = project.WithAnalyzerReferences(ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)) @@ -270,7 +272,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -282,7 +284,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim document = project.Documents.Single() Dim span = (Await document.GetSyntaxRootAsync()).FullSpan - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span) Assert.Equal(1, diagnostics.Length) Assert.Equal(workspaceDiagnosticAnalyzer.DiagDescriptor.Id, diagnostics(0).Id) @@ -334,14 +335,15 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim p1 = solution.Projects.Single(Function(p) p.Language = LanguageNames.CSharp) p1 = p1.WithAnalyzerReferences(SpecializedCollections.SingletonCollection(New AnalyzerImageReference(ImmutableArray.Create(analyzer1)))) solution = p1.Solution + SerializerService.TestAccessor.AddAnalyzerImageReferences(p1.AnalyzerReferences) Dim p2 = solution.Projects.Single(Function(p) p.Language = LanguageNames.VisualBasic) p2 = p2.WithAnalyzerReferences(SpecializedCollections.SingletonCollection(New AnalyzerImageReference(ImmutableArray.Create(analyzer2)))) solution = p2.Solution + SerializerService.TestAccessor.AddAnalyzerImageReferences(p2.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim hostAnalyzers = solution.SolutionState.Analyzers Dim workspaceDescriptors = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache) @@ -382,7 +384,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(analyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim diagnosticService2 = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService2 = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptors = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService2.AnalyzerInfoCache) Assert.Equal(1, descriptors.Count) @@ -430,8 +432,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference2 = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location) project = project.AddAnalyzerReference(analyzerReference2) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) ' Verify no duplicate diagnostics. @@ -482,9 +483,10 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New ThrowsExceptionAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -493,7 +495,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Empty(diagnostics) @@ -517,22 +518,22 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockStartedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim root = Await document.GetSyntaxRootAsync().ConfigureAwait(False) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, root.FullSpan) Assert.Empty(diagnostics) diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") @@ -565,9 +566,8 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim span = (Await document.GetSyntaxRootAsync().ConfigureAwait(False)).FullSpan Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span).ConfigureAwait(False) Assert.Equal(1, diagnostics.Length) @@ -588,8 +588,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() For Each actionKind As OperationAnalyzer.ActionKind In [Enum].GetValues(GetType(OperationAnalyzer.ActionKind)) Dim solution = workspace.CurrentSolution @@ -597,13 +596,14 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New OperationAnalyzer(actionKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Length) Dim diagnostic = diagnostics.First() @@ -630,16 +630,16 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockEndedAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Equal(1, diagnostics.Length) @@ -664,9 +664,10 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockStartedAndEndedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -674,7 +675,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Equal(1, diagnostics.Length) Dim diagnostic = diagnostics.First() @@ -740,9 +740,10 @@ class AnonymousFunctions Dim analyzer = New CodeBlockStartedAndEndedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -750,7 +751,6 @@ class AnonymousFunctions Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Equal(4, diagnostics.Length) @@ -776,10 +776,10 @@ class AnonymousFunctions Dim analyzer = New CompilationEndedAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -801,7 +801,7 @@ class AnonymousFunctions ' Test "GetDiagnosticsForIdsAsync" does force computation of compilation end diagnostics. ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. Dim projectDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, projectDiagnostics.Length) @@ -837,6 +837,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim projectDiagnostics = Await DiagnosticProviderTestUtilities.GetProjectDiagnosticsAsync(workspace, project) Assert.Equal(1, projectDiagnostics.Count()) @@ -862,6 +863,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Make couple of dummy invocations to GetDocumentDiagnostics. Dim document = project.Documents.Single() @@ -901,6 +903,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim projectDiagnostics = Await DiagnosticProviderTestUtilities.GetProjectDiagnosticsAsync(workspace, project) @@ -936,17 +939,18 @@ class AnonymousFunctions Dim analyzer = New NamedTypeAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) + Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) @@ -975,9 +979,10 @@ class AnonymousFunctions Dim analyzer = New PartialTypeDiagnosticAnalyzer(indexOfDeclToReportDiagnostic:=1) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -985,7 +990,6 @@ class AnonymousFunctions Dim document = project.Documents.Single(Function(d) d.Name = "Test1.cs") Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan) Assert.Equal(1, diagnostics.Length) Assert.Equal(PartialTypeDiagnosticAnalyzer.DiagDescriptor.Id, diagnostics.Single().Id) @@ -1031,17 +1035,17 @@ class AnonymousFunctions ' Test partial type diagnostic reported on all source files. Dim analyzerReference = New AnalyzerImageReference(analyzers) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) ' Verify project diagnostics contains diagnostics reported on both partial definitions. - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Length) Dim file1HasDiag = False, file2HasDiag = False @@ -1083,9 +1087,10 @@ public class B Dim analyzer As DiagnosticAnalyzer = New CodeBlockOrSyntaxNodeAnalyzer(isCodeBlockAnalyzer:=True) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1093,7 +1098,6 @@ public class B Dim document = project.Documents.Single() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan) Assert.Equal(6, diagnostics.Length) Assert.Equal(3, diagnostics.Where(Function(d) d.Id = CodeBlockOrSyntaxNodeAnalyzer.Descriptor1.Id).Count) @@ -1126,9 +1130,10 @@ public class B Dim analyzer As DiagnosticAnalyzer = New CodeBlockOrSyntaxNodeAnalyzer(isCodeBlockAnalyzer:=False) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1136,7 +1141,6 @@ public class B Dim document = project.Documents.Single() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan) Assert.Equal(3, diagnostics.Length) @@ -1169,9 +1173,10 @@ public class B Dim analyzer As DiagnosticAnalyzer = New MethodSymbolAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1180,8 +1185,6 @@ public class B Dim text = Await document.GetTextAsync() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = (Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan)). OrderBy(Function(d) d.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text).Start).ToArray() @@ -1221,9 +1224,10 @@ End Class Dim analyzer = New MustOverrideMethodAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1231,7 +1235,6 @@ End Class Dim document = project.Documents.Single() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan) Assert.Equal(1, diagnostics.Length) Assert.Equal(1, diagnostics.Where(Function(d) d.Id = MustOverrideMethodAnalyzer.Descriptor1.Id).Count) @@ -1284,9 +1287,10 @@ public class B Dim analyzer = New FieldDeclarationAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1294,7 +1298,6 @@ public class B Dim document = project.Documents.Single() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim text = Await document.GetTextAsync() Dim diagnostics = (Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan)). OrderBy(Function(d) d.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text).Start). @@ -1331,9 +1334,10 @@ public class B Dim analyzer = New FieldDeclarationAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1341,7 +1345,6 @@ public class B Dim document = project.Documents.Single() Dim fullSpan = (Await document.GetSyntaxRootAsync()).FullSpan - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim text = Await document.GetTextAsync() Dim diagnostics = (Await GetDiagnosticsForSpanAsync(diagnosticService, document, fullSpan)). OrderBy(Function(d) d.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text).Start). @@ -1397,6 +1400,7 @@ public class B Dim analyzer = New CompilationAnalyzerWithAnalyzerOptions() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Add additional document Dim additionalDocText = "First" @@ -1404,8 +1408,7 @@ public class B project = additionalDoc.Project Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Await TestCompilationAnalyzerWithAnalyzerOptionsCoreAsync(project, additionalDocText, diagnosticService) @@ -1419,7 +1422,10 @@ public class B End Using End Function - Private Shared Async Function TestCompilationAnalyzerWithAnalyzerOptionsCoreAsync(project As Project, expectedDiagnosticMessage As String, diagnosticService As DiagnosticAnalyzerService) As Task + Private Shared Async Function TestCompilationAnalyzerWithAnalyzerOptionsCoreAsync( + project As Project, + expectedDiagnosticMessage As String, + diagnosticService As IDiagnosticAnalyzerService) As Task Dim descriptorsMap = project.Solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1952,16 +1958,16 @@ End Class Dim analyzer = New CodeBlockActionAnalyzer(onlyStatelessAction) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Dim expectedCount = If(onlyStatelessAction, 1, 2) @@ -2012,16 +2018,16 @@ namespace ConsoleApplication1 ' Add analyzer Dim analyzerReference = New AnalyzerImageReference(analyzers.ToImmutableArray()) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await GetDiagnosticsForDocumentAsync(diagnosticService, document) Assert.Equal(1, diagnostics.Length) @@ -2079,10 +2085,10 @@ class MyClass Dim analyzer = New AnalyzerWithNoSupportedDiagnostics() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2118,10 +2124,10 @@ class MyClass Dim analyzer = New CompilationAnalyzerWithSeverity(DiagnosticSeverity.Hidden, configurable:=False) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2131,11 +2137,8 @@ class MyClass Assert.Equal(analyzer.Descriptor.Id, descriptors.Single().Id) ' Get cached project diagnostics. - Dim diagnostics = Await diagnosticService.GetCachedDiagnosticsAsync( - workspace, project.Id, documentId:=Nothing, - includeLocalDocumentDiagnostics:=True, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.ForceAnalyzeProjectAsync( + project, CancellationToken.None) ' in v2, solution crawler never creates non-local hidden diagnostics. ' v2 still creates those for LB and explicit queries such as FixAll. @@ -2144,7 +2147,7 @@ class MyClass ' Get diagnostics explicitly Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Length) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) @@ -2175,6 +2178,7 @@ class C Dim analyzer = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Get span to analyze Dim document = project.Documents.Single() @@ -2183,8 +2187,7 @@ class C Dim span = localDecl.Span Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Verify diagnostics for span Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span) @@ -2222,16 +2225,16 @@ class C Dim analyzer = New EnsureNoMergedNamespaceSymbolAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + project, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Empty(diagnostics) End Using @@ -2267,10 +2270,10 @@ class MyClass Dim compilerAnalyzer = New CSharpCompilerDiagnosticAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(compilerAnalyzer, syntaxAnalyzer, semanticAnalyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for the given DiagnosticKind Dim document = project.Documents.Single() @@ -2337,10 +2340,10 @@ class MyClass Dim compilerAnalyzer = New CSharpCompilerDiagnosticAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(compilerAnalyzer, syntaxAnalyzer, semanticAnalyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for fine grained DiagnosticKind in random order Dim document = project.Documents.Single() @@ -2427,9 +2430,10 @@ public class C Dim analyzer = New AllActionsAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2441,7 +2445,6 @@ public class C Assert.Equal("M1", firstMethodDecl.Identifier.ValueText) Dim span = firstMethodDecl.Span - Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim text = Await document.GetTextAsync() Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span) Assert.Empty(diagnostics) diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb index ab5edbe9480e7..f1b0f2327a4a7 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.Composition +Imports System.Threading Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Host @@ -134,7 +135,7 @@ $$ Return Task.CompletedTask End Function - Public Overrides Function GetDescriptionAsync(document As Document, item As CompletionItem, cancellationToken As Threading.CancellationToken) As Task(Of CompletionDescription) + Public Overrides Function GetDescriptionAsync(document As Document, item As CompletionItem, cancellationToken As CancellationToken) As Task(Of CompletionDescription) Return Task.FromResult(CompletionDescription.FromText(DescriptionText)) End Function End Class diff --git a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb index 2409e8479831e..66d9c617a1253 100644 --- a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb @@ -113,7 +113,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Private Shared ReadOnly s_controllerMocksMap As New ConditionalWeakTable(Of Controller, ControllerMocks) Private Shared Function GetMocks(controller As Controller) As ControllerMocks Dim result As ControllerMocks = Nothing - Roslyn.Utilities.Contract.ThrowIfFalse(s_controllerMocksMap.TryGetValue(controller, result)) + Contract.ThrowIfFalse(s_controllerMocksMap.TryGetValue(controller, result)) Return result End Function diff --git a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs index 3ac452b07a1a8..6b00f7ebc79e0 100644 --- a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs +++ b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs @@ -225,7 +225,7 @@ public CaretPosition GetCaretPoint() => TextView.Caret.Position; /// - /// Used in synchronous methods to ensure all outstanding work has been + /// Used in synchronous methods to ensure all outstanding work has been /// completed. /// public void AssertNoAsynchronousOperationsRunning() diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs deleted file mode 100644 index 7c4baeeb75e67..0000000000000 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [Export(typeof(IDiagnosticAnalyzerService)), Shared, PartNotDiscoverable] - internal class MockDiagnosticAnalyzerService : IDiagnosticAnalyzerService - { - private readonly ArrayBuilder<(DiagnosticData Diagnostic, DiagnosticKind KindFilter)> _diagnosticsWithKindFilter; - public bool RequestedRefresh; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public MockDiagnosticAnalyzerService(IGlobalOptionService globalOptions) - { - GlobalOptions = globalOptions; - _diagnosticsWithKindFilter = ArrayBuilder<(DiagnosticData Diagnostic, DiagnosticKind KindFilter)>.GetInstance(); - } - - public void AddDiagnostic(DiagnosticData diagnostic, DiagnosticKind diagnosticKind) - => _diagnosticsWithKindFilter.Add((diagnostic, diagnosticKind)); - - public void AddDiagnostics(ImmutableArray diagnostics, DiagnosticKind diagnosticKind) - { - foreach (var diagnostic in diagnostics) - AddDiagnostic(diagnostic, diagnosticKind); - } - - public void RequestDiagnosticRefresh() - => RequestedRefresh = true; - - public DiagnosticAnalyzerInfoCache AnalyzerInfoCache - => throw new NotImplementedException(); - - public IGlobalOptionService GlobalOptions { get; } - - public bool ContainsDiagnostics(Workspace workspace, ProjectId projectId) - => throw new NotImplementedException(); - - public Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - => throw new NotImplementedException(); - - public Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => throw new NotImplementedException(); - - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => throw new NotImplementedException(); - - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => throw new NotImplementedException(); - - public Task> GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) - => Task.FromResult(_diagnosticsWithKindFilter.Where(d => diagnosticKind == d.KindFilter).Select(d => d.Diagnostic).ToImmutableArray()); - - public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => throw new NotImplementedException(); - } -} diff --git a/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj b/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj index 540e000766193..0b19db7854e6a 100644 --- a/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj +++ b/src/EditorFeatures/TestUtilities/Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj @@ -10,10 +10,6 @@ false true - - - - diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.Callback.cs b/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.Callback.cs index af0b25b4bef97..33a09ed7c6423 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.Callback.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.Callback.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; using Roslyn.Utilities; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.cs b/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.cs index 3610e24b04132..b7be4d6a4249c 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/NavigateToTestAggregator.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; using Moq; using Roslyn.Utilities; diff --git a/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs index 291cc01aced8b..bb234f12cb3f0 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs @@ -31,12 +31,11 @@ namespace Microsoft.CodeAnalysis.Test.Utilities; -public partial class EditorTestWorkspace : TestWorkspace, ILspWorkspace +public partial class EditorTestWorkspace : TestWorkspace { private const string ReferencesOnDiskAttributeName = "ReferencesOnDisk"; private readonly Dictionary _createdTextBuffers = []; - private readonly bool _supportsLspMutation; internal EditorTestWorkspace( TestComposition? composition = null, @@ -44,8 +43,7 @@ internal EditorTestWorkspace( Guid solutionTelemetryId = default, bool disablePartialSolutions = true, bool ignoreUnchangeableDocumentsWhenApplyingChanges = true, - WorkspaceConfigurationOptions? configurationOptions = null, - bool supportsLspMutation = false) + WorkspaceConfigurationOptions? configurationOptions = null) : base(composition ?? EditorTestCompositions.EditorFeatures, workspaceKind, solutionTelemetryId, @@ -53,22 +51,6 @@ internal EditorTestWorkspace( ignoreUnchangeableDocumentsWhenApplyingChanges, configurationOptions) { - _supportsLspMutation = supportsLspMutation; - } - - bool ILspWorkspace.SupportsMutation => _supportsLspMutation; - - ValueTask ILspWorkspace.UpdateTextIfPresentAsync(DocumentId documentId, SourceText sourceText, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(_supportsLspMutation); - OnDocumentTextChanged(documentId, sourceText, PreservationMode.PreserveIdentity, requireDocumentPresent: false); - return ValueTaskFactory.CompletedTask; - } - - internal override ValueTask TryOnDocumentClosedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(_supportsLspMutation); - return base.TryOnDocumentClosedAsync(documentId, cancellationToken); } private protected override EditorTestHostDocument CreateDocument( diff --git a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb index fee5d7f6359b2..c23580a6824f3 100644 --- a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb +++ b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb @@ -30,7 +30,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeCleanup New DiagnosticSet(FeaturesResources.Apply_parentheses_preferences, IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId, IDEDiagnosticIds.AddRequiredParenthesesDiagnosticId), New DiagnosticSet(AnalyzersResources.Add_accessibility_modifiers, - IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId), + IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId), New DiagnosticSet(FeaturesResources.Apply_coalesce_expression_preferences, IDEDiagnosticIds.UseCoalesceExpressionForTernaryConditionalCheckDiagnosticId), New DiagnosticSet(FeaturesResources.Apply_object_collection_initialization_preferences, diff --git a/src/EditorFeatures/VisualBasic/EndConstructGeneration/ReplaceSpanResult.vb b/src/EditorFeatures/VisualBasic/EndConstructGeneration/ReplaceSpanResult.vb index 909b925f7be53..da7392e50945a 100644 --- a/src/EditorFeatures/VisualBasic/EndConstructGeneration/ReplaceSpanResult.vb +++ b/src/EditorFeatures/VisualBasic/EndConstructGeneration/ReplaceSpanResult.vb @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.EndConstructGeneration Private ReadOnly _newCaretPosition As Integer? Public Sub New(snapshotSpan As SnapshotSpan, replacementText As String, newCaretPosition As Integer?) - ThrowIfNull(replacementText) + Contract.ThrowIfNull(replacementText) _snapshotSpan = snapshotSpan _replacementText = replacementText diff --git a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj index 6f4fcb44d0880..e823fb16776ab 100644 --- a/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.vbproj @@ -29,7 +29,7 @@ - + diff --git a/src/EditorFeatures/VisualBasicTest/ChangeSignature/ReorderParameters.InvocationLocations.vb b/src/EditorFeatures/VisualBasicTest/ChangeSignature/ReorderParameters.InvocationLocations.vb index 0b6e46604c2db..aab4aded50829 100644 --- a/src/EditorFeatures/VisualBasicTest/ChangeSignature/ReorderParameters.InvocationLocations.vb +++ b/src/EditorFeatures/VisualBasicTest/ChangeSignature/ReorderParameters.InvocationLocations.vb @@ -585,7 +585,7 @@ End Class]]>.NormalizedValue() #Region "Code Refactoring" - Public Async Function ReorderIndexerParameters_CodeRefactoring_InMethodDeclaration() As Threading.Tasks.Task + Public Async Function ReorderIndexerParameters_CodeRefactoring_InMethodDeclaration() As Task Dim markup = .NormalizedValue() End Function - Public Async Function ReorderIndexerParameters_CodeRefactoring_NotInMethodBody() As Threading.Tasks.Task + Public Async Function ReorderIndexerParameters_CodeRefactoring_NotInMethodBody() As Task Dim markup = .NormalizedValue() End Function - Public Async Function ReorderIndexerParameters_CodeRefactoring_InCallSite_ViaCodeAction() As Threading.Tasks.Task + Public Async Function ReorderIndexerParameters_CodeRefactoring_InCallSite_ViaCodeAction() As Task Dim markup = Return MyBase.GetHashCode()$$ End Function - Sub bar() + Sub bar() End Sub End Class @@ -1246,7 +1246,7 @@ Public Class Class2 Public Overrides Sub M( i As Integer) MyBase.M(i)$$ End Sub - ' Comment on body + ' Comment on body End Class]]> Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "M(i As Integer)", expectedCode.Value.Replace(vbLf, vbCrLf)) diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest.vb index b87ec47eb3680..b3af39f3ff3d3 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest.vb @@ -28,7 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics End Function Friend Overloads Async Function TestAsync( - initialMarkup As XElement, expected As XElement, Optional index As Integer = 0) As Threading.Tasks.Task + initialMarkup As XElement, expected As XElement, Optional index As Integer = 0) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Dim expectedStr = expected.ConvertTestSourceTag() @@ -37,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics index:=index) End Function - Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Threading.Tasks.Task + Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Await MyBase.TestMissingAsync(initialMarkupStr, New TestParameters(parseOptions:=Nothing, compilationOptions:=_compilationOptions)) diff --git a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.ControlFlowAnalysis.vb b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.ControlFlowAnalysis.vb index dcc551f6e8f9e..240afe523411c 100644 --- a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.ControlFlowAnalysis.vb +++ b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.ControlFlowAnalysis.vb @@ -15,7 +15,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ExtractMethod Public Class FlowAnalysis - Public Async Function TestExitSub() As Threading.Tasks.Task + Public Async Function TestExitSub() As Task Dim code = Class Test Sub Test() [|Exit Sub|] @@ -26,7 +26,7 @@ End Class End Function - Public Async Function TestExitFunction() As Threading.Tasks.Task + Public Async Function TestExitFunction() As Task Dim code = Class Test Function Test1() As Integer Console.Write(42) @@ -271,7 +271,7 @@ End Class End Function - Public Async Function BugFix6313_2() As Threading.Tasks.Task + Public Async Function BugFix6313_2() As Task Dim code = Imports System Class A diff --git a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.TriviaProcessor.vb b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.TriviaProcessor.vb index dd7e134ac7838..14a593768af58 100644 --- a/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.TriviaProcessor.vb +++ b/src/EditorFeatures/VisualBasicTest/ExtractMethod/ExtractMethodTests.TriviaProcessor.vb @@ -9,7 +9,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ExtractMethod Public Class TriviaProcessor - Public Async Function TestCommentBeforeCode() As Threading.Tasks.Task + Public Async Function TestCommentBeforeCode() As Task Dim code = Class C Sub M() [|'comment @@ -32,7 +32,7 @@ End Class End Function - Public Async Function LineContinuation() As Threading.Tasks.Task + Public Async Function LineContinuation() As Task Dim code = Module Program Sub Main Dim x = [|1. _ @@ -55,7 +55,7 @@ End Module End Function - Public Async Function LineContinuation2() As Threading.Tasks.Task + Public Async Function LineContinuation2() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -98,7 +98,7 @@ End Module End Function - Public Async Function ImplicitLineContinuation() As Threading.Tasks.Task + Public Async Function ImplicitLineContinuation() As Task Dim code = Imports System.Linq Module A Sub Main() @@ -123,7 +123,7 @@ End Module End Function - Public Async Function ImplicitLineContinuation2() As Threading.Tasks.Task + Public Async Function ImplicitLineContinuation2() As Task Dim code = Imports System.Linq Module A Sub Main() diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb index e2eff36a2a1c4..123c74f17b5b5 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb @@ -215,7 +215,7 @@ End Class End Function - Public Function VisualBasicAddAccessibilityModifiers() As Task + Public Function VisualBasicAddOrRemoveAccessibilityModifiers() As Task Dim code As String = "Class Program Public Shared Sub Method() Console.WriteLine(""Hello"") diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb b/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb index 0aef18c39f966..a8a0221d97a60 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting End Sub - Public Async Function SimpleOneLineNugget() As Threading.Tasks.Task + Public Async Function SimpleOneLineNugget() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -44,7 +44,7 @@ End Module - Public Async Function SimpleScriptBlock() As Threading.Tasks.Task + Public Async Function SimpleScriptBlock() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -75,7 +75,7 @@ End Module End Function - Public Async Function SimpleMultiLineNugget() As Threading.Tasks.Task + Public Async Function SimpleMultiLineNugget() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -113,7 +113,7 @@ End Module - Public Async Function SimpleQueryWithinNugget() As Threading.Tasks.Task + Public Async Function SimpleQueryWithinNugget() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -151,7 +151,7 @@ End Module End Function - Public Async Function SingleLineFunctionLambdaInNugget() As Threading.Tasks.Task + Public Async Function SingleLineFunctionLambdaInNugget() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -186,7 +186,7 @@ End Module End Function - Public Async Function MultiLineFunctionLambdaInNugget() As Threading.Tasks.Task + Public Async Function MultiLineFunctionLambdaInNugget() As Task Dim code = Imports System Imports System.Collections.Generic Imports System.Linq @@ -246,7 +246,7 @@ End Module ''' The rule has to be set up for each set of spans, currently we test just one Private Shared Async Function AssertFormatWithBaseIndentAfterReplacingLfToCrLfAsync(content As String, expected As String, - baseIndentation As Integer) As Threading.Tasks.Task + baseIndentation As Integer) As Task ' do this since xml value put only vbLf content = content.Replace(vbLf, vbCrLf) diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb index 6aa0312032bef..e57a62172f560 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb @@ -174,7 +174,7 @@ End Class Assert.NotNull(Await Record.ExceptionAsync(Function() TestAsync(codeWithMarkup, indentation, indentStyle:=indentStyle))) End Function - Private Shared Async Function TestAsync(codeWithMarkup As String, indentation As Integer, Optional indentStyle As FormattingOptions2.IndentStyle = FormattingOptions2.IndentStyle.Smart) As Threading.Tasks.Task + Private Shared Async Function TestAsync(codeWithMarkup As String, indentation As Integer, Optional indentStyle As FormattingOptions2.IndentStyle = FormattingOptions2.IndentStyle.Smart) As Task Dim code As String = Nothing Dim position As Integer = 0 MarkupTestFile.GetPosition(codeWithMarkup, code, position) diff --git a/src/EditorFeatures/VisualBasicTest/SignatureHelp/AbstractVisualBasicSignatureHelpProviderTests.vb b/src/EditorFeatures/VisualBasicTest/SignatureHelp/AbstractVisualBasicSignatureHelpProviderTests.vb index 81f35e47e6fed..28bd205b74a1e 100644 --- a/src/EditorFeatures/VisualBasicTest/SignatureHelp/AbstractVisualBasicSignatureHelpProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/SignatureHelp/AbstractVisualBasicSignatureHelpProviderTests.vb @@ -11,7 +11,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SignatureHelp ' We want to skip script testing in all VB stuff for now. - Protected Overrides Function TestAsync(markupWithPositionAndOptSpan As String, Optional expectedOrderedItemsOrNull As IEnumerable(Of SignatureHelpTestItem) = Nothing, Optional usePreviousCharAsTrigger As Boolean = False, Optional sourceCodeKind As Microsoft.CodeAnalysis.SourceCodeKind? = Nothing, Optional experimental As Boolean = False) As Threading.Tasks.Task + Protected Overrides Function TestAsync(markupWithPositionAndOptSpan As String, Optional expectedOrderedItemsOrNull As IEnumerable(Of SignatureHelpTestItem) = Nothing, Optional usePreviousCharAsTrigger As Boolean = False, Optional sourceCodeKind As Microsoft.CodeAnalysis.SourceCodeKind? = Nothing, Optional experimental As Boolean = False) As Task If (sourceCodeKind.HasValue) Then Return MyBase.TestAsync(markupWithPositionAndOptSpan, expectedOrderedItemsOrNull, usePreviousCharAsTrigger, sourceCodeKind, experimental) Else @@ -19,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SignatureHelp End If End Function - Protected Overrides Function VerifyCurrentParameterNameAsync(markupWithPosition As String, expectedParameterName As String, Optional sourceCodeKind As Microsoft.CodeAnalysis.SourceCodeKind? = Nothing) As Threading.Tasks.Task + Protected Overrides Function VerifyCurrentParameterNameAsync(markupWithPosition As String, expectedParameterName As String, Optional sourceCodeKind As Microsoft.CodeAnalysis.SourceCodeKind? = Nothing) As Task If (sourceCodeKind.HasValue) Then Return MyBase.VerifyCurrentParameterNameAsync(markupWithPosition, expectedParameterName, sourceCodeKind) Else diff --git a/src/EditorFeatures/VisualBasicTest/Structure/CommentStructureTests.vb b/src/EditorFeatures/VisualBasicTest/Structure/CommentStructureTests.vb index 65dcd079c2464..07000d1247ea7 100644 --- a/src/EditorFeatures/VisualBasicTest/Structure/CommentStructureTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Structure/CommentStructureTests.vb @@ -29,7 +29,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Outlining ElseIf token.TrailingTrivia.Contains(trivia) Then Return CreateCommentsRegions(token.TrailingTrivia) Else - Throw Roslyn.Utilities.ExceptionUtilities.Unreachable + Throw ExceptionUtilities.Unreachable End If End Function diff --git a/src/ExpressionEvaluator/Core/Source/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.csproj b/src/ExpressionEvaluator/Core/Source/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.csproj index 3271d4fbd6d4d..5744d70120de4 100644 --- a/src/ExpressionEvaluator/Core/Source/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.csproj +++ b/src/ExpressionEvaluator/Core/Source/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.csproj @@ -10,9 +10,6 @@ true - - Compiler\ExceptionUtilities.cs - Compiler\MetadataTypeCodeExtensions.cs @@ -25,9 +22,6 @@ Compiler\CommonGeneratedNames.cs - - Compiler\NullableAttributes.cs - Compiler\RoslynString.cs @@ -64,5 +58,6 @@ + diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.ResultProvider.csproj b/src/ExpressionEvaluator/Core/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.ResultProvider.csproj index 4c83167c2a17a..c28f388bd2ddc 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.ResultProvider.csproj +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.ResultProvider.csproj @@ -28,21 +28,9 @@ Compiler\InternalUtilities\EnumField.cs - - Compiler\InternalUtilities\ExceptionUtilities.cs - - - Compiler\InternalUtilities\NullableAttributes.cs - Compiler\InternalUtilities\ReflectionUtilities.cs - - Compilers\InternalUtilities\InterpolatedStringHandlerAttribute.cs - - - Compilers\InternalUtilities\InterpolatedStringHandlerArgumentAttribute.cs - Compiler\InternalUtilities\ObjectPool`1.cs @@ -90,4 +78,5 @@ + \ No newline at end of file diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs index bd857b6fc3ffb..9adcc73fa102b 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs @@ -132,13 +132,13 @@ internal NamespaceTypeDefinitionNoBase(INamespaceTypeDefinition underlyingType) public sealed override bool Equals(object obj) { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } public sealed override int GetHashCode() { // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. - throw Roslyn.Utilities.ExceptionUtilities.Unreachable(); + throw ExceptionUtilities.Unreachable(); } } } diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj b/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj index 7def48e1d75a6..a62c0965c6d4a 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj @@ -40,21 +40,9 @@ Compiler\InternalUtilities\EnumField.cs - - Compiler\InternalUtilities\ExceptionUtilities.cs - - - Compiler\InternalUtilities\NullableAttributes.cs - Compiler\InternalUtilities\ReflectionUtilities.cs - - Compilers\InternalUtilities\InterpolatedStringHandlerAttribute.cs - - - Compilers\InternalUtilities\InterpolatedStringHandlerArgumentAttribute.cs - Compiler\InternalUtilities\ObjectPool`1.cs @@ -94,4 +82,5 @@ + diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.FixAllProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.FixAllProvider.cs index 238b2127dab7c..284bc6945b647 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.FixAllProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.FixAllProvider.cs @@ -61,14 +61,11 @@ private sealed class FixAllCodeAction(Func, CancellationToken, Task> _createChangedSolution = createChangedSolution; - protected override async Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) + protected override async Task> ComputeOperationsAsync(IProgress progress, CancellationToken cancellationToken) { var changedSolution = await _createChangedSolution( - CodeActionPurpose.Preview, CodeAnalysisProgress.None, cancellationToken).ConfigureAwait(false); - if (changedSolution is null) - return []; - - return new CodeActionOperation[] { new ApplyChangesOperation(changedSolution) }; + CodeActionPurpose.Preview, progress, cancellationToken).ConfigureAwait(false); + return changedSolution is null ? [] : [new ApplyChangesOperation(changedSolution)]; } } } diff --git a/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs b/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs index d59700a5386cd..429d77ba862fe 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/MoveType/CSharpMoveTypeService.cs @@ -20,6 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.MoveType; internal sealed class CSharpMoveTypeService() : AbstractMoveTypeService { + protected override (string name, int arity) GetSymbolNameAndArity(BaseTypeDeclarationSyntax syntax) + => (syntax.Identifier.ValueText, syntax is TypeDeclarationSyntax { TypeParameterList.Parameters.Count: var arity } ? arity : 0); + protected override bool IsMemberDeclaration(SyntaxNode syntaxNode) => syntaxNode is MemberDeclarationSyntax; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs index 6df55c5e6ee55..a7b7a5ce93764 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs @@ -51,6 +51,7 @@ internal sealed class SnippetCompletionProvider : LSPCompletionProvider CSharpSnippetIdentifiers.StaticIntMain, CSharpSnippetIdentifiers.Struct, CSharpSnippetIdentifiers.StaticVoidMain, + CSharpSnippetIdentifiers.Using, CSharpSnippetIdentifiers.While ]; diff --git a/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs index c238e4b2dd533..5cb11e73a8cf6 100644 --- a/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -129,9 +129,7 @@ protected override SyntaxNode ConvertPropertyToExpressionBodyIfDesired( var preference = info.Options.PreferExpressionBodiedProperties.Value; if (preference == ExpressionBodyPreference.Never) - { - return propertyDeclaration.WithSemicolonToken(default); - } + return propertyDeclaration; // if there is a get accessors only, we can move the expression body to the property if (propertyDeclaration.AccessorList?.Accessors.Count == 1 && @@ -146,7 +144,7 @@ protected override SyntaxNode ConvertPropertyToExpressionBodyIfDesired( } } - return propertyDeclaration.WithSemicolonToken(default); + return propertyDeclaration; } protected override SyntaxNode GetTypeBlock(SyntaxNode syntaxNode) diff --git a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs index 60a3d2ce75ce8..f28948a0c3fbc 100644 --- a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs @@ -11,7 +11,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertToInterpolatedString; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPlaceholderToInterpolatedString), Shared] -internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider : +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider() : AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider< ExpressionSyntax, LiteralExpressionSyntax, @@ -21,12 +23,6 @@ internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefact ArgumentListSyntax, InterpolationSyntax> { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider() - { - } - protected override ExpressionSyntax ParseExpression(string text) => SyntaxFactory.ParseExpression(text); } diff --git a/src/Features/CSharp/Portable/Diagnostics/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs b/src/Features/CSharp/Portable/Diagnostics/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs index 6bd117bf73410..c19cb644ff215 100644 --- a/src/Features/CSharp/Portable/Diagnostics/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs +++ b/src/Features/CSharp/Portable/Diagnostics/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs @@ -60,7 +60,8 @@ namespace Microsoft.CodeAnalysis.CSharp.LanguageServer; "CS9207", // ErrorCode.ERR_InterceptableMethodMustBeOrdinary "CS8419", // ErrorCode.ERR_PossibleAsyncIteratorWithoutYield "CS8420", // ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait - "CS9217" // ErrorCode.ERR_RefLocalAcrossAwait + "CS9217", // ErrorCode.ERR_RefLocalAcrossAwait + "CS9274" // ErrorCode.ERR_DataSectionStringLiteralHashCollision )] [Shared] internal sealed class CSharpLspBuildOnlyDiagnostics : ILspBuildOnlyDiagnostics diff --git a/src/Features/CSharp/Portable/Formatting/CSharpAccessibilityModifiersNewDocumentFormattingProvider.cs b/src/Features/CSharp/Portable/Formatting/CSharpAccessibilityModifiersNewDocumentFormattingProvider.cs index e43d6fbe50178..212256cebcce8 100644 --- a/src/Features/CSharp/Portable/Formatting/CSharpAccessibilityModifiersNewDocumentFormattingProvider.cs +++ b/src/Features/CSharp/Portable/Formatting/CSharpAccessibilityModifiersNewDocumentFormattingProvider.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.AddAccessibilityModifiers; +using Microsoft.CodeAnalysis.AddOrRemoveAccessibilityModifiers; using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.LanguageService; @@ -41,7 +41,7 @@ public async Task FormatNewDocumentAsync(Document document, Document? var typeDeclarations = root.DescendantNodes().Where(node => syntaxFacts.IsTypeDeclaration(node)); var editor = new SyntaxEditor(root, document.Project.Solution.Services); - var service = document.GetRequiredLanguageService(); + var service = document.GetRequiredLanguageService(); foreach (var declaration in typeDeclarations) { @@ -69,7 +69,7 @@ public async Task FormatNewDocumentAsync(Document document, Document? if (type == null) continue; - AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, type, declaration); + AddOrRemoveAccessibilityModifiersHelpers.UpdateDeclaration(editor, type, declaration); } return document.WithSyntaxRoot(editor.GetChangedRoot()); diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs index 3121368ae6fa1..3340b0799a889 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -53,12 +51,17 @@ protected override Document IntroduceLocal( var updatedExpression = expression.WithoutTrivia(); var simplifierOptions = (CSharpSimplifierOptions)options.SimplifierOptions; - // If the target-type new syntax is preferred and "var" is not preferred under any circumstance, then we use the target-type new syntax. - // The approach is not exhaustive. We aim to support codebases that rely strictly on the target-type new syntax (i.e., no "var"). + // If the target-type new syntax is preferred and "var" is not preferred under any circumstance, then we use the + // target-type new syntax. The approach is not exhaustive. We aim to support codebases that rely strictly on the + // target-type new syntax (i.e., no "var"). if (simplifierOptions.ImplicitObjectCreationWhenTypeIsApparent.Value && simplifierOptions.GetUseVarPreference() == UseVarPreference.None && updatedExpression is ObjectCreationExpressionSyntax objectCreationExpression) { - updatedExpression = ImplicitObjectCreationExpression(objectCreationExpression.ArgumentList, objectCreationExpression.Initializer); + var (newKeyword, argumentList) = objectCreationExpression.ArgumentList is null + ? (objectCreationExpression.NewKeyword.WithoutTrailingTrivia(), ArgumentList().WithoutLeadingTrivia().WithTrailingTrivia(objectCreationExpression.NewKeyword.TrailingTrivia)) + : (objectCreationExpression.NewKeyword, objectCreationExpression.ArgumentList); + updatedExpression = ImplicitObjectCreationExpression( + newKeyword, argumentList, objectCreationExpression.Initializer); } var declarationStatement = LocalDeclarationStatement( @@ -83,7 +86,7 @@ protected override Document IntroduceLocal( case ArrowExpressionClauseSyntax arrowExpression: // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol), // both of which are a shorthand for the getter and always return a value - var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent, cancellationToken) as IMethodSymbol; + var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.GetRequiredParent(), cancellationToken) as IMethodSymbol; var createReturnStatement = true; if (method is not null) @@ -235,7 +238,7 @@ private Document RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( CancellationToken cancellationToken) { var oldBody = arrowExpression; - var oldParentingNode = oldBody.Parent; + var oldParentingNode = oldBody.GetRequiredParent(); var leadingTrivia = oldBody.GetLeadingTrivia() .AddRange(oldBody.ArrowToken.TrailingTrivia); @@ -320,7 +323,7 @@ scope is ICompilationUnitSyntax if (scope is BlockSyntax block) { - var firstAffectedStatement = block.Statements.Single(s => firstAffectedExpression.GetAncestorOrThis().Contains(s)); + var firstAffectedStatement = block.Statements.Single(s => firstAffectedExpression.GetAncestorOrThis()!.Contains(s)); var firstAffectedStatementIndex = block.Statements.IndexOf(firstAffectedStatement); editor.ReplaceNode( block, @@ -333,7 +336,7 @@ scope is ICompilationUnitSyntax } else { - var firstAffectedGlobalStatement = compilationUnit.Members.OfType().Single(s => firstAffectedExpression.GetAncestorOrThis().Contains(s)); + var firstAffectedGlobalStatement = compilationUnit.Members.OfType().Single(s => firstAffectedExpression.GetAncestorOrThis()!.Contains(s)); var firstAffectedGlobalStatementIndex = compilationUnit.Members.IndexOf(firstAffectedGlobalStatement); editor.ReplaceNode( compilationUnit, @@ -348,6 +351,8 @@ scope is ICompilationUnitSyntax return document.Document.WithSyntaxRoot(editor.GetChangedRoot()); } +#nullable disable + private Document IntroduceLocalDeclarationIntoBlock( SemanticDocument document, BlockSyntax block, @@ -371,12 +376,6 @@ private Document IntroduceLocalDeclarationIntoBlock( var matches = FindMatches(document, expression, document, [scope], allOccurrences, cancellationToken); Debug.Assert(matches.Contains(expression)); - //(document, matches) = await ComplexifyParentingStatementsAsync(document, matches, cancellationToken).ConfigureAwait(false); - - //// Our original expression should have been one of the matches, which were tracked as part - //// of complexification, so we can retrieve the latest version of the expression here. - //expression = document.Root.GetCurrentNode(expression); - var root = document.Root; ISet allAffectedStatements = new HashSet(matches.SelectMany(expr => GetApplicableStatementAncestors(expr))); @@ -421,6 +420,8 @@ private Document IntroduceLocalDeclarationIntoBlock( return document.Document.WithSyntaxRoot(newRoot); } +#nullable restore + private static IEnumerable GetApplicableStatementAncestors(ExpressionSyntax expr) { foreach (var statement in expr.GetAncestorsOrThis()) @@ -454,10 +455,11 @@ private static int GetFirstStatementAffectedIndex(SyntaxNode innermostCommonBloc var localFunctionIdentifiers = localFunctions.Select(node => ((LocalFunctionStatementSyntax)node).Identifier.ValueText); // Find all calls to the applicable local functions within the scope. - var localFunctionCalls = innermostCommonBlock.DescendantNodes().Where(node => node is InvocationExpressionSyntax invocationExpression && - invocationExpression.Expression.GetRightmostName() != null && - !invocationExpression.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression) && - localFunctionIdentifiers.Contains(invocationExpression.Expression.GetRightmostName().Identifier.ValueText)); + var localFunctionCalls = innermostCommonBlock.DescendantNodes().Where( + node => node is InvocationExpressionSyntax invocationExpression && + invocationExpression.Expression.GetRightmostName() is { } rightmostName && + !invocationExpression.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression) && + localFunctionIdentifiers.Contains(rightmostName.Identifier.ValueText)); if (localFunctionCalls.IsEmpty()) { diff --git a/src/Features/CSharp/Portable/NavigateTo/CSharpNavigateToSearchService.cs b/src/Features/CSharp/Portable/NavigateTo/CSharpNavigateToSearchService.cs index 1ba731b48c9ce..bece1c625bb40 100644 --- a/src/Features/CSharp/Portable/NavigateTo/CSharpNavigateToSearchService.cs +++ b/src/Features/CSharp/Portable/NavigateTo/CSharpNavigateToSearchService.cs @@ -12,6 +12,4 @@ namespace Microsoft.CodeAnalysis.CSharp.NavigateTo; [ExportLanguageService(typeof(INavigateToSearchService), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class CSharpNavigateToSearchService() : AbstractNavigateToSearchService -{ -} +internal sealed class CSharpNavigateToSearchService() : AbstractNavigateToSearchService; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs index 406a380e2ca8a..0631779f5892a 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs @@ -25,5 +25,6 @@ internal static class CSharpSnippetIdentifiers public const string StaticIntMain = "sim"; public const string Struct = "struct"; public const string StaticVoidMain = "svm"; + public const string Using = "using"; public const string While = "while"; } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpUsingSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpUsingSnippetProvider.cs new file mode 100644 index 0000000000000..65f148c6f587e --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpUsingSnippetProvider.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets; + +[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpUsingSnippetProvider() : AbstractUsingSnippetProvider +{ + public override string Identifier => CSharpSnippetIdentifiers.Using; + + public override string Description => CSharpFeaturesResources.using_statement; + + protected override ImmutableArray GetPlaceHolderLocationsList(UsingStatementSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + var expression = node.Expression!; + return [new SnippetPlaceholder(expression.ToString(), expression.SpanStart)]; + } + + protected override int GetTargetCaretPosition(UsingStatementSyntax usingStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + usingStatement, + static s => (BlockSyntax)s.Statement, + sourceText); + + protected override Task AddIndentationToDocumentAsync(Document document, UsingStatementSyntax usingStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + document, + usingStatement, + static s => (BlockSyntax)s.Statement, + cancellationToken); +} diff --git a/src/Features/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs b/src/Features/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs index 111cea0267bfa..1884ab2379f76 100644 --- a/src/Features/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs +++ b/src/Features/CSharpTest/ConvertAutoPropertyToFullProperty/ConvertAutoPropertyToFullPropertyTests.cs @@ -1444,4 +1444,34 @@ public int Goo """; await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, index: 1, parseOptions: CSharp14); } + + [Theory] + [InlineData("set"), InlineData("init")] + [WorkItem("https://github.com/dotnet/roslyn/issues/76992")] + public async Task ProduceFieldBackedProperty2(string setter) + { + var text = $$""" + class TestClass + { + public int G[||]oo { get; {{setter}}; } = 0; + } + """; + var expected = $$""" + class TestClass + { + public int Goo + { + get + { + return field; + } + {{setter}} + { + field = value; + } + } = 0; + } + """; + await TestInRegularAndScriptAsync(text, expected, options: DoNotPreferExpressionBodiedAccessors, index: 1, parseOptions: CSharp14); + } } diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs index f91442f8ca011..394d5834bb4e9 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -15,12 +16,12 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertToInterpolatedString; [Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] -public class ConvertPlaceholderToInterpolatedStringTests : AbstractCSharpCodeActionTest_NoEditor +public sealed class ConvertPlaceholderToInterpolatedStringTests : AbstractCSharpCodeActionTest_NoEditor { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(TestWorkspace workspace, TestParameters parameters) => new CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider(); - private static readonly string[] CompositeFormattedMethods = + private static readonly ImmutableArray CompositeFormattedMethods = [ "Console.Write", "Console.WriteLine", @@ -81,8 +82,7 @@ static string MakeFormattedParameters(int numberOfParameters) } } - [Theory] - [MemberData(nameof(InvocationData))] + [Theory, MemberData(nameof(InvocationData))] public async Task TestInvocationSubstitution(string before, string after) { await TestInRegularAndScriptAsync( @@ -1187,4 +1187,37 @@ void M() } """); } + + [Theory, MemberData(nameof(InvocationData))] + [WorkItem("https://github.com/dotnet/roslyn/issues/68469")] + public async Task TestInvocationSubstitution_FixAll(string before, string after) + { + await TestInRegularAndScriptAsync( + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + {|FixAllInDocument:{{before}}|}; + {{before}}; + } + } + """, + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + {{after}}; + {{after}}; + } + } + """); + } } diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index 7d9906343d41b..10a161b802860 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -446,8 +446,7 @@ void Method() var analyzerReference = new AnalyzerImageReference([new CSharpCompilerDiagnosticAnalyzer()]); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); - var incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace); + var diagnosticService = workspace.ExportProvider.GetExportedValue(); var suppressionProvider = CreateDiagnosticProviderAndFixer(workspace).Item2; var suppressionProviderFactory = new Lazy(() => suppressionProvider, new CodeChangeProviderMetadata("SuppressionProvider", languages: [LanguageNames.CSharp])); diff --git a/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs b/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs index d373fe07a0bfb..9053cd70d1bbb 100644 --- a/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs @@ -4334,7 +4334,7 @@ public T this[int i] } [Fact] - public async Task TestIntroduceLocalWithTargetTypedNew() + public async Task TestIntroduceLocalWithTargetTypedNew1() { var code = """ @@ -4383,6 +4383,48 @@ private class Numbers {} await TestInRegularAndScriptAsync(code, expected, options: optionsCollection); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77276")] + public async Task TestIntroduceLocalWithTargetTypedNew2() + { + var code = + """ + public static class Demo + { + public static void Test() + { + Console.WriteLine([|new Class1 { Value = 123 }|]); + } + } + + public sealed class Class1 + { + public int Value { get; set; } + } + """; + + var expected = + """ + public static class Demo + { + public static void Test() + { + Class1 {|Rename:class1|} = new() { Value = 123 }; + Console.WriteLine(class1); + } + } + + public sealed class Class1 + { + public int Value { get; set; } + } + """; + + await TestInRegularAndScriptAsync(code, expected, options: new(GetLanguage()) + { + { CSharpCodeStyleOptions.ImplicitObjectCreationWhenTypeIsApparent, new CodeStyleOption2(true, NotificationOption2.Warning) }, + }); + } + [Fact] public async Task TestIntroduceFieldInExpressionBodiedPropertyGetter() { diff --git a/src/Features/CSharpTest/Snippets/CSharpUsingSnippetProviderTests.cs b/src/Features/CSharpTest/Snippets/CSharpUsingSnippetProviderTests.cs new file mode 100644 index 0000000000000..3cc449347bab4 --- /dev/null +++ b/src/Features/CSharpTest/Snippets/CSharpUsingSnippetProviderTests.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets; + +[Trait(Traits.Feature, Traits.Features.Snippets)] +public sealed class CSharpUsingSnippetProviderTests : AbstractCSharpSnippetProviderTests +{ + protected override string SnippetIdentifier => "using"; + + [Fact] + public async Task InsertUsingSnippetInMethodTest() + { + await VerifySnippetAsync(""" + class Program + { + public void Method() + { + $$ + } + } + """, """ + class Program + { + public void Method() + { + using ({|0:resource|}) + { + $$ + } + } + } + """); + } + + [Fact] + public async Task InsertUsingSnippetInGlobalContextTest() + { + await VerifySnippetAsync(""" + $$ + """, """ + using ({|0:resource|}) + { + $$ + } + """); + } + + [Fact] + public async Task NoUsingSnippetInBusingNamespaceTest() + { + await VerifySnippetIsAbsentAsync(""" + namespace Namespace + { + $$ + } + """); + } + + [Fact] + public async Task NoUsingSnippetInFileScopedNamespaceTest() + { + await VerifySnippetIsAbsentAsync(""" + namespace Namespace; + $$ + """); + } + + [Fact] + public async Task InsertUsingSnippetInConstructorTest() + { + await VerifySnippetAsync(""" + class Program + { + public Program() + { + $$ + } + } + """, """ + class Program + { + public Program() + { + using ({|0:resource|}) + { + $$ + } + } + } + """); + } + + [Fact] + public async Task NoUsingSnippetInTypeBodyTest() + { + await VerifySnippetIsAbsentAsync(""" + class Program + { + $$ + } + """); + } + + [Fact] + public async Task InsertUsingSnippetInLocalFunctionTest() + { + await VerifySnippetAsync(""" + class Program + { + public void Method() + { + void LocalFunction() + { + $$ + } + } + } + """, """ + class Program + { + public void Method() + { + void LocalFunction() + { + using ({|0:resource|}) + { + $$ + } + } + } + } + """); + } + + [Fact] + public async Task InsertUsingSnippetInAnonymousFunctionTest() + { + await VerifySnippetAsync(""" + class Program + { + public void Method() + { + var action = delegate() + { + $$ + }; + } + } + """, """ + class Program + { + public void Method() + { + var action = delegate() + { + using ({|0:resource|}) + { + $$ + } + }; + } + } + """); + } + + [Fact] + public async Task InsertUsingSnippetInParenthesizedLambdaExpressionTest() + { + await VerifySnippetAsync(""" + class Program + { + public void Method() + { + var action = () => + { + $$ + }; + } + } + """, """ + class Program + { + public void Method() + { + var action = () => + { + using ({|0:resource|}) + { + $$ + } + }; + } + } + """); + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 741e36da818da..0c9811d172e7e 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings; diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs index bbdd9444b28fa..35aede8ef24c6 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.Editor.cs @@ -2,13 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; @@ -19,15 +16,16 @@ internal abstract partial class AbstractMoveTypeService private abstract class Editor( TService service, - State state, + SemanticDocument semanticDocument, + TTypeDeclarationSyntax typeDeclaration, string fileName, CancellationToken cancellationToken) { - protected State State { get; } = state; protected TService Service { get; } = service; + protected SemanticDocument SemanticDocument { get; } = semanticDocument; + protected TTypeDeclarationSyntax TypeDeclaration { get; } = typeDeclaration; protected string FileName { get; } = fileName; protected CancellationToken CancellationToken { get; } = cancellationToken; - protected SemanticDocument SemanticDocument => State.SemanticDocument; /// /// Operations performed by CodeAction. @@ -35,27 +33,21 @@ private abstract class Editor( public virtual async Task> GetOperationsAsync() { var solution = await GetModifiedSolutionAsync().ConfigureAwait(false); - - if (solution == null) - { - return []; - } - - return [new ApplyChangesOperation(solution)]; + return solution == null ? [] : [new ApplyChangesOperation(solution)]; } /// /// Incremental solution edits that correlate to code operations /// - public abstract Task GetModifiedSolutionAsync(); + public abstract Task GetModifiedSolutionAsync(); - public static Editor GetEditor(MoveTypeOperationKind operationKind, TService service, State state, string fileName, CancellationToken cancellationToken) + public static Editor GetEditor(MoveTypeOperationKind operationKind, TService service, SemanticDocument document, TTypeDeclarationSyntax typeDeclaration, string fileName, CancellationToken cancellationToken) => operationKind switch { - MoveTypeOperationKind.MoveType => new MoveTypeEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.RenameType => new RenameTypeEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.RenameFile => new RenameFileEditor(service, state, fileName, cancellationToken), - MoveTypeOperationKind.MoveTypeNamespaceScope => new MoveTypeNamespaceScopeEditor(service, state, fileName, cancellationToken), + MoveTypeOperationKind.MoveType => new MoveTypeEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.RenameType => new RenameTypeEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.RenameFile => new RenameFileEditor(service, document, typeDeclaration, fileName, cancellationToken), + MoveTypeOperationKind.MoveTypeNamespaceScope => new MoveTypeNamespaceScopeEditor(service, document, typeDeclaration, fileName, cancellationToken), _ => throw ExceptionUtilities.UnexpectedValue(operationKind), }; } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs index 0cc0ababd3d02..c279936be45a9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeCodeAction.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; @@ -17,41 +14,43 @@ internal abstract partial class AbstractMoveTypeService _operationKind switch { MoveTypeOperationKind.MoveType => string.Format(FeaturesResources.Move_type_to_0, _fileName), - MoveTypeOperationKind.RenameType => string.Format(FeaturesResources.Rename_type_to_0, _state.DocumentNameWithoutExtension), + MoveTypeOperationKind.RenameType => string.Format(FeaturesResources.Rename_type_to_0, GetDocumentNameWithoutExtension(_document)), MoveTypeOperationKind.RenameFile => string.Format(FeaturesResources.Rename_file_to_0, _fileName), MoveTypeOperationKind.MoveTypeNamespaceScope => string.Empty, _ => throw ExceptionUtilities.UnexpectedValue(_operationKind), }; - public override string Title => _title; + public override string Title { get; } protected override async Task> ComputeOperationsAsync( IProgress progress, CancellationToken cancellationToken) { - var editor = Editor.GetEditor(_operationKind, _service, _state, _fileName, cancellationToken); + var editor = Editor.GetEditor(_operationKind, _service, _document, _typeDeclaration, _fileName, cancellationToken); return await editor.GetOperationsAsync().ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs index f904672a73af1..e4fc8389b3993 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs @@ -25,9 +25,10 @@ internal abstract partial class AbstractMoveTypeService /// Given a document and a type contained in it, moves the type @@ -42,10 +43,10 @@ private sealed class MoveTypeEditor( /// 3. Add this forked document to the solution. /// 4. Finally, update the original document and remove the type from it. /// - public override async Task GetModifiedSolutionAsync() + public override async Task GetModifiedSolutionAsync() { // Fork, update and add as new document. - var projectToBeUpdated = SemanticDocument.Document.Project; + var projectToBeUpdated = SemanticDocument.Project; var newDocumentId = DocumentId.CreateNewId(projectToBeUpdated.Id, FileName); // We do this process in the following steps: @@ -142,7 +143,7 @@ private async Task AddNewDocumentWithSingleTypeDeclarationAsync(Docume documentEditor.RemoveAllAttributes(root); // Now remove any leading directives on the type-node that actually correspond to prior nodes we removed. - var leadingTrivia = State.TypeNode.GetLeadingTrivia().ToSet(); + var leadingTrivia = this.TypeDeclaration.GetLeadingTrivia().ToSet(); foreach (var directive in correspondingDirectives) { if (leadingTrivia.Contains(directive.ParentTrivia)) @@ -185,7 +186,7 @@ void AddCorrespondingDirectives(SyntaxNode member, HashSet directive private void RemoveLeadingBlankLinesFromMovedType(DocumentEditor documentEditor) { - documentEditor.ReplaceNode(State.TypeNode, + documentEditor.ReplaceNode(this.TypeDeclaration, (currentNode, generator) => { var currentTypeNode = (TTypeDeclarationSyntax)currentNode; @@ -240,7 +241,7 @@ private async Task RemoveTypeFromSourceDocumentAsync(Document sourceDo // Now cleanup and remove the type we're moving to the new file. RemoveLeadingBlankLinesFromMovedType(documentEditor); - documentEditor.RemoveNode(State.TypeNode, SyntaxRemoveOptions.KeepUnbalancedDirectives); + documentEditor.RemoveNode(this.TypeDeclaration, SyntaxRemoveOptions.KeepUnbalancedDirectives); var updatedDocument = documentEditor.GetChangedDocument(); updatedDocument = await AddFileBannerHelpers.CopyBannerAsync(updatedDocument, sourceDocument.FilePath, sourceDocument, this.CancellationToken).ConfigureAwait(false); @@ -260,12 +261,12 @@ private ISet GetMembersToRemove(SyntaxNode root) var spine = new HashSet(); // collect the parent chain of declarations to keep. - spine.AddRange(State.TypeNode.GetAncestors()); + spine.AddRange(this.TypeDeclaration.GetAncestors()); // get potential namespace, types and members to remove. var removableCandidates = root .DescendantNodes(spine.Contains) - .Where(n => FilterToTopLevelMembers(n, State.TypeNode)).ToSet(); + .Where(n => FilterToTopLevelMembers(n, this.TypeDeclaration)).ToSet(); // diff candidates with items we want to keep. removableCandidates.ExceptWith(spine); @@ -304,12 +305,12 @@ private void AddPartialModifiersToTypeChain( bool removeTypeInheritance, bool removePrimaryConstructor) { - var semanticFacts = State.SemanticDocument.Document.GetRequiredLanguageService(); - var typeChain = State.TypeNode.Ancestors().OfType(); + var semanticFacts = SemanticDocument.GetRequiredLanguageService(); + var typeChain = this.TypeDeclaration.Ancestors().OfType(); foreach (var node in typeChain) { - var symbol = (INamedTypeSymbol?)State.SemanticDocument.SemanticModel.GetDeclaredSymbol(node, CancellationToken); + var symbol = (INamedTypeSymbol)SemanticDocument.SemanticModel.GetRequiredDeclaredSymbol(node, CancellationToken); Contract.ThrowIfNull(symbol); if (!semanticFacts.IsPartial(symbol, CancellationToken)) { @@ -338,8 +339,8 @@ private void AddPartialModifiersToTypeChain( private TTypeDeclarationSyntax RemoveLeadingBlankLines( TTypeDeclarationSyntax currentTypeNode) { - var syntaxFacts = State.SemanticDocument.Document.GetRequiredLanguageService(); - var bannerService = State.SemanticDocument.Document.GetRequiredLanguageService(); + var syntaxFacts = SemanticDocument.GetRequiredLanguageService(); + var bannerService = SemanticDocument.GetRequiredLanguageService(); var withoutBlankLines = bannerService.GetNodeWithoutLeadingBlankLines(currentTypeNode); diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs index 73aa4788eb51b..596ed816f2a80 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeNamespaceScopeEditor.cs @@ -20,43 +20,40 @@ internal abstract partial class AbstractMoveTypeService< /// it will evaluate if the namespace scope needs to be closed and reopened to create a new scope. /// private sealed class MoveTypeNamespaceScopeEditor( - TService service, State state, string fileName, CancellationToken cancellationToken) - : Editor(service, state, fileName, cancellationToken) + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) + : Editor(service, document, typeDeclaration, fileName, cancellationToken) { public override async Task GetModifiedSolutionAsync() { - var node = State.TypeNode; - var documentToEdit = State.SemanticDocument.Document; - - if (node.Parent is not TNamespaceDeclarationSyntax namespaceDeclaration) - return null; - - return await GetNamespaceScopeChangedSolutionAsync(namespaceDeclaration, node, documentToEdit, CancellationToken).ConfigureAwait(false); + return TypeDeclaration.Parent is TNamespaceDeclarationSyntax namespaceDeclaration + ? await GetNamespaceScopeChangedSolutionAsync(namespaceDeclaration).ConfigureAwait(false) + : null; } - private static async Task GetNamespaceScopeChangedSolutionAsync( - TNamespaceDeclarationSyntax namespaceDeclaration, - TTypeDeclarationSyntax typeToMove, - Document documentToEdit, - CancellationToken cancellationToken) + private async Task GetNamespaceScopeChangedSolutionAsync( + TNamespaceDeclarationSyntax namespaceDeclaration) { - var syntaxFactsService = documentToEdit.GetRequiredLanguageService(); + var syntaxFactsService = SemanticDocument.GetRequiredLanguageService(); var childNodes = syntaxFactsService.GetMembersOfBaseNamespaceDeclaration(namespaceDeclaration); if (childNodes.Count <= 1) return null; - var editor = await DocumentEditor.CreateAsync(documentToEdit, cancellationToken).ConfigureAwait(false); - editor.RemoveNode(typeToMove, SyntaxRemoveOptions.KeepNoTrivia); + var editor = await DocumentEditor.CreateAsync(SemanticDocument.Document, this.CancellationToken).ConfigureAwait(false); + editor.RemoveNode(this.TypeDeclaration, SyntaxRemoveOptions.KeepNoTrivia); var generator = editor.Generator; - var index = childNodes.IndexOf(typeToMove); + var index = childNodes.IndexOf(this.TypeDeclaration); var itemsBefore = childNodes.Take(index).ToImmutableArray(); var itemsAfter = childNodes.Skip(index + 1).ToImmutableArray(); var name = syntaxFactsService.GetDisplayName(namespaceDeclaration, DisplayNameOptions.IncludeNamespaces); - var newNamespaceDeclaration = generator.NamespaceDeclaration(name, WithElasticTrivia(typeToMove)).WithAdditionalAnnotations(NamespaceScopeMovedAnnotation); + var newNamespaceDeclaration = generator.NamespaceDeclaration(name, WithElasticTrivia(this.TypeDeclaration)).WithAdditionalAnnotations(NamespaceScopeMovedAnnotation); if (itemsBefore.Any() && itemsAfter.Any()) { diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs index 031364a87dbd4..73607e6b9e236 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameFileEditor.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -13,29 +11,25 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; internal abstract partial class AbstractMoveTypeService { - private sealed class RenameFileEditor(TService service, State state, string fileName, CancellationToken cancellationToken) : Editor(service, state, fileName, cancellationToken) + /// + /// Renames the file to match the type contained in it. + /// + private sealed class RenameFileEditor( + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) : Editor(service, document, typeDeclaration, fileName, cancellationToken) { - public override Task> GetOperationsAsync() - => Task.FromResult(RenameFileToMatchTypeName()); - - public override Task GetModifiedSolutionAsync() - { - var modifiedSolution = SemanticDocument.Project.Solution - .WithDocumentName(SemanticDocument.Document.Id, FileName); - - return Task.FromResult(modifiedSolution); - } - - /// - /// Renames the file to match the type contained in it. - /// - private ImmutableArray RenameFileToMatchTypeName() + public override async Task> GetOperationsAsync() { - var documentId = SemanticDocument.Document.Id; - var oldSolution = SemanticDocument.Document.Project.Solution; - var newSolution = oldSolution.WithDocumentName(documentId, FileName); - + var newSolution = await GetModifiedSolutionAsync().ConfigureAwait(false); + Contract.ThrowIfNull(newSolution); return [new ApplyChangesOperation(newSolution)]; } + + public override Task GetModifiedSolutionAsync() + => Task.FromResult( + SemanticDocument.Project.Solution.WithDocumentName(SemanticDocument.Document.Id, FileName)); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs index a7ede6d827d32..6ec382833414d 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.RenameTypeEditor.cs @@ -2,29 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; internal abstract partial class AbstractMoveTypeService { - private sealed class RenameTypeEditor(TService service, State state, string fileName, CancellationToken cancellationToken) : Editor(service, state, fileName, cancellationToken) + private sealed class RenameTypeEditor( + TService service, + SemanticDocument document, + TTypeDeclarationSyntax typeDeclaration, + string fileName, + CancellationToken cancellationToken) : Editor(service, document, typeDeclaration, fileName, cancellationToken) { - /// /// Renames a type to match its containing file name. /// - public override async Task GetModifiedSolutionAsync() + public override async Task GetModifiedSolutionAsync() { // TODO: detect conflicts ahead of time and open an inline rename session if any exists. // this will bring up dashboard with conflicts and will allow the user to resolve them. // if no such conflicts exist, proceed with RenameSymbolAsync. - var solution = SemanticDocument.Document.Project.Solution; - var symbol = State.SemanticDocument.SemanticModel.GetDeclaredSymbol(State.TypeNode, CancellationToken); + var solution = SemanticDocument.Project.Solution; + var symbol = SemanticDocument.SemanticModel.GetRequiredDeclaredSymbol(this.TypeDeclaration, CancellationToken); return await Renamer.RenameSymbolAsync(solution, symbol, new SymbolRenameOptions(), FileName, CancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs deleted file mode 100644 index e7b7c1141a844..0000000000000 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.State.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.IO; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.CodeRefactorings.MoveType; - -internal abstract partial class AbstractMoveTypeService -{ - private sealed class State - { - public SemanticDocument SemanticDocument { get; } - - public TTypeDeclarationSyntax TypeNode { get; set; } - public string TypeName { get; set; } - public string DocumentNameWithoutExtension { get; set; } - public bool IsDocumentNameAValidIdentifier { get; set; } - - private State(SemanticDocument document) - { - SemanticDocument = document; - } - - internal static State Generate( - SemanticDocument document, TTypeDeclarationSyntax typeDeclaration, - CancellationToken cancellationToken) - { - var state = new State(document); - if (!state.TryInitialize(typeDeclaration, cancellationToken)) - { - return null; - } - - return state; - } - - private bool TryInitialize( - TTypeDeclarationSyntax typeDeclaration, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return false; - } - - var tree = SemanticDocument.SyntaxTree; - var root = SemanticDocument.Root; - var syntaxFacts = SemanticDocument.Document.GetLanguageService(); - - // compiler declared types, anonymous types, types defined in metadata should be filtered out. - if (SemanticDocument.SemanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken) is not INamedTypeSymbol typeSymbol || - typeSymbol.Locations.Any(static loc => loc.IsInMetadata) || - typeSymbol.IsAnonymousType || - typeSymbol.IsImplicitlyDeclared || - typeSymbol.Name == string.Empty) - { - return false; - } - - TypeNode = typeDeclaration; - TypeName = typeSymbol.Name; - DocumentNameWithoutExtension = Path.GetFileNameWithoutExtension(SemanticDocument.Document.Name); - IsDocumentNameAValidIdentifier = syntaxFacts.IsValidIdentifier(DocumentNameWithoutExtension); - - return true; - } - } -} diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs index ffc6f4db6dbca..39ff19a2d8b3b 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -38,83 +37,69 @@ internal abstract partial class AbstractMoveTypeService GetRelevantNodeAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); protected abstract bool IsMemberDeclaration(SyntaxNode syntaxNode); + protected string GetSymbolName(TTypeDeclarationSyntax syntax) + => GetSymbolNameAndArity(syntax).name; + public override async Task> GetRefactoringAsync( Document document, TextSpan textSpan, CancellationToken cancellationToken) { - var state = await CreateStateAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - return CreateActions(state, cancellationToken); + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var typeDeclaration = await GetTypeDeclarationAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + return CreateActions(semanticDocument, typeDeclaration); } public override async Task GetModifiedSolutionAsync(Document document, TextSpan textSpan, MoveTypeOperationKind operationKind, CancellationToken cancellationToken) { - var state = await CreateStateAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - - if (state == null) - { + var typeDeclaration = await GetTypeDeclarationAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + if (typeDeclaration == null) return document.Project.Solution; - } - var suggestedFileNames = GetSuggestedFileNames( - state.TypeNode, - IsNestedType(state.TypeNode), - state.TypeName, - state.SemanticDocument.Document.Name, - state.SemanticDocument.SemanticModel, - cancellationToken); - - var editor = Editor.GetEditor(operationKind, (TService)this, state, suggestedFileNames.FirstOrDefault(), cancellationToken); + var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var suggestedFileNames = GetSuggestedFileNames(semanticDocument, typeDeclaration, includeComplexFileNames: false); + + var editor = Editor.GetEditor(operationKind, (TService)this, semanticDocument, typeDeclaration, suggestedFileNames.First(), cancellationToken); var modifiedSolution = await editor.GetModifiedSolutionAsync().ConfigureAwait(false); return modifiedSolution ?? document.Project.Solution; } - private async Task CreateStateAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + private async Task GetTypeDeclarationAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) { var nodeToAnalyze = await GetRelevantNodeAsync(document, textSpan, cancellationToken).ConfigureAwait(false); if (nodeToAnalyze == null) return null; - var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - return State.Generate(semanticDocument, nodeToAnalyze, cancellationToken); + var name = this.GetSymbolName(nodeToAnalyze); + return name == "" ? null : nodeToAnalyze; } - private ImmutableArray CreateActions(State? state, CancellationToken cancellationToken) + private ImmutableArray CreateActions( + SemanticDocument document, TTypeDeclarationSyntax? typeDeclaration) { - if (state is null) + if (typeDeclaration is null) return []; - var typeMatchesDocumentName = TypeMatchesDocumentName( - state.TypeNode, - state.TypeName, - state.DocumentNameWithoutExtension, - state.SemanticDocument.SemanticModel, - cancellationToken); + var documentNameWithoutExtension = GetDocumentNameWithoutExtension(document); + var typeMatchesDocumentName = TypeMatchesDocumentName(typeDeclaration, documentNameWithoutExtension); + // if type name matches document name, per style conventions, we have nothing to do. if (typeMatchesDocumentName) - { - // if type name matches document name, per style conventions, we have nothing to do. return []; - } using var _ = ArrayBuilder.GetInstance(out var actions); - var manyTypes = MultipleTopLevelTypeDeclarationInSourceDocument(state.SemanticDocument.Root); - var isNestedType = IsNestedType(state.TypeNode); - var syntaxFacts = state.SemanticDocument.Document.GetRequiredLanguageService(); - var isClassNextToGlobalStatements = manyTypes - ? false - : ClassNextToGlobalStatements(state.SemanticDocument.Root, syntaxFacts); + var manyTypes = MultipleTopLevelTypeDeclarationInSourceDocument(document.Root); + var isNestedType = IsNestedType(typeDeclaration); + + var syntaxFacts = document.GetRequiredLanguageService(); + var isClassNextToGlobalStatements = !manyTypes && ClassNextToGlobalStatements(document.Root, syntaxFacts); var suggestedFileNames = GetSuggestedFileNames( - state.TypeNode, - isNestedType, - state.TypeName, - state.SemanticDocument.Document.Name, - state.SemanticDocument.SemanticModel, - cancellationToken); + document, typeDeclaration, includeComplexFileNames: false); // (1) Add Move type to new file code action: // case 1: There are multiple type declarations in current document. offer, move to new file. @@ -126,26 +111,21 @@ private ImmutableArray CreateActions(State? state, CancellationToken if (manyTypes || isNestedType || isClassNextToGlobalStatements) { foreach (var fileName in suggestedFileNames) - { - actions.Add(GetCodeAction(state, fileName, operationKind: MoveTypeOperationKind.MoveType)); - } + actions.Add(GetCodeAction(fileName, operationKind: MoveTypeOperationKind.MoveType)); } // (2) Add rename file and rename type code actions: // Case: No type declaration in file matches the file name. - if (!AnyTopLevelTypeMatchesDocumentName(state, cancellationToken)) + if (!AnyTopLevelTypeMatchesDocumentName()) { foreach (var fileName in suggestedFileNames) - { - actions.Add(GetCodeAction(state, fileName, operationKind: MoveTypeOperationKind.RenameFile)); - } + actions.Add(GetCodeAction(fileName, operationKind: MoveTypeOperationKind.RenameFile)); - // only if the document name can be legal identifier in the language, - // offer to rename type with document name - if (state.IsDocumentNameAValidIdentifier) + // Only if the document name can be legal identifier in the language, offer to rename type with document name + if (syntaxFacts.IsValidIdentifier(documentNameWithoutExtension)) { actions.Add(GetCodeAction( - state, fileName: state.DocumentNameWithoutExtension, + fileName: documentNameWithoutExtension, operationKind: MoveTypeOperationKind.RenameType)); } } @@ -153,14 +133,19 @@ private ImmutableArray CreateActions(State? state, CancellationToken Debug.Assert(actions.Count != 0, "No code actions found for MoveType Refactoring"); return actions.ToImmutableAndClear(); + + bool AnyTopLevelTypeMatchesDocumentName() + => TopLevelTypeDeclarations(document.Root).Any( + typeDeclaration => TypeMatchesDocumentName( + typeDeclaration, documentNameWithoutExtension)); + + MoveTypeCodeAction GetCodeAction(string fileName, MoveTypeOperationKind operationKind) + => new((TService)this, document, typeDeclaration, operationKind, fileName); } private static bool ClassNextToGlobalStatements(SyntaxNode root, ISyntaxFactsService syntaxFacts) => syntaxFacts.ContainsGlobalStatement(root); - private MoveTypeCodeAction GetCodeAction(State state, string fileName, MoveTypeOperationKind operationKind) - => new((TService)this, state, operationKind, fileName); - private static bool IsNestedType(TTypeDeclarationSyntax typeNode) => typeNode.Parent is TTypeDeclarationSyntax; @@ -174,23 +159,10 @@ private static bool MultipleTopLevelTypeDeclarationInSourceDocument(SyntaxNode r => TopLevelTypeDeclarations(root).Skip(1).Any(); private static IEnumerable TopLevelTypeDeclarations(SyntaxNode root) - => root.DescendantNodes(n => n is TCompilationUnitSyntax or TNamespaceDeclarationSyntax) - .OfType(); - - private static bool AnyTopLevelTypeMatchesDocumentName(State state, CancellationToken cancellationToken) - { - var root = state.SemanticDocument.Root; - var semanticModel = state.SemanticDocument.SemanticModel; + => root.DescendantNodes(n => n is TCompilationUnitSyntax or TNamespaceDeclarationSyntax).OfType(); - return TopLevelTypeDeclarations(root).Any( - typeDeclaration => - { - var typeName = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken).Name; - return TypeMatchesDocumentName( - typeDeclaration, typeName, state.DocumentNameWithoutExtension, - semanticModel, cancellationToken); - }); - } + private static string GetDocumentNameWithoutExtension(SemanticDocument document) + => Path.GetFileNameWithoutExtension(document.Document.Name); /// /// checks if type name matches its parent document name, per style rules. @@ -199,58 +171,90 @@ private static bool AnyTopLevelTypeMatchesDocumentName(State state, Cancellation /// Note: For a nested type, a matching document name could be just the type name or a /// dotted qualified name of its type hierarchy. /// - protected static bool TypeMatchesDocumentName( + protected bool TypeMatchesDocumentName( TTypeDeclarationSyntax typeNode, - string typeName, - string documentNameWithoutExtension, - SemanticModel semanticModel, - CancellationToken cancellationToken) + string documentNameWithoutExtension) { // If it is not a nested type, we compare the unqualified type name with the document name. // If it is a nested type, the type name `Outer.Inner` matches file names `Inner.cs` and `Outer.Inner.cs` - var namesMatch = typeName.Equals(documentNameWithoutExtension, StringComparison.CurrentCulture); - if (!namesMatch) - { - var typeNameParts = GetTypeNamePartsForNestedTypeNode(typeNode, semanticModel, cancellationToken); - var fileNameParts = documentNameWithoutExtension.Split('.'); + var (typeName, arity) = GetSymbolNameAndArity(typeNode); + if (TypeNameMatches(documentNameWithoutExtension, typeName, arity)) + return true; + + var typeNameParts = GetTypeNamePartsForNestedTypeNode(typeNode).ToImmutableArray(); + var fileNameParts = documentNameWithoutExtension.Split('.', '+'); - // qualified type name `Outer.Inner` matches file names `Inner.cs` and `Outer.Inner.cs` - return typeNameParts.SequenceEqual(fileNameParts, StringComparer.CurrentCulture); + if (typeNameParts.Length != fileNameParts.Length) + return false; + + // qualified type name `Outer.Inner` matches file names `Inner.cs` and `Outer.Inner.cs` as well as + // Outer`1.Inner`2.cs + for (int i = 0, n = typeNameParts.Length; i < n; i++) + { + if (!TypeNameMatches(fileNameParts[i], typeNameParts[i].name, typeNameParts[i].arity)) + return false; } - return namesMatch; + return true; + } + + private static bool TypeNameMatches(string documentNameWithoutExtension, string typeName, int arity) + { + if (typeName.Equals(documentNameWithoutExtension, StringComparison.CurrentCulture)) + return true; + + if ($"{typeName}`{arity}".Equals(documentNameWithoutExtension, StringComparison.CurrentCulture)) + return true; + + return false; } - private static ImmutableArray GetSuggestedFileNames( + private ImmutableArray GetSuggestedFileNames( + SemanticDocument document, TTypeDeclarationSyntax typeNode, - bool isNestedType, - string typeName, - string documentNameWithExtension, - SemanticModel semanticModel, - CancellationToken cancellationToken) + bool includeComplexFileNames) { + var documentNameWithExtension = document.Document.Name; + var isNestedType = IsNestedType(typeNode); + var (typeName, arity) = this.GetSymbolNameAndArity(typeNode); var fileExtension = Path.GetExtension(documentNameWithExtension); var standaloneName = typeName + fileExtension; - // If it is a nested type, we should match type hierarchy's name parts with the file name. + using var _ = ArrayBuilder.GetInstance(out var suggestedFileNames); + + suggestedFileNames.Add(typeName + fileExtension); + if (includeComplexFileNames && arity > 0) + suggestedFileNames.Add($"{typeName}`{arity}{fileExtension}"); + if (isNestedType) { - var typeNameParts = GetTypeNamePartsForNestedTypeNode(typeNode, semanticModel, cancellationToken); - var dottedName = typeNameParts.Join(".") + fileExtension; + var typeNameParts = GetTypeNamePartsForNestedTypeNode(typeNode); + AddNameParts(typeNameParts.Select(t => t.name)); + + if (includeComplexFileNames && typeNameParts.Any(t => t.arity > 0)) + AddNameParts(typeNameParts.Select(t => t.arity > 0 ? $"{t.name}`{t.arity}" : t.name)); + } + + return suggestedFileNames.ToImmutableAndClear(); + + void AddNameParts(IEnumerable parts) + { + AddNamePartsWithSeparator(parts, "."); - return [standaloneName, dottedName]; + if (includeComplexFileNames) + AddNamePartsWithSeparator(parts, "+"); } - else + + void AddNamePartsWithSeparator(IEnumerable parts, string separator) { - return [standaloneName]; + suggestedFileNames.Add(parts.Join(separator) + fileExtension); } } - private static IEnumerable GetTypeNamePartsForNestedTypeNode( - TTypeDeclarationSyntax typeNode, SemanticModel semanticModel, CancellationToken cancellationToken) - => typeNode.AncestorsAndSelf() - .OfType() - .Select(n => semanticModel.GetRequiredDeclaredSymbol(n, cancellationToken).Name) - .Reverse(); + private IEnumerable<(string name, int arity)> GetTypeNamePartsForNestedTypeNode(TTypeDeclarationSyntax typeNode) + => typeNode.AncestorsAndSelf() + .OfType() + .Select(GetSymbolNameAndArity) + .Reverse(); } diff --git a/src/Features/Core/Portable/Completion/CompletionService.ProviderManager.cs b/src/Features/Core/Portable/Completion/CompletionService.ProviderManager.cs index 3eb3abbd0a57f..546ffa37175d9 100644 --- a/src/Features/Core/Portable/Completion/CompletionService.ProviderManager.cs +++ b/src/Features/Core/Portable/Completion/CompletionService.ProviderManager.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion; diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs index f2a5475f289e3..85ecf6aa8d684 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractMemberInsertingCompletionProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -45,26 +46,8 @@ protected static CompletionItemRules GetRules() public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) { - var newDocument = await DetermineNewDocumentAsync(document, item, cancellationToken).ConfigureAwait(false); + var (newDocument, newPosition) = await DetermineNewDocumentAsync(document, item, cancellationToken).ConfigureAwait(false); var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - var newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - int? newPosition = null; - - // Attempt to find the inserted node and move the caret appropriately - if (newRoot != null) - { - var caretTarget = newRoot.GetAnnotatedNodes(_annotation).FirstOrDefault(); - if (caretTarget != null) - { - var targetPosition = GetTargetCaretPosition(caretTarget); - - if (targetPosition > 0 && targetPosition <= newText.Length) - { - newPosition = targetPosition; - } - } - } var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var changesArray = changes.ToImmutableArray(); @@ -73,7 +56,7 @@ public override async Task GetChangeAsync(Document document, C return CompletionChange.Create(change, changesArray, newPosition, includesCommitCharacter: true); } - private async Task DetermineNewDocumentAsync( + private async Task<(Document, int? caretPosition)> DetermineNewDocumentAsync( Document document, CompletionItem completionItem, CancellationToken cancellationToken) @@ -122,15 +105,13 @@ private async Task DetermineNewDocumentAsync( // Generating the new document failed because we somehow couldn't resolve // the underlying symbol's SymbolKey. At this point, we won't be able to // make any changes, so just return the document we started with. - return document; + return (document, null); } var memberContainingDocumentCleanupOptions = await document.GetCodeCleanupOptionsAsync(cancellationToken).ConfigureAwait(false); - document = await RemoveDestinationNodeAsync(memberContainingDocument, memberContainingDocumentCleanupOptions, cancellationToken).ConfigureAwait(false); - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); - document = await Simplifier.ReduceAsync(document, _annotation, memberContainingDocumentCleanupOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); - return await Formatter.FormatAsync(document, _annotation, formattingOptions, cancellationToken).ConfigureAwait(false); + var result = await RemoveDestinationNodeAsync(memberContainingDocument, memberContainingDocumentCleanupOptions, cancellationToken).ConfigureAwait(false); + return result; } private async Task GenerateMemberAndUsingsAsync( @@ -187,41 +168,88 @@ private TextSpan ComputeDestinationSpan(SyntaxNode insertionRoot) return TextSpan.FromBounds(startToken.Value.SpanStart, line.EndIncludingLineBreak); } - private async Task RemoveDestinationNodeAsync( + private async Task<(Document Document, int? CaretPosition)> RemoveDestinationNodeAsync( Document memberContainingDocument, CodeCleanupOptions cleanupOptions, CancellationToken cancellationToken) { - // At this stage we have created the replacing node, but we also have the source node that triggered the completion - // To remove the old node, we need to port over the trivia and then recalculate the node to remove - // since we may have adjusted the position of the node by inserting trivia before the new node - + // We now have a replacement node inserted into the document, but we still have the source code that triggered completion (with associated trivia). + // We need to move the trivia to the new replacement and remove the original code. var root = await memberContainingDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // Compute the destination span and node in the new tree. Depending on the context, the destination node can end up + // containing more code (and trivia) than what we want to remove. For example + // ``` + // override Eq + // [Attribute] public void M() {} + // ``` + // returns a destination node (array syntax) containing both the `override Eq` and the `[Attribute]` on the line below. var destinationSpan = ComputeDestinationSpan(root); var destinationNode = root.FindNode(destinationSpan, true); + var syntaxFacts = memberContainingDocument.Project.Services.GetRequiredService(); + + // Given that the destination node can contain code we want to keep, we can't directly remove it using syntax tree manipulations. + // It is difficult to create a valid syntax tree manipulation that does only a partial removal of the destination node / tokens. + // + // Instead it is much easier to remove the old completion source with a direct text edit (we know the exact span). + // But in order to do that, we first must move trivia to the replacement node and format/simplify (requires tree annotations). + + // First find all tokens inside the node that intersect the span being deleted. Move trivia from these tokens to the replacement node. + // Tokens from the destination node, but outside the destination span should not be touched. + var destinationTokens = destinationNode.DescendantTokens(destinationSpan); + + SyntaxTriviaList leadingTriviaToCopy = []; + SyntaxTriviaList trailingTriviaToCopy = []; + root = root.ReplaceTokens(destinationTokens, (original, originalAdjusted) => + { + // Save the trivia for later application onto the replacement node and then delete. + leadingTriviaToCopy = leadingTriviaToCopy.AddRange(original.LeadingTrivia); + trailingTriviaToCopy = trailingTriviaToCopy.AddRange(original.TrailingTrivia); + + var trailingEndOfLine = originalAdjusted.TrailingTrivia.FirstOrNull(t => syntaxFacts.IsEndOfLineTrivia(t)); + var destinationWithoutTrivia = originalAdjusted.WithoutTrivia(); + // If there was an end of line attached to the destination token, keep it as otherwise lines below + // may get moved up to the same line as the destination span line we're removing later. + if (trailingEndOfLine is not null) + destinationWithoutTrivia = destinationWithoutTrivia.WithTrailingTrivia(trailingEndOfLine.Value); + return destinationWithoutTrivia; + }); + + // Add the saved trivia on to the replacement node. var replacingNode = root.GetAnnotatedNodes(_annotation).Single(); - // WithTriviaFrom does not completely fit our purpose because we could be missing trivia from interior missing tokens, - // with all the last tokens being missing, and thus only having part of the picture - root = root.ReplaceNode(replacingNode, replacingNode.WithTriviaFromIncludingMissingTokens(destinationNode)); + root = root.ReplaceNode(replacingNode, replacingNode.WithLeadingTrivia(leadingTriviaToCopy).WithTrailingTrivia(trailingTriviaToCopy)); + + // We've finished the major modifications, we can now format and simplify. + var document = memberContainingDocument.WithSyntaxRoot(root); + document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, Formatter.Annotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); - // Now that we have replaced the node, find the destination node again + // Formatting/simplification changed the tree, so recompute the destination span. + root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); destinationSpan = ComputeDestinationSpan(root); - destinationNode = root.FindNode(destinationSpan); - SyntaxNode newRoot; - if (destinationSpan.Contains(destinationNode.Span)) - { - newRoot = root.RemoveNode(destinationNode, SyntaxRemoveOptions.KeepNoTrivia)!; - } - else + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // We have basically the final tree. Calculate the new caret position while we still have the annotations. + int? caretPosition = null; + var caretTarget = root.GetAnnotatedNodes(_annotation).FirstOrDefault(); + if (caretTarget != null) { - var tokens = destinationNode.DescendantTokens(destinationSpan); - newRoot = root.ReplaceTokens(tokens, static (_, _) => default); - } + var targetPosition = GetTargetCaretPosition(caretTarget); - var document = memberContainingDocument.WithSyntaxRoot(newRoot); + if (targetPosition > 0 && targetPosition <= text.Length) + { + // The new replacement method should always be inserted before the destination span we're removing. + // This means the caret position in the inserted method should be safe to return as-is. + Debug.Assert(targetPosition < destinationSpan.Start); + caretPosition = targetPosition; + } + } - document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken).ConfigureAwait(false); - document = await Formatter.FormatAsync(document, Formatter.Annotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); + // Now we can finally delete the destination span. It is safe to delete the whole line here (instead of just the destination span) + // as override completion will not be shown with unrelated text preceding or following the override trigger. + text.GetLineAndOffset(destinationSpan.Start, out var lineNumber, out _); + var textChange = new TextChange(text.Lines[lineNumber].SpanIncludingLineBreak, string.Empty); - return document; + text = text.WithChanges(textChange); + return (document.WithText(text), caretPosition); } internal override Task GetDescriptionWorkerAsync(Document document, CompletionItem item, CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs index e91c21220a3bb..4799c399e5108 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs @@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Threading; namespace Microsoft.CodeAnalysis.Completion.Providers; diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs index 64b2d8787533e..7c3bb764c5faa 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.Host; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Threading; namespace Microsoft.CodeAnalysis.Completion.Providers; diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index a4ce028eefdb4..0d8ad3bde2e12 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -24,7 +24,7 @@ internal static class SymbolCompletionItem private static readonly Action, ArrayBuilder>> s_addSymbolEncoding = AddSymbolEncoding; private static readonly Action, ArrayBuilder>> s_addSymbolInfo = AddSymbolInfo; - private static readonly char[] s_projectSeperators = [';']; + private const char ProjectSeperatorChar = ';'; private static CompletionItem CreateWorker( string displayText, @@ -236,13 +236,45 @@ private static void AddSupportedPlatforms(ArrayBuilder ProjectId.CreateFromSerialized(Guid.Parse(s))), - candidateProjects.Split(s_projectSeperators).SelectAsArray(s => ProjectId.CreateFromSerialized(Guid.Parse(s)))); + SplitIntoProjectIds(invalidProjects), + SplitIntoProjectIds(candidateProjects)); } return null; } + private static ImmutableArray SplitIntoProjectIds(string projectIds) + { + // Does the equivalent of string.Split, with fewer allocations + var start = 0; + var current = 0; + using var _ = ArrayBuilder.GetInstance(out var builder); + + while (current < projectIds.Length) + { + if (projectIds[current] == ProjectSeperatorChar) + { + if (start != current) + { + var projectGuid = Guid.Parse(projectIds.Substring(start, current - start)); + builder.Add(ProjectId.CreateFromSerialized(projectGuid)); + } + + start = current + 1; + } + + current++; + } + + if (start != current) + { + var projectGuid = Guid.Parse(projectIds.Substring(start, current - start)); + builder.Add(ProjectId.CreateFromSerialized(projectGuid)); + } + + return builder.ToImmutableAndClear(); + } + public static int GetContextPosition(CompletionItem item) { if (item.TryGetProperty("ContextPosition", out var text) && diff --git a/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs index 9fdb11ae24361..1275577e9282f 100644 --- a/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -99,9 +99,9 @@ private async Task ExpandToFullPropertyAsync( var fieldName = await GetFieldNameAsync(document, propertySymbol, cancellationToken).ConfigureAwait(false); var (newGetAccessor, newSetAccessor) = GetNewAccessors(info, property, fieldName, cancellationToken); - editor.ReplaceNode( - property, - CreateFinalProperty(document, property, info, newGetAccessor, newSetAccessor)); + var finalProperty = CreateFinalProperty( + document, GetPropertyWithoutInitializer(property), info, newGetAccessor, newSetAccessor); + editor.ReplaceNode(property, finalProperty); // add backing field, plus initializer if it exists var newField = CodeGenerationSymbolFactory.CreateFieldSymbol( @@ -137,7 +137,7 @@ protected SyntaxNode CreateFinalProperty( var fullProperty = generator .WithAccessorDeclarations( - GetPropertyWithoutInitializer(property), + property, newSetAccessor == null ? [newGetAccessor] : [newGetAccessor, newSetAccessor]) diff --git a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs index 6f6906bec80e7..45f15e9dc395f 100644 --- a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs @@ -9,12 +9,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ConvertToInterpolatedString; @@ -26,7 +29,7 @@ internal abstract class AbstractConvertPlaceholderToInterpolatedStringRefactorin TInterpolatedStringExpressionSyntax, TArgumentSyntax, TArgumentListExpressionSyntax, - TInterpolationSyntax> : CodeRefactoringProvider + TInterpolationSyntax> : SyntaxEditorBasedCodeRefactoringProvider where TExpressionSyntax : SyntaxNode where TLiteralExpressionSyntax : TExpressionSyntax where TInvocationExpressionSyntax : TExpressionSyntax @@ -35,29 +38,32 @@ internal abstract class AbstractConvertPlaceholderToInterpolatedStringRefactorin where TArgumentListExpressionSyntax : SyntaxNode where TInterpolationSyntax : SyntaxNode { - protected abstract TExpressionSyntax ParseExpression(string text); + private readonly record struct InvocationData( + TInvocationExpressionSyntax Invocation, + TArgumentSyntax PlaceholderArgument, + IMethodSymbol InvocationSymbol, + TInterpolatedStringExpressionSyntax InterpolatedString, + bool ShouldReplaceInvocation); - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var (document, span, cancellationToken) = context; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + protected abstract TExpressionSyntax ParseExpression(string text); - var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String); - if (stringType.IsErrorType()) - return; + protected override ImmutableArray SupportedFixAllScopes { get; } = AllFixAllScopes; + private InvocationData? AnalyzeInvocation( + Document document, + SemanticModel semanticModel, + TInvocationExpressionSyntax invocation, + TArgumentSyntax placeholderArgument, + CancellationToken cancellationToken) + { var syntaxFacts = document.GetRequiredLanguageService(); - var (invocation, placeholderArgument) = await TryFindInvocationAsync().ConfigureAwait(false); - if (invocation is null || placeholderArgument is null) - return; - var placeholderExpression = syntaxFacts.GetExpressionOfArgument(placeholderArgument); var stringToken = placeholderExpression.GetFirstToken(); // don't offer if the string argument has errors in it, or if converting it to an interpolated string creates errors. if (stringToken.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) - return; + return null; // Not supported if there are any omitted arguments following the placeholder. var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); @@ -67,14 +73,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte { var argument = arguments[i]; if (syntaxFacts.GetExpressionOfArgument(argument) is null) - return; + return null; if (syntaxFacts.GetRefKindOfArgument(argument) != RefKind.None) - return; + return null; } if (semanticModel.GetSymbolInfo(invocation, cancellationToken).GetAnySymbol() is not IMethodSymbol invocationSymbol) - return; + return null; // If the user is actually passing an array to a params argument, we can't change this to be an interpolated string. if (invocationSymbol.IsParams()) @@ -82,31 +88,52 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var lastArgument = syntaxFacts.GetArgumentsOfInvocationExpression(invocation).Last(); var lastArgumentType = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(lastArgument), cancellationToken).Type; if (lastArgumentType is IArrayTypeSymbol) - return; + return null; } // if the user is explicitly passing in a CultureInfo, don't offer as it's likely they want specialized // formatting for the values. foreach (var argument in arguments) { - var type = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(argument)).Type; + var type = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(argument), cancellationToken).Type; if (type is { Name: nameof(CultureInfo), ContainingNamespace.Name: nameof(System.Globalization), ContainingNamespace.ContainingNamespace.Name: nameof(System) }) - return; + return null; } if (ParseExpression("$" + stringToken.Text) is not TInterpolatedStringExpressionSyntax interpolatedString) - return; + return null; if (interpolatedString.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) - return; + return null; var shouldReplaceInvocation = invocationSymbol is { ContainingType.SpecialType: SpecialType.System_String, Name: nameof(string.Format) }; + return new(invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation); + } + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var (document, span, cancellationToken) = context; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String); + if (stringType.IsErrorType()) + return; + + var syntaxFacts = document.GetRequiredLanguageService(); + + var (invocation, placeholderArgument) = await TryFindInvocationAsync().ConfigureAwait(false); + if (invocation is null || placeholderArgument is null) + return; + + var data = this.AnalyzeInvocation(document, semanticModel, invocation, placeholderArgument, cancellationToken); + if (data is null) + return; + context.RegisterRefactoring( CodeAction.Create( FeaturesResources.Convert_to_interpolated_string, - cancellationToken => CreateInterpolatedStringAsync( - document, invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation, cancellationToken), + cancellationToken => CreateInterpolatedStringAsync(document, data.Value, cancellationToken), nameof(FeaturesResources.Convert_to_interpolated_string)), invocation.Span); @@ -118,7 +145,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var invocations = await document.GetRelevantNodesAsync(span, cancellationToken).ConfigureAwait(false); foreach (var invocation in invocations) { - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } @@ -127,7 +154,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // User selected a single argument of the invocation (expression / format string) instead of the whole invocation. var selectedArgument = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); var invocation = selectedArgument?.Parent?.Parent as TInvocationExpressionSyntax; - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } @@ -136,47 +163,52 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // User selected the whole argument list: string format with placeholders plus all expressions var argumentList = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); var invocation = argumentList?.Parent as TInvocationExpressionSyntax; - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } return default; } + } - TArgumentSyntax? FindValidPlaceholderArgument(TInvocationExpressionSyntax? invocation) + private static TArgumentSyntax? FindValidPlaceholderArgument( + ISyntaxFacts syntaxFacts, TInvocationExpressionSyntax? invocation, CancellationToken cancellationToken) + { + if (invocation != null) { - if (invocation != null) + // look for a string argument containing `"...{0}..."`, followed by more arguments. + var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); + for (int i = 0, n = arguments.Count - 1; i < n; i++) { - // look for a string argument containing `"...{0}..."`, followed by more arguments. - var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); - for (int i = 0, n = arguments.Count - 1; i < n; i++) + cancellationToken.ThrowIfCancellationRequested(); + + var argument = arguments[i]; + var expression = syntaxFacts.GetExpressionOfArgument(argument); + if (syntaxFacts.IsStringLiteralExpression(expression)) { - var argument = arguments[i]; - var expression = syntaxFacts.GetExpressionOfArgument(argument); - if (syntaxFacts.IsStringLiteralExpression(expression)) + var remainingArgCount = arguments.Count - i - 1; + Debug.Assert(remainingArgCount > 0); + var stringLiteralText = expression.GetFirstToken().Text; + if (stringLiteralText.Contains('{') && stringLiteralText.Contains('}')) { - var remainingArgCount = arguments.Count - i - 1; - Debug.Assert(remainingArgCount > 0); - var stringLiteralText = expression.GetFirstToken().Text; - if (stringLiteralText.Contains('{') && stringLiteralText.Contains('}')) - { - if (IsValidPlaceholderArgument(stringLiteralText, remainingArgCount)) - return (TArgumentSyntax)argument; - } + if (IsValidPlaceholderArgument(stringLiteralText, remainingArgCount)) + return argument; } } } - - return null; } + return null; + bool IsValidPlaceholderArgument(string stringLiteralText, int remainingArgCount) { // See how many arguments follow the `"...{0}..."`. We have to have a {0}, {1}, ... {N} part in the // string for each of them. Note, those could be in any order. for (var i = 0; i < remainingArgCount; i++) { + cancellationToken.ThrowIfCancellationRequested(); + var indexString = i.ToString(CultureInfo.InvariantCulture); if (!ContainsIndex(stringLiteralText, indexString)) return false; @@ -219,23 +251,79 @@ bool ContainsIndex(string stringLiteralText, string indexString) } } - private static async Task CreateInterpolatedStringAsync( + protected override async Task FixAllAsync( Document document, - TInvocationExpressionSyntax invocation, - TArgumentSyntax placeholderArgument, - IMethodSymbol invocationSymbol, - TInterpolatedStringExpressionSyntax interpolatedString, - bool shouldReplaceInvocation, + ImmutableArray fixAllSpans, + SyntaxEditor editor, + string? equivalenceKey, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService(); + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var normalizedSpans = new NormalizedTextSpanCollection(fixAllSpans); + + using var _ = ArrayBuilder.GetInstance(out var stack); + stack.Push(root); + + while (stack.TryPop(out var node)) + { + if (node is TInvocationExpressionSyntax invocation) + { + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); + if (placeholderArgument is not null && + AnalyzeInvocation(document, semanticModel, invocation, placeholderArgument, cancellationToken) is { } invocationData) + { + ReplaceInvocation(editor, semanticModel, syntaxFacts, invocationData, cancellationToken); + continue; + } + } + + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + { + var childNode = child.AsNode(); + if (normalizedSpans.IntersectsWith(childNode!.Span)) + stack.Push(childNode); + } + } + } + } + + private static async Task CreateInterpolatedStringAsync( + Document document, + InvocationData invocationData, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var syntaxGenerator = document.GetRequiredLanguageService(); + var editor = new SyntaxEditor(root, syntaxGenerator); + + ReplaceInvocation(editor, semanticModel, syntaxFacts, invocationData, cancellationToken); + + return document.WithSyntaxRoot(editor.GetChangedRoot()); + } + + private static void ReplaceInvocation( + SyntaxEditor editor, + SemanticModel semanticModel, + ISyntaxFacts syntaxFacts, + InvocationData invocationData, + CancellationToken cancellationToken) + { + var (invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation) = invocationData; + var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); var literalExpression = (TLiteralExpressionSyntax?)syntaxFacts.GetExpressionOfArgument(placeholderArgument); Contract.ThrowIfNull(literalExpression); - var syntaxGenerator = document.GetRequiredLanguageService(); + var syntaxGenerator = editor.Generator; var newInterpolatedString = InsertArgumentsIntoInterpolatedString( @@ -248,9 +336,8 @@ private static async Task CreateInterpolatedStringAsync( syntaxFacts.GetExpressionOfInvocationExpression(invocation), newInterpolatedString); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = root.ReplaceNode(invocation, replacementNode.WithTriviaFrom(invocation)); - return document.WithSyntaxRoot(newRoot); + editor.ReplaceNode(invocation, replacementNode.WithTriviaFrom(invocation)); + return; ImmutableArray GetReorderedArgumentsAfterPlaceholderArgument() { diff --git a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs index cc52cd179ee96..6776c555c1283 100644 --- a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -23,39 +24,35 @@ internal sealed class CodeAnalysisDiagnosticAnalyzerServiceFactory() : IWorkspac public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { var diagnosticAnalyzerService = workspaceServices.SolutionServices.ExportProvider.GetExports().Single().Value; - var diagnosticsRefresher = workspaceServices.SolutionServices.ExportProvider.GetExports().Single().Value; - return new CodeAnalysisDiagnosticAnalyzerService(diagnosticAnalyzerService, diagnosticsRefresher, workspaceServices.Workspace); + return new CodeAnalysisDiagnosticAnalyzerService(diagnosticAnalyzerService, workspaceServices.Workspace); } private sealed class CodeAnalysisDiagnosticAnalyzerService : ICodeAnalysisDiagnosticAnalyzerService { private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; - private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly Workspace _workspace; /// - /// List of projects that we've finished running "run code analysis" on. Cached results can now be returned for - /// these through and . + /// Mapping of projects to the diagnostics for the projects that we've finished running "run code analysis" on. + /// Cached results can now be returned for these through + /// and . /// - private readonly ConcurrentSet _analyzedProjectIds = []; + private readonly ConcurrentDictionary> _analyzedProjectToDiagnostics = []; /// /// Previously analyzed projects that we no longer want to report results for. This happens when an explicit /// build is kicked off. At that point, we want the build results to win out for a particular project. We mark - /// this project (as opposed to removing from ) as we want our LSP handler to - /// still think it should process it, as that will the cause the diagnostics to be removed when they now - /// transition to an empty list returned from this type. + /// this project (as opposed to removing from ) as we want our LSP + /// handler to still think it should process it, as that will the cause the diagnostics to be removed when they + /// now transition to an empty list returned from this type. /// private readonly ConcurrentSet _clearedProjectIds = []; public CodeAnalysisDiagnosticAnalyzerService( IDiagnosticAnalyzerService diagnosticAnalyzerService, - IDiagnosticsRefresher diagnosticsRefresher, Workspace workspace) { _diagnosticAnalyzerService = diagnosticAnalyzerService; - _diagnosticsRefresher = diagnosticsRefresher; _workspace = workspace; _workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -70,11 +67,11 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) case WorkspaceChangeKind.SolutionReloaded: case WorkspaceChangeKind.SolutionRemoved: - _analyzedProjectIds.Clear(); + _analyzedProjectToDiagnostics.Clear(); _clearedProjectIds.Clear(); // Let LSP know so that it requests up to date info, and will see our cached info disappear. - _diagnosticsRefresher.RequestWorkspaceRefresh(); + _diagnosticAnalyzerService.RequestDiagnosticRefresh(); break; } } @@ -82,13 +79,14 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) public void Clear() { // Clear the list of analyzed projects. - _clearedProjectIds.AddRange(_analyzedProjectIds); + _clearedProjectIds.AddRange(_analyzedProjectToDiagnostics.Keys); // Let LSP know so that it requests up to date info, and will see our cached info disappear. - _diagnosticsRefresher.RequestWorkspaceRefresh(); + _diagnosticAnalyzerService.RequestDiagnosticRefresh(); } - public bool HasProjectBeenAnalyzed(ProjectId projectId) => _analyzedProjectIds.Contains(projectId); + public bool HasProjectBeenAnalyzed(ProjectId projectId) + => _analyzedProjectToDiagnostics.ContainsKey(projectId); public async Task RunAnalysisAsync(Solution solution, ProjectId? projectId, Action onAfterProjectAnalyzed, CancellationToken cancellationToken) { @@ -115,11 +113,11 @@ await RoslynParallel.ForEachAsync( private async ValueTask AnalyzeProjectCoreAsync(Project project, Action onAfterProjectAnalyzed, CancellationToken cancellationToken) { // Execute force analysis for the project. - await _diagnosticAnalyzerService.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + var diagnostics = await _diagnosticAnalyzerService.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); // Add the given project to the analyzed projects list **after** analysis has completed. // We need this ordering to ensure that 'HasProjectBeenAnalyzed' call above functions correctly. - _analyzedProjectIds.Add(project.Id); + _analyzedProjectToDiagnostics[project.Id] = diagnostics; // Remove from the cleared list now that we've run a more recent "run code analysis" on this project. _clearedProjectIds.Remove(project.Id); @@ -128,47 +126,50 @@ private async ValueTask AnalyzeProjectCoreAsync(Project project, Action onAfterProjectAnalyzed(project); // Finally, invoke a workspace refresh request for LSP client to pull onto these diagnostics. - // TODO: Below call will eventually be replaced with a special workspace refresh request that skips - // pulling document diagnostics and also does not add any delay for pulling workspace diagnostics. - _diagnosticsRefresher.RequestWorkspaceRefresh(); + // + // TODO: Below call will eventually be replaced with a special workspace refresh request that skips pulling + // document diagnostics and also does not add any delay for pulling workspace diagnostics. + _diagnosticAnalyzerService.RequestDiagnosticRefresh(); } /// - /// Running code analysis on the project force computes and caches the diagnostics on the - /// DiagnosticAnalyzerService. We return these cached document diagnostics here, including both local and - /// non-local document diagnostics. + /// Running code analysis on the project force computes and caches the diagnostics in . We return these cached document diagnostics here, including both + /// local and non-local document diagnostics. /// /// /// Only returns non-suppressed diagnostics. /// - public async Task> GetLastComputedDocumentDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken) + public ImmutableArray GetLastComputedDocumentDiagnostics(DocumentId documentId) { if (_clearedProjectIds.Contains(documentId.ProjectId)) return []; - var diagnostics = await _diagnosticAnalyzerService.GetCachedDiagnosticsAsync( - _workspace, documentId.ProjectId, documentId, includeLocalDocumentDiagnostics: true, - includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); - return diagnostics.WhereAsArray(d => !d.IsSuppressed); + if (!_analyzedProjectToDiagnostics.TryGetValue(documentId.ProjectId, out var diagnostics)) + return []; + + return diagnostics.WhereAsArray(static (d, documentId) => + !d.IsSuppressed && d.DataLocation.DocumentId == documentId, documentId); } /// - /// Running code analysis on the project force computes and caches the diagnostics on the - /// DiagnosticAnalyzerService. We return these cached project diagnostics here, i.e. diagnostics with no - /// location, by excluding all local and non-local document diagnostics. + /// Running code analysis on the project force computes and caches the diagnostics in . We return these cached project diagnostics here, i.e. diagnostics + /// with no location, by excluding all local and non-local document diagnostics. /// /// /// Only returns non-suppressed diagnostics. /// - public async Task> GetLastComputedProjectDiagnosticsAsync(ProjectId projectId, CancellationToken cancellationToken) + public ImmutableArray GetLastComputedProjectDiagnostics(ProjectId projectId) { if (_clearedProjectIds.Contains(projectId)) return []; - var diagnostics = await _diagnosticAnalyzerService.GetCachedDiagnosticsAsync( - _workspace, projectId, documentId: null, includeLocalDocumentDiagnostics: false, - includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false); - return diagnostics.WhereAsArray(d => !d.IsSuppressed); + if (!_analyzedProjectToDiagnostics.TryGetValue(projectId, out var diagnostics)) + return []; + + return diagnostics.WhereAsArray(static (d, projectId) => + !d.IsSuppressed && d.ProjectId == projectId && d.DataLocation.DocumentId == null, projectId); } } } diff --git a/src/Features/Core/Portable/Diagnostics/ICodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/ICodeAnalysisDiagnosticAnalyzerService.cs index ec4a395e3e964..5b80a8e4b01bd 100644 --- a/src/Features/Core/Portable/Diagnostics/ICodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/ICodeAnalysisDiagnosticAnalyzerService.cs @@ -18,36 +18,39 @@ internal interface ICodeAnalysisDiagnosticAnalyzerService : IWorkspaceService void Clear(); /// - /// Runs all the applicable analyzers on the given project or entire solution if is null. + /// Runs all the applicable analyzers on the given project or entire solution if is + /// null. /// Task RunAnalysisAsync(Solution solution, ProjectId? projectId, Action onAfterProjectAnalyzed, CancellationToken cancellationToken); /// - /// Returns true if was invoked - /// on either the current or a prior snapshot of the project or containing solution for the given . - /// This method will keep returning true for a given project ID once any given snapshot of the project has been analyzed. - /// This changes once the solution is closed/reloaded, at which point all the projects are returned back to not analyzed state - /// and this method will return false. + /// Returns true if was + /// invoked on either the current or a prior snapshot of the project or containing solution for the given . This method will keep returning true for a given project ID once any given snapshot of the + /// project has been analyzed. This changes once the solution is closed/reloaded, at which point all the projects + /// are returned back to not analyzed state and this method will return false. /// bool HasProjectBeenAnalyzed(ProjectId projectId); /// - /// Returns analyzer diagnostics reported on the given > from the last - /// invocation on the containing project or solution. - /// The caller is expected to check prior to calling this method. + /// Returns analyzer diagnostics reported on the given > from the last invocation on the containing + /// project or solution. The caller is expected to check prior to + /// calling this method. /// /// /// Note that the returned diagnostics may not be from the latest document snapshot. /// - Task> GetLastComputedDocumentDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken); + ImmutableArray GetLastComputedDocumentDiagnostics(DocumentId documentId); /// - /// Returns analyzer diagnostics without any document location reported on the given > from the last - /// invocation on the given project or solution. - /// The caller is expected to check prior to calling this method. + /// Returns analyzer diagnostics without any document location reported on the given > + /// from the last + /// invocation on the given project or solution. The caller is expected to check prior to calling this method. /// /// /// Note that the returned diagnostics may not be from the latest project snapshot. /// - Task> GetLastComputedProjectDiagnosticsAsync(ProjectId projectId, CancellationToken cancellationToken); + ImmutableArray GetLastComputedProjectDiagnostics(ProjectId projectId); } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 406c168c5324e..3ce287befb209 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -3,20 +3,16 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Diagnostics; internal interface IDiagnosticAnalyzerService { - public IGlobalOptionService GlobalOptions { get; } - /// /// Provides and caches analyzer information. /// @@ -28,39 +24,17 @@ internal interface IDiagnosticAnalyzerService void RequestDiagnosticRefresh(); /// - /// Get diagnostics currently stored in the source. returned diagnostic might be out-of-date if solution has changed but analyzer hasn't run for the new solution. + /// Force analyzes the given project by running all applicable analyzers on the project. /// - /// Workspace for the document/project/solution to compute diagnostics for. - /// Optional project to scope the returned diagnostics. - /// Optional document to scope the returned diagnostics. - /// - /// Indicates if local document diagnostics must be returned. - /// Local diagnostics are the ones that are reported by analyzers on the same file for which the callback was received - /// and hence can be computed by analyzing a single file in isolation. - /// - /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. - /// - /// Cancellation token. - Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - - /// - /// Force analyzes the given project by running all applicable analyzers on the project and caching the reported analyzer diagnostics. - /// - Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); + Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned /// should be up-to-date with respect to the given solution. Note that for project case, this method returns - /// diagnostics from all project documents as well. Use if you want + /// diagnostics from all project documents as well. Use if you want /// to fetch only project diagnostics without source locations. /// - /// Solution to fetch the diagnostics for. - /// Optional project to scope the returned diagnostics. + /// Project to fetch the diagnostics for. /// Optional document to scope the returned diagnostics. /// Optional set of diagnostic IDs to scope the returned diagnostics. /// Option callback to filter out analyzers to execute for computing diagnostics. @@ -75,7 +49,7 @@ internal interface IDiagnosticAnalyzerService /// project must be analyzed to get the complete set of non-local document diagnostics. /// /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocumentIds, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync(Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from @@ -83,8 +57,7 @@ internal interface IDiagnosticAnalyzerService /// this method doesn't return any document diagnostics. Use to also fetch /// those. /// - /// Solution to fetch the diagnostics for. - /// Optional project to scope the returned diagnostics. + /// Project to fetch the diagnostics for. /// Optional set of diagnostic IDs to scope the returned diagnostics. /// Option callback to filter out analyzers to execute for computing diagnostics. /// @@ -93,7 +66,7 @@ internal interface IDiagnosticAnalyzerService /// Entire project must be analyzed to get the complete set of non-local diagnostics. /// /// Cancellation token. - Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetProjectDiagnosticsForIdsAsync(Project project, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// /// Return up to date diagnostics for the given span for the document @@ -105,7 +78,6 @@ internal interface IDiagnosticAnalyzerService /// Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, - bool includeCompilerDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, bool isExplicit, @@ -146,15 +118,6 @@ public static Task> GetDiagnosticsForSpanAsync(th { Func? shouldIncludeDiagnostic = diagnosticId != null ? id => id == diagnosticId : null; return service.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic, - includeCompilerDiagnostics: true, priorityProvider, - diagnosticKind, isExplicit, cancellationToken); - } - - public static Task> GetDiagnosticsForIdsAsync( - this IDiagnosticAnalyzerService service, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - { - return service.GetDiagnosticsForIdsAsync( - solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocumentIds: null, - includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + priorityProvider, diagnosticKind, isExplicit, cancellationToken); } } diff --git a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs index 5c4048ac1afbf..503a4fdd866b5 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs @@ -20,7 +20,7 @@ internal abstract class AbstractLanguageDetector( where TOptions : struct, Enum { protected readonly EmbeddedLanguageInfo Info = info; - protected readonly EmbeddedLanguageDetector Detector = new EmbeddedLanguageDetector(info, languageIdentifiers, commentDetector); + protected readonly EmbeddedLanguageDetector Detector = new(info, languageIdentifiers, commentDetector); /// /// Whether or not this is an argument to a well known api for this language (like Regex.Match or JToken.Parse). diff --git a/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs b/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs index 40607160ecfcb..ec8859bb31ff3 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs @@ -322,7 +322,8 @@ private bool IsEmbeddedLanguageStringLiteralToken( semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken) ?? semanticModel.GetDeclaredSymbol(syntaxFacts.GetIdentifierOfVariableDeclarator(variableDeclarator).GetRequiredParent(), cancellationToken); - return IsLocalConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier); + return IsLocalConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier) || + IsFieldConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier); } return false; @@ -336,46 +337,83 @@ private bool IsLocalConsumedByApiWithStringSyntaxAttribute( [NotNullWhen(true)] out string? identifier) { identifier = null; - if (symbol is not ILocalSymbol localSymbol) + if (symbol is not ILocalSymbol { Name: not "" } localSymbol) return false; var blockFacts = this.Info.BlockFacts; - var syntaxFacts = this.Info.SyntaxFacts; var block = tokenParent.AncestorsAndSelf().FirstOrDefault(blockFacts.IsExecutableBlock); if (block is null) return false; - var localName = localSymbol.Name; - if (localName == "") - return false; - // Now look at the next statements that follow for usages of this local variable. foreach (var statement in blockFacts.GetExecutableBlockStatements(block)) { - foreach (var descendent in statement.DescendantNodesAndSelf()) - { - cancellationToken.ThrowIfCancellationRequested(); + if (CheckDescendants(localSymbol, semanticModel, statement, cancellationToken, out identifier)) + return true; + } - if (!syntaxFacts.IsIdentifierName(descendent)) - continue; + return false; + } - var identifierToken = syntaxFacts.GetIdentifierOfIdentifierName(descendent); - if (identifierToken.ValueText != localName) - continue; + private bool IsFieldConsumedByApiWithStringSyntaxAttribute( + ISymbol? symbol, + SyntaxNode tokenParent, + SemanticModel semanticModel, + CancellationToken cancellationToken, + [NotNullWhen(true)] out string? identifier) + { + identifier = null; + if (symbol is not IFieldSymbol { Name: not "" } fieldSymbol) + return false; - var otherSymbol = semanticModel.GetSymbolInfo(descendent, cancellationToken).GetAnySymbol(); + var isConst = fieldSymbol.IsConst; + var isStaticReadonly = fieldSymbol.IsStatic && fieldSymbol.IsReadOnly; + if (!isConst && !isStaticReadonly) + return false; - // Only do a direct check here. We don't want to continually do indirect checks where a string literal - // is assigned to one local, assigned to another local, assigned to another local, and so on. - if (localSymbol.Equals(otherSymbol) && - IsEmbeddedLanguageStringLiteralToken_Direct(identifierToken, semanticModel, cancellationToken, out identifier)) - { - return true; - } + var syntaxFacts = this.Info.SyntaxFacts; + + var typeDeclaration = tokenParent.AncestorsAndSelf().FirstOrDefault(syntaxFacts.IsTypeDeclaration); + if (typeDeclaration is null) + return false; + + return CheckDescendants(fieldSymbol, semanticModel, typeDeclaration, cancellationToken, out identifier); + } + + private bool CheckDescendants( + ISymbol symbol, + SemanticModel semanticModel, + SyntaxNode node, + CancellationToken cancellationToken, + [NotNullWhen(true)] out string? identifier) + { + var symbolName = symbol.Name; + var syntaxFacts = this.Info.SyntaxFacts; + + foreach (var descendent in node.DescendantNodesAndSelf()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!syntaxFacts.IsIdentifierName(descendent)) + continue; + + var identifierToken = syntaxFacts.GetIdentifierOfIdentifierName(descendent); + if (identifierToken.ValueText != symbolName) + continue; + + var otherSymbol = semanticModel.GetSymbolInfo(descendent, cancellationToken).GetAnySymbol(); + + // Only do a direct check here. We don't want to continually do indirect checks where a string literal + // is assigned to one local, assigned to another local, assigned to another local, and so on. + if (symbol.Equals(otherSymbol) && + IsEmbeddedLanguageStringLiteralToken_Direct(identifierToken, semanticModel, cancellationToken, out identifier)) + { + return true; } } + identifier = null; return false; } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingIdleProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingIdleProcessor.cs index 1852b45b27be3..94ccaba167f4a 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingIdleProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingIdleProcessor.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs index f3850a0e9f046..6b4436bfaa40a 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; diff --git a/src/Features/Core/Portable/GenerateFromMembers/GenerateFromMembersHelpers.cs b/src/Features/Core/Portable/GenerateFromMembers/GenerateFromMembersHelpers.cs index 2b3d574956fac..0c627eaa1a961 100644 --- a/src/Features/Core/Portable/GenerateFromMembers/GenerateFromMembersHelpers.cs +++ b/src/Features/Core/Portable/GenerateFromMembers/GenerateFromMembersHelpers.cs @@ -70,10 +70,10 @@ private static bool IsWritableFieldOrProperty(ISymbol symbol) }; private static bool IsViableField(IFieldSymbol field) - => field.AssociatedSymbol == null; + => field is { AssociatedSymbol: null, CanBeReferencedByName: true }; private static bool IsViableProperty(IPropertySymbol property) - => property.Parameters.IsEmpty; + => property is { Parameters.IsEmpty: true, CanBeReferencedByName: true }; /// /// Returns an array of parameter symbols that correspond to selected member symbols. diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index f43c6f2ca15b6..9113e819be5c8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 90e55bc5a6137..dfbf67bc3e488 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 0cdad427b8d35..430cdf81db059 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -8,9 +8,15 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PatternMatching; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -35,16 +41,16 @@ public async Task SearchDocumentAsync( await client.TryInvokeAsync( document.Project, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), + service.SearchDocumentAndRelatedDocumentsAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; } - await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); + await SearchDocumentAndRelatedDocumentsInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static async Task SearchDocumentInCurrentProcessAsync( + public static async Task SearchDocumentAndRelatedDocumentsInCurrentProcessAsync( Document document, string searchPattern, IImmutableSet kinds, @@ -54,12 +60,85 @@ public static async Task SearchDocumentInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - var results = new ConcurrentSet(); - await SearchSingleDocumentAsync( - document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); + // In parallel, search both the document requested, and any relevant 'related documents' we find for it. For the + // original document, search the entirety of it (by passing 'null' in for the 'spans' argument). For related + // documents, only search the spans of the partial-types/inheriting-types that we find for the types in this + // starting document. + await Task.WhenAll( + SearchDocumentsInCurrentProcessAsync([(document, spans: null)]), + SearchRelatedDocumentsInCurrentProcessAsync()).ConfigureAwait(false); + + Task SearchDocumentsInCurrentProcessAsync(ImmutableArray<(Document document, NormalizedTextSpanCollection? spans)> documentAndSpans) + => ProducerConsumer.RunParallelAsync( + documentAndSpans, + produceItems: static async (documentAndSpan, onItemFound, args, cancellationToken) => + { + var (patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound) = args; + await SearchSingleDocumentAsync( + documentAndSpan.document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, + item => + { + // Ensure that the results found while searching the single document intersect the desired + // subrange of the document we're searching in. For the primary document this will always + // succeed (since we're searching the full document). But for related documents this may fail + // if the results is not in the span of any of the types in those files we're searching. + if (documentAndSpan.spans is null || documentAndSpan.spans.IntersectsWith(item.DeclaredSymbolInfo.Span)) + onItemFound(item); + }, + cancellationToken).ConfigureAwait(false); + }, + consumeItems: static (values, args, cancellationToken) => args.onItemsFound(values, default, cancellationToken), + args: (patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound), + cancellationToken); + + async Task SearchRelatedDocumentsInCurrentProcessAsync() + { + var relatedDocuments = await GetRelatedDocumentsAsync().ConfigureAwait(false); + await SearchDocumentsInCurrentProcessAsync(relatedDocuments).ConfigureAwait(false); + } - if (results.Count > 0) - await onItemsFound([.. results], default, cancellationToken).ConfigureAwait(false); + async Task> GetRelatedDocumentsAsync() + { + // For C#/VB we define 'related documents' as those containing types in the inheritance chain of types in + // the originating file (as well as all partial parts of the original and inheritance types). This way a + // user can search for symbols scoped to the 'current document' and still get results for the members found + // in partial parts. + + var solution = document.Project.Solution; + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + using var _ = ArrayBuilder.GetInstance(out var topLevelNodes); + syntaxFacts.AddTopLevelMembers(root, topLevelNodes); + + // Keep track of all of the interesting spans in each document we find. Note: we will convert this to a + // NormalizedTextSpanCollection before returning it. That way the span of an outer partial type will + // encompass the span of an inner one and we won't get duplicates for the same symbol. + var documentToTextSpans = new MultiDictionary(); + + foreach (var topLevelMember in topLevelNodes) + { + if (semanticModel.GetDeclaredSymbol(topLevelMember, cancellationToken) is not INamedTypeSymbol namedTypeSymbol) + continue; + + foreach (var type in namedTypeSymbol.GetBaseTypesAndThis()) + { + foreach (var reference in type.DeclaringSyntaxReferences) + { + var relatedDocument = solution.GetDocument(reference.SyntaxTree); + if (relatedDocument is null) + continue; + + documentToTextSpans.Add(relatedDocument, reference.Span); + } + } + } + + // Ensure we don't search the original document we were already searching. + documentToTextSpans.Remove(document); + return documentToTextSpans.SelectAsArray(kvp => (kvp.Key, new NormalizedTextSpanCollection(kvp.Value)))!; + } } public async Task SearchProjectsAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index c25d5ffd8c675..30d264e854da2 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index e50aa7fad8794..abd3cf893d7be 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -11,13 +11,14 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; internal interface IRemoteNavigateToSearchService { - ValueTask SearchDocumentAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask SearchDocumentAndRelatedDocumentsAsync(Checksum solutionChecksum, DocumentId documentId, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask SearchProjectsAsync(Checksum solutionChecksum, ImmutableArray projectIds, ImmutableArray priorityDocumentIds, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask SearchGeneratedDocumentsAsync(Checksum solutionChecksum, ImmutableArray projectIds, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs index 865997ee60408..fdb53dfa525e5 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs @@ -18,6 +18,11 @@ namespace Microsoft.CodeAnalysis.QuickInfo; internal static class QuickInfoUtilities { + /// + /// Display variable name only. + /// + private static readonly SymbolDisplayFormat s_nullableDisplayFormat = new SymbolDisplayFormat(); + public static Task CreateQuickInfoItemAsync(SolutionServices services, SemanticModel semanticModel, TextSpan span, ImmutableArray symbols, SymbolDescriptionOptions options, CancellationToken cancellationToken) => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, onTheFlyDocsInfo: null, cancellationToken); @@ -136,8 +141,8 @@ public static async Task CreateQuickInfoItemAsync( var nullableMessage = flowState switch { - NullableFlowState.MaybeNull => string.Format(FeaturesResources._0_may_be_null_here, symbol.Name), - NullableFlowState.NotNull => string.Format(FeaturesResources._0_is_not_null_here, symbol.Name), + NullableFlowState.MaybeNull => string.Format(FeaturesResources._0_may_be_null_here, symbol.ToDisplayString(s_nullableDisplayFormat)), + NullableFlowState.NotNull => string.Format(FeaturesResources._0_is_not_null_here, symbol.ToDisplayString(s_nullableDisplayFormat)), _ => null }; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractUsingSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractUsingSnippetProvider.cs new file mode 100644 index 0000000000000..f64d64c1af24b --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractUsingSnippetProvider.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; + +internal abstract class AbstractUsingSnippetProvider : AbstractStatementSnippetProvider + where TUsingStatementSyntax : SyntaxNode +{ + protected sealed override async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + { + var generator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var identifierName = NameGenerator.GenerateUniqueName("resource", + n => semanticModel.LookupSymbols(position, name: n).IsEmpty); + var statement = generator.UsingStatement(generator.IdentifierName(identifierName), statements: []); + return new TextChange(TextSpan.FromBounds(position, position), statement.NormalizeWhitespace().ToFullString()); + } +} diff --git a/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs b/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs index 27ed0aeb698ca..30b0271f8ecb1 100644 --- a/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs +++ b/src/Features/Core/Portable/Structure/Syntax/AbstractBlockStructureProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Structure; diff --git a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs index cd4fc6bc994cd..27e98ede07ae8 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs @@ -22,7 +22,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -154,7 +154,7 @@ private protected virtual IDocumentServiceProvider GetDocumentServiceProvider() protected virtual TestComposition GetComposition() => FeaturesTestCompositions.Features - .AddAssemblies(typeof(DiagnosticIncrementalAnalyzer).Assembly); + .AddAssemblies(typeof(DiagnosticAnalyzerService).Assembly); protected virtual void InitializeWorkspace(TestWorkspace workspace, TestParameters parameters) { diff --git a/src/EditorFeatures/ExternalAccess/Copilot/.editorconfig b/src/Features/ExternalAccess/Copilot/.editorconfig similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/.editorconfig rename to src/Features/ExternalAccess/Copilot/.editorconfig diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs b/src/Features/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs similarity index 92% rename from src/EditorFeatures/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs rename to src/Features/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs index 8c78970c45ca5..8bf7c472a6250 100644 --- a/src/EditorFeatures/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs +++ b/src/Features/ExternalAccess/Copilot/Analyzer/CopilotChecksumWrapper.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot; /// /// Exposed to provide an efficient checksum implementation. -/// Intended usage including cahching responses w/o retaining potentially long strings. +/// Intended usage including caching responses w/o retaining potentially long strings. /// internal sealed class CopilotChecksumWrapper { diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Analyzer/CopilotUtilities.cs b/src/Features/ExternalAccess/Copilot/Analyzer/CopilotUtilities.cs similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/Analyzer/CopilotUtilities.cs rename to src/Features/ExternalAccess/Copilot/Analyzer/CopilotUtilities.cs diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs similarity index 99% rename from src/EditorFeatures/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs rename to src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs index 1a0fed9bd383b..8bd3c115522e8 100644 --- a/src/EditorFeatures/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; diff --git a/src/EditorFeatures/ExternalAccess/Copilot/CodeMapper/ICSharpCopilotMapCodeService.cs b/src/Features/ExternalAccess/Copilot/CodeMapper/ICSharpCopilotMapCodeService.cs similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/CodeMapper/ICSharpCopilotMapCodeService.cs rename to src/Features/ExternalAccess/Copilot/CodeMapper/ICSharpCopilotMapCodeService.cs diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs similarity index 99% rename from src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs rename to src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs index a75f5e8f6f026..a48c1afcde012 100644 --- a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs @@ -161,7 +161,7 @@ public async Task> GetCachedDocumentDiagnosticsAsync( protected virtual Task> GetDiagnosticsIntersectWithSpanAsync(Document document, IReadOnlyList diagnostics, TextSpan span, CancellationToken cancellationToken) { - return Task.FromResult(diagnostics.WhereAsArray((diagnostic, _) => diagnostic.Location.SourceSpan.IntersectsWith(span), state: (object)null)); + return Task.FromResult(diagnostics.WhereAsArray((diagnostic, _) => diagnostic.Location.SourceSpan.IntersectsWith(span), state: (object?)null)); } public async Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs similarity index 65% rename from src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs rename to src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs index 8d5a5a91088af..3b2460250fd74 100644 --- a/src/EditorFeatures/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs @@ -16,49 +16,41 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.ServiceBroker; namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp; [ExportLanguageService(typeof(ICopilotCodeAnalysisService), LanguageNames.CSharp), Shared] -internal sealed partial class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnalysisService +internal sealed class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnalysisService { - private readonly Lazy _lazyExternalCopilotService; + private IExternalCSharpCopilotCodeAnalysisService AnalysisService { get; } [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpCopilotCodeAnalysisService( - [Import(AllowDefault = true)] IExternalCSharpCopilotCodeAnalysisService? externalCopilotService, - IDiagnosticsRefresher diagnosticsRefresher, - SVsServiceProvider serviceProvider, - IVsService brokeredServiceContainer + [Import] IExternalCSharpCopilotCodeAnalysisService externalCopilotService, + IDiagnosticsRefresher diagnosticsRefresher ) : base(diagnosticsRefresher) { - _lazyExternalCopilotService = new Lazy(GetExternalService, LazyThreadSafetyMode.PublicationOnly); - - IExternalCSharpCopilotCodeAnalysisService GetExternalService() - => externalCopilotService ?? new ReflectionWrapper(serviceProvider, brokeredServiceContainer); + AnalysisService = externalCopilotService; } protected override Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.AnalyzeDocumentAsync(document, span, promptTitle, cancellationToken); + => AnalysisService.AnalyzeDocumentAsync(document, span, promptTitle, cancellationToken); protected override Task> GetAvailablePromptTitlesCoreAsync(Document document, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.GetAvailablePromptTitlesAsync(document, cancellationToken); + => AnalysisService.GetAvailablePromptTitlesAsync(document, cancellationToken); protected override Task> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.GetCachedDiagnosticsAsync(document, promptTitle, cancellationToken); + => AnalysisService.GetCachedDiagnosticsAsync(document, promptTitle, cancellationToken); protected override Task IsAvailableCoreAsync(CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.IsAvailableAsync(cancellationToken); + => AnalysisService.IsAvailableAsync(cancellationToken); protected override Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.StartRefinementSessionAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken); + => AnalysisService.StartRefinementSessionAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken); protected override Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken); + => AnalysisService.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken); protected override async Task> GetDiagnosticsIntersectWithSpanAsync( Document document, IReadOnlyList diagnostics, TextSpan span, CancellationToken cancellationToken) @@ -81,5 +73,5 @@ protected override async Task> GetDiagnosticsIntersec } protected override Task IsFileExcludedCoreAsync(string filePath, CancellationToken cancellationToken) - => _lazyExternalCopilotService.Value.IsFileExcludedAsync(filePath, cancellationToken); + => AnalysisService.IsFileExcludedAsync(filePath, cancellationToken); } diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs b/src/Features/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs rename to src/Features/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Internal/RelatedDocuments/CSharpCopilotRelatedDocumentsService.cs b/src/Features/ExternalAccess/Copilot/Internal/RelatedDocuments/CSharpCopilotRelatedDocumentsService.cs similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/Internal/RelatedDocuments/CSharpCopilotRelatedDocumentsService.cs rename to src/Features/ExternalAccess/Copilot/Internal/RelatedDocuments/CSharpCopilotRelatedDocumentsService.cs diff --git a/src/EditorFeatures/ExternalAccess/Copilot/InternalAPI.Shipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Shipped.txt similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/InternalAPI.Shipped.txt rename to src/Features/ExternalAccess/Copilot/InternalAPI.Shipped.txt diff --git a/src/EditorFeatures/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/InternalAPI.Unshipped.txt rename to src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt diff --git a/src/EditorFeatures/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj b/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj similarity index 74% rename from src/EditorFeatures/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj rename to src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj index 0da1aa938d434..69e66be254d12 100644 --- a/src/EditorFeatures/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj +++ b/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj @@ -4,13 +4,14 @@ Library Microsoft.CodeAnalysis.ExternalAccess.Copilot - net472 + $(NetVSShared);net472 + true true Microsoft.CodeAnalysis.ExternalAccess.Copilot - A supporting package for Copilot Chat: + A supporting package for GitHub Copilot: https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_git/VisualStudio.Conversations @@ -21,14 +22,11 @@ --> + - - - - - + diff --git a/src/EditorFeatures/ExternalAccess/Copilot/PublicAPI.Shipped.txt b/src/Features/ExternalAccess/Copilot/PublicAPI.Shipped.txt similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/PublicAPI.Shipped.txt rename to src/Features/ExternalAccess/Copilot/PublicAPI.Shipped.txt diff --git a/src/EditorFeatures/ExternalAccess/Copilot/PublicAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/PublicAPI.Unshipped.txt similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/PublicAPI.Unshipped.txt rename to src/Features/ExternalAccess/Copilot/PublicAPI.Unshipped.txt diff --git a/src/EditorFeatures/ExternalAccess/Copilot/RelatedDocuments/ICopilotRelatedDocumentsService.cs b/src/Features/ExternalAccess/Copilot/RelatedDocuments/ICopilotRelatedDocumentsService.cs similarity index 100% rename from src/EditorFeatures/ExternalAccess/Copilot/RelatedDocuments/ICopilotRelatedDocumentsService.cs rename to src/Features/ExternalAccess/Copilot/RelatedDocuments/ICopilotRelatedDocumentsService.cs diff --git a/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj b/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj index 08a3b376f97de..1b6c512bb1cfb 100644 --- a/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj +++ b/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj @@ -55,7 +55,6 @@ - diff --git a/src/Features/Lsif/Generator/Program.cs b/src/Features/Lsif/Generator/Program.cs index f3ee22fb79c11..940a0c69698c6 100644 --- a/src/Features/Lsif/Generator/Program.cs +++ b/src/Features/Lsif/Generator/Program.cs @@ -26,17 +26,17 @@ internal static class Program { public static Task Main(string[] args) { - var solution = new Option("--solution") { Description = "input solution file" }.AcceptExistingOnly(); - var project = new Option("--project") { Description = "input project file" }.AcceptExistingOnly(); - var compilerInvocation = new Option("--compiler-invocation") { Description = "path to a .json file that contains the information for a csc/vbc invocation" }.AcceptExistingOnly(); - var binLog = new Option("--binlog") { Description = "path to a MSBuild binlog that csc/vbc invocations will be extracted from" }.AcceptExistingOnly(); - var output = new Option("--output") { Description = "file to write the LSIF output to, instead of the console", DefaultValueFactory = _ => null }; + var solution = new CliOption("--solution") { Description = "input solution file" }.AcceptExistingOnly(); + var project = new CliOption("--project") { Description = "input project file" }.AcceptExistingOnly(); + var compilerInvocation = new CliOption("--compiler-invocation") { Description = "path to a .json file that contains the information for a csc/vbc invocation" }.AcceptExistingOnly(); + var binLog = new CliOption("--binlog") { Description = "path to a MSBuild binlog that csc/vbc invocations will be extracted from" }.AcceptExistingOnly(); + var output = new CliOption("--output") { Description = "file to write the LSIF output to, instead of the console", DefaultValueFactory = _ => null }; output.AcceptLegalFilePathsOnly(); - var outputFormat = new Option("--output-format") { Description = "format of LSIF output", DefaultValueFactory = _ => LsifFormat.Line }; - var log = new Option("--log") { Description = "file to write a log to", DefaultValueFactory = _ => null }; + var outputFormat = new CliOption("--output-format") { Description = "format of LSIF output", DefaultValueFactory = _ => LsifFormat.Line }; + var log = new CliOption("--log") { Description = "file to write a log to", DefaultValueFactory = _ => null }; log.AcceptLegalFilePathsOnly(); - var generateCommand = new RootCommand("generates an LSIF file") + var generateCommand = new CliRootCommand("generates an LSIF file") { solution, project, diff --git a/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb b/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb index ed51d59f2626f..07ab30c4f4720 100644 --- a/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb +++ b/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb @@ -2,11 +2,9 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Test.Utilities Imports Roslyn.LanguageServer.Protocol Imports Roslyn.Test.Utilities -Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests @@ -52,7 +50,6 @@ using System.Linq;|}", "...")> , openDocuments:=False, composition:=TestLsifOutput.TestComposition) - Dim annotatedLocations = Await AbstractLanguageServerProtocolTests.GetAnnotatedLocationsAsync(workspace, workspace.CurrentSolution) Dim expectedRanges = annotatedLocations.SelectMany(Function(kvp) kvp.Value.Select(Function(location) CreateFoldingRange(kvp.Key, location.Range, collapsedText))).OrderByDescending(Function(range) range.StartLine).ToArray() diff --git a/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj b/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj index 0a9d98d4bcde6..ac2f33f5aabe0 100644 --- a/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj +++ b/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj @@ -10,6 +10,7 @@ + diff --git a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index f61982ada6dcc..22e23e97cd922 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1594,15 +1594,8 @@ public async Task HasChanges() EndDebuggingSession(debuggingSession); } - public enum DocumentKind - { - Source, - Additional, - AnalyzerConfig, - } - [Theory, CombinatorialData] - public async Task HasChanges_Documents(DocumentKind documentKind) + public async Task HasChanges_Documents(TextDocumentKind documentKind) { using var _ = CreateWorkspace(out var solution, out var service); var log = new TraceLog("Test"); @@ -1617,15 +1610,15 @@ public async Task HasChanges_Documents(DocumentKind documentKind) { switch (documentKind) { - case DocumentKind.Source: + case TextDocumentKind.Document: context.AddSource("Generated.cs", context.Compilation.SyntaxTrees.SingleOrDefault(t => t.FilePath.EndsWith("X.cs"))?.ToString() ?? "none"); break; - case DocumentKind.Additional: + case TextDocumentKind.AdditionalDocument: context.AddSource("Generated.cs", context.AdditionalFiles.FirstOrDefault()?.GetText().ToString() ?? "none"); break; - case DocumentKind.AnalyzerConfig: + case TextDocumentKind.AnalyzerConfigDocument: var syntaxTree = context.Compilation.SyntaxTrees.Single(t => t.FilePath.EndsWith("A.cs")); var content = context.AnalyzerConfigOptions.GetOptions(syntaxTree).TryGetValue("x", out var optionValue) ? optionValue.ToString() : "none"; @@ -1663,9 +1656,9 @@ public async Task HasChanges_Documents(DocumentKind documentKind) var documentId = DocumentId.CreateNewId(projectId); solution = documentKind switch { - DocumentKind.Source => solution.AddDocument(documentId, "X", CreateText("xxx"), filePath: pathX), - DocumentKind.Additional => solution.AddAdditionalDocument(documentId, "X", CreateText("xxx"), filePath: pathX), - DocumentKind.AnalyzerConfig => solution.AddAnalyzerConfigDocument(documentId, "X", GetAnalyzerConfigText([("x", "1")]), filePath: pathX), + TextDocumentKind.Document => solution.AddDocument(documentId, "X", CreateText("xxx"), filePath: pathX), + TextDocumentKind.AdditionalDocument => solution.AddAdditionalDocument(documentId, "X", CreateText("xxx"), filePath: pathX), + TextDocumentKind.AnalyzerConfigDocument => solution.AddAnalyzerConfigDocument(documentId, "X", GetAnalyzerConfigText([("x", "1")]), filePath: pathX), _ => throw ExceptionUtilities.Unreachable(), }; Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); @@ -1683,7 +1676,7 @@ public async Task HasChanges_Documents(DocumentKind documentKind) var diagnostics = new ArrayBuilder(); await EditSession.PopulateChangedAndAddedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None); Assert.Empty(diagnostics); - AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id)); + AssertEx.Equal(documentKind == TextDocumentKind.Document ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id)); Assert.Equal(1, generatorExecutionCount); @@ -1696,9 +1689,9 @@ public async Task HasChanges_Documents(DocumentKind documentKind) solution = documentKind switch { - DocumentKind.Source => solution.WithDocumentText(documentId, CreateText("xxx")), - DocumentKind.Additional => solution.WithAdditionalDocumentText(documentId, CreateText("xxx")), - DocumentKind.AnalyzerConfig => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "1")])), + TextDocumentKind.Document => solution.WithDocumentText(documentId, CreateText("xxx")), + TextDocumentKind.AdditionalDocument => solution.WithAdditionalDocumentText(documentId, CreateText("xxx")), + TextDocumentKind.AnalyzerConfigDocument => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "1")])), _ => throw ExceptionUtilities.Unreachable(), }; Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); @@ -1707,7 +1700,7 @@ public async Task HasChanges_Documents(DocumentKind documentKind) Assert.Equal(0, generatorExecutionCount); // source generator infrastructure compares content and reuses state if it matches (SourceGeneratedDocumentState.WithUpdatedGeneratedContent): - AssertEx.Equal(documentKind == DocumentKind.Source ? new[] { documentId } : [], + AssertEx.Equal(documentKind == TextDocumentKind.Document ? new[] { documentId } : [], await EditSession.GetChangedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None)); await EditSession.PopulateChangedAndAddedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None); @@ -1724,20 +1717,20 @@ public async Task HasChanges_Documents(DocumentKind documentKind) oldSolution = solution; solution = documentKind switch { - DocumentKind.Source => solution.WithDocumentText(documentId, CreateText("xxx-changed")), - DocumentKind.Additional => solution.WithAdditionalDocumentText(documentId, CreateText("xxx-changed")), - DocumentKind.AnalyzerConfig => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "2")])), + TextDocumentKind.Document => solution.WithDocumentText(documentId, CreateText("xxx-changed")), + TextDocumentKind.AdditionalDocument => solution.WithAdditionalDocumentText(documentId, CreateText("xxx-changed")), + TextDocumentKind.AnalyzerConfigDocument => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "2")])), _ => throw ExceptionUtilities.Unreachable(), }; Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, pathX, CancellationToken.None)); - AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId], + AssertEx.Equal(documentKind == TextDocumentKind.Document ? [documentId, generatedDocumentId] : [generatedDocumentId], await EditSession.GetChangedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None)); await EditSession.PopulateChangedAndAddedDocumentsAsync(log, oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None); Assert.Empty(diagnostics); - AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id)); + AssertEx.Equal(documentKind == TextDocumentKind.Document ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id)); Assert.Equal(1, generatorExecutionCount); @@ -1749,9 +1742,9 @@ public async Task HasChanges_Documents(DocumentKind documentKind) oldSolution = solution; solution = documentKind switch { - DocumentKind.Source => solution.RemoveDocument(documentId), - DocumentKind.Additional => solution.RemoveAdditionalDocument(documentId), - DocumentKind.AnalyzerConfig => solution.RemoveAnalyzerConfigDocument(documentId), + TextDocumentKind.Document => solution.RemoveDocument(documentId), + TextDocumentKind.AdditionalDocument => solution.RemoveAdditionalDocument(documentId), + TextDocumentKind.AnalyzerConfigDocument => solution.RemoveAnalyzerConfigDocument(documentId), _ => throw ExceptionUtilities.Unreachable(), }; Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None)); diff --git a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj index 2aa8f604ab261..766f43532bf19 100644 --- a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj +++ b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj @@ -10,10 +10,6 @@ true true - - - - @@ -37,6 +33,7 @@ + diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/MoveType/VisualBasicMoveTypeService.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/MoveType/VisualBasicMoveTypeService.vb index 6c2b0e8efd6a6..4ff27cb564f00 100644 --- a/src/Features/VisualBasic/Portable/CodeRefactorings/MoveType/VisualBasicMoveTypeService.vb +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/MoveType/VisualBasicMoveTypeService.vb @@ -20,6 +20,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.MoveType Public Sub New() End Sub + Protected Overrides Function GetSymbolNameAndArity(syntax As TypeBlockSyntax) As (name As String, arity As Integer) + Dim statement = syntax.BlockStatement + Return (statement.Identifier.ValueText, If(statement.TypeParameterList?.Parameters.Count, 0)) + End Function + Protected Overrides Function IsMemberDeclaration(syntaxNode As SyntaxNode) As Boolean Return TypeOf syntaxNode Is MethodBaseSyntax OrElse TypeOf syntaxNode Is MethodBlockBaseSyntax End Function diff --git a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb index 8ef9b016e58ea..0bbc2ea490efe 100644 --- a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb @@ -10,7 +10,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertToInterpolatedString - Partial Friend Class VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider + Partial Friend NotInheritable Class VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider Inherits AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider(Of ExpressionSyntax, LiteralExpressionSyntax, diff --git a/src/Features/VisualBasic/Portable/NavigateTo/VisualBasicNavigateToSearchService.vb b/src/Features/VisualBasic/Portable/NavigateTo/VisualBasicNavigateToSearchService.vb index 6e2901f52da73..5ba3943fdc734 100644 --- a/src/Features/VisualBasic/Portable/NavigateTo/VisualBasicNavigateToSearchService.vb +++ b/src/Features/VisualBasic/Portable/NavigateTo/VisualBasicNavigateToSearchService.vb @@ -8,7 +8,7 @@ Imports Microsoft.CodeAnalysis.NavigateTo Namespace Microsoft.CodeAnalysis.VisualBasic.NavigateTo - Friend Class VisualBasicNavigateToSearchService + Friend NotInheritable Class VisualBasicNavigateToSearchService Inherits AbstractNavigateToSearchService diff --git a/src/Features/VisualBasicTest/CodeActions/AbstractVisualBasicCodeActionTest_NoEditor.vb b/src/Features/VisualBasicTest/CodeActions/AbstractVisualBasicCodeActionTest_NoEditor.vb index 8a077a9b0adc5..3373cd2cba8f3 100644 --- a/src/Features/VisualBasicTest/CodeActions/AbstractVisualBasicCodeActionTest_NoEditor.vb +++ b/src/Features/VisualBasicTest/CodeActions/AbstractVisualBasicCodeActionTest_NoEditor.vb @@ -26,7 +26,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings Return input.Replace("\n", vbCrLf) End Function - Protected Overloads Async Function TestAsync(initialMarkup As XElement, expected As XElement, Optional index As Integer = 0, Optional parseOptions As ParseOptions = Nothing) As Threading.Tasks.Task + Protected Overloads Async Function TestAsync(initialMarkup As XElement, expected As XElement, Optional index As Integer = 0, Optional parseOptions As ParseOptions = Nothing) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Dim expectedStr = expected.ConvertTestSourceTag() If parseOptions Is Nothing Then @@ -36,7 +36,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings End If End Function - Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Threading.Tasks.Task + Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Await MyBase.TestMissingAsync(initialMarkupStr, diff --git a/src/Features/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest_NoEditor.vb b/src/Features/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest_NoEditor.vb index da635844e6056..279b58ab310ff 100644 --- a/src/Features/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest_NoEditor.vb +++ b/src/Features/VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest_NoEditor.vb @@ -28,7 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics End Function Friend Overloads Async Function TestAsync( - initialMarkup As XElement, expected As XElement, Optional index As Integer = 0) As Threading.Tasks.Task + initialMarkup As XElement, expected As XElement, Optional index As Integer = 0) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Dim expectedStr = expected.ConvertTestSourceTag() @@ -37,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics index:=index) End Function - Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Threading.Tasks.Task + Protected Overloads Async Function TestMissingAsync(initialMarkup As XElement) As Task Dim initialMarkupStr = initialMarkup.ConvertTestSourceTag() Await MyBase.TestMissingAsync(initialMarkupStr, New TestParameters(parseOptions:=Nothing, compilationOptions:=_compilationOptions)) diff --git a/src/Features/VisualBasicTest/GenerateVariable/GenerateVariableTests.vb b/src/Features/VisualBasicTest/GenerateVariable/GenerateVariableTests.vb index bd95346f49e3a..ff58f72d0e3ca 100644 --- a/src/Features/VisualBasicTest/GenerateVariable/GenerateVariableTests.vb +++ b/src/Features/VisualBasicTest/GenerateVariable/GenerateVariableTests.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.Threading.Tasks Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics @@ -22,7 +23,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Genera End Function - Public Async Function TestGenerateSimpleProperty() As Threading.Tasks.Task + Public Async Function TestGenerateSimpleProperty() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -39,7 +40,7 @@ End Module") End Function - Public Async Function TestGenerateSimpleField() As Threading.Tasks.Task + Public Async Function TestGenerateSimpleField() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -57,7 +58,7 @@ index:=1) End Function - Public Async Function TestGenerateReadOnlyField() As Threading.Tasks.Task + Public Async Function TestGenerateReadOnlyField() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -75,7 +76,7 @@ index:=2) End Function - Public Async Function TestGenerateFromAssignment() As Threading.Tasks.Task + Public Async Function TestGenerateFromAssignment() As Task Await TestInRegularAndScriptAsync( "Class C Shared Sub M @@ -138,7 +139,7 @@ index:=1) End Function - Public Async Function TestGenerateProtectedSharedFieldIntoBase() As Threading.Tasks.Task + Public Async Function TestGenerateProtectedSharedFieldIntoBase() As Task Await TestInRegularAndScriptAsync( "Class Base End Class @@ -173,7 +174,7 @@ End Class") End Function - Public Async Function TestGenerateFriendAccessibilityForField() As Threading.Tasks.Task + Public Async Function TestGenerateFriendAccessibilityForField() As Task Await TestInRegularAndScriptAsync( "Class A End Class @@ -194,7 +195,7 @@ index:=1) End Function - Public Async Function TestGeneratePropertyOnInterface1() As Threading.Tasks.Task + Public Async Function TestGeneratePropertyOnInterface1() As Task Await TestInRegularAndScriptAsync( "Interface IGoo End Interface @@ -216,7 +217,7 @@ End Class") End Function - Public Async Function TestGeneratePropertyOnInterface2() As Threading.Tasks.Task + Public Async Function TestGeneratePropertyOnInterface2() As Task Await TestInRegularAndScriptAsync( "Interface IGoo End Interface @@ -238,7 +239,7 @@ End Class", index:=1) End Function - Public Async Function TestGeneratePropertyIntoModule() As Threading.Tasks.Task + Public Async Function TestGeneratePropertyIntoModule() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -263,7 +264,7 @@ End Class") End Function - Public Async Function TestFieldPropertyIntoModule() As Threading.Tasks.Task + Public Async Function TestFieldPropertyIntoModule() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -947,7 +948,7 @@ End Class") End Function - Public Async Function TestFieldWithAnonymousTypeType() As Threading.Tasks.Task + Public Async Function TestFieldWithAnonymousTypeType() As Task Await TestInRegularAndScriptAsync( "Imports System Imports System.Collections.Generic @@ -2712,7 +2713,7 @@ end class", index:=1) End Function - Public Async Function TestGenerateSimplePropertyInSyncLock() As Threading.Tasks.Task + Public Async Function TestGenerateSimplePropertyInSyncLock() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -2731,7 +2732,7 @@ End Module") End Function - Public Async Function TestGenerateSimpleFieldInSyncLock() As Threading.Tasks.Task + Public Async Function TestGenerateSimpleFieldInSyncLock() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) @@ -2751,7 +2752,7 @@ index:=1) End Function - Public Async Function TestGenerateReadOnlyFieldInSyncLock() As Threading.Tasks.Task + Public Async Function TestGenerateReadOnlyFieldInSyncLock() As Task Await TestInRegularAndScriptAsync( "Module Program Sub Main(args As String()) diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj index d06fa7a7bee4a..778289fdaf640 100644 --- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj +++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj @@ -36,20 +36,11 @@ - - - - - - - - - @@ -61,6 +52,8 @@ + + @@ -76,4 +69,7 @@ + + + \ No newline at end of file diff --git a/src/Interactive/HostProcess/x86/InteractiveHost32.csproj b/src/Interactive/HostProcess/x86/InteractiveHost32.csproj index 70e32290b8987..3f5e9f3862b8e 100644 --- a/src/Interactive/HostProcess/x86/InteractiveHost32.csproj +++ b/src/Interactive/HostProcess/x86/InteractiveHost32.csproj @@ -9,6 +9,7 @@ true true true + win-x86 diff --git a/src/LanguageServer/ExternalAccess/Copilot/Handler/AbstractCopilotLspServiceDocumentRequestHandler.cs b/src/LanguageServer/ExternalAccess/Copilot/Handler/AbstractCopilotLspServiceDocumentRequestHandler.cs new file mode 100644 index 0000000000000..a9965a35987e7 --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/Handler/AbstractCopilotLspServiceDocumentRequestHandler.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CommonLanguageServerProtocol.Framework; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +/// +internal abstract class AbstractCopilotLspServiceDocumentRequestHandler : ILspServiceDocumentRequestHandler +{ + public abstract Task HandleRequestAsync(TRequest request, CopilotRequestContext context, CancellationToken cancellationToken); + public abstract Uri GetTextDocumentUri(TRequest request); + + bool IMethodHandler.MutatesSolutionState => false; + bool ISolutionRequiredHandler.RequiresLSPSolution => true; + + TextDocumentIdentifier ITextDocumentIdentifierHandler.GetTextDocumentIdentifier(TRequest request) + => new() { Uri = GetTextDocumentUri(request) }; + + Task IRequestHandler.HandleRequestAsync(TRequest request, RequestContext context, CancellationToken cancellationToken) + => HandleRequestAsync(request, new CopilotRequestContext(context), cancellationToken); +} diff --git a/src/LanguageServer/ExternalAccess/Copilot/Handler/CopilotRequestContext.cs b/src/LanguageServer/ExternalAccess/Copilot/Handler/CopilotRequestContext.cs new file mode 100644 index 0000000000000..ee5a6713c96bf --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/Handler/CopilotRequestContext.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.LanguageServer.Handler; + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +/// +/// Context for requests handled by +/// +internal readonly struct CopilotRequestContext(RequestContext context) +{ + /// + /// The solution state that the request should operate on. + /// + public Solution Solution => context.Solution ?? throw new InvalidOperationException(); + + /// + public Document? Document => context.Document; + + public T GetRequiredService() where T : class => context.GetRequiredService(); +} diff --git a/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Shipped.txt b/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Shipped.txt new file mode 100644 index 0000000000000..7dc5c58110bfa --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Unshipped.txt new file mode 100644 index 0000000000000..49ab443409011 --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -0,0 +1,20 @@ +#nullable enable +abstract Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.AbstractCopilotLspServiceDocumentRequestHandler.GetTextDocumentUri(TRequest request) -> System.Uri! +abstract Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.AbstractCopilotLspServiceDocumentRequestHandler.HandleRequestAsync(TRequest request, Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.AbstractCopilotLspServiceDocumentRequestHandler +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.AbstractCopilotLspServiceDocumentRequestHandler.AbstractCopilotLspServiceDocumentRequestHandler() -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotLspServices +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotLspServices.CopilotLspServices() -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotLspServices.CopilotLspServices(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices) -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotLspServices.GetService() -> T? +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotMethodAttribute +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotMethodAttribute.CopilotMethodAttribute(string! method) -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext.CopilotRequestContext() -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext.CopilotRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext.Document.get -> Microsoft.CodeAnalysis.Document? +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext.GetRequiredService() -> T! +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.CopilotRequestContext.Solution.get -> Microsoft.CodeAnalysis.Solution! +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.ExportCopilotStatelessLspServiceAttribute +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.ExportCopilotStatelessLspServiceAttribute.ExportCopilotStatelessLspServiceAttribute(System.Type! type) -> void +Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.ICopilotLspService \ No newline at end of file diff --git a/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotLspServices.cs b/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotLspServices.cs new file mode 100644 index 0000000000000..b3b4030c31b38 --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotLspServices.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +internal readonly struct CopilotLspServices(LspServices lspServices) +{ + public T? GetService() where T : notnull + => lspServices.GetService(); +} diff --git a/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotMethodAttribute.cs b/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotMethodAttribute.cs new file mode 100644 index 0000000000000..024493d3aafee --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/LspServices/CopilotMethodAttribute.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.LanguageServer.Handler; + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +/// +/// An attribute which identifies the method which a Copilot request handler like +/// implements. +/// +internal sealed class CopilotMethodAttribute(string method) : MethodAttribute(method); diff --git a/src/LanguageServer/ExternalAccess/Copilot/LspServices/ExportCopilotStatelessLspServiceAttribute.cs b/src/LanguageServer/ExternalAccess/Copilot/LspServices/ExportCopilotStatelessLspServiceAttribute.cs new file mode 100644 index 0000000000000..c07744f489501 --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/LspServices/ExportCopilotStatelessLspServiceAttribute.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.LanguageServer.Handler; + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +/// +internal sealed class ExportCopilotStatelessLspServiceAttribute(Type type) : + ExportCSharpVisualBasicStatelessLspServiceAttribute(type, WellKnownLspServerKinds.Any); diff --git a/src/LanguageServer/ExternalAccess/Copilot/LspServices/ICopilotLspService.cs b/src/LanguageServer/ExternalAccess/Copilot/LspServices/ICopilotLspService.cs new file mode 100644 index 0000000000000..593eba8b3fbe3 --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/LspServices/ICopilotLspService.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot; + +/// +/// Interface to mark a Copilot LSP service. +/// +internal interface ICopilotLspService : ILspService +{ +} diff --git a/src/LanguageServer/ExternalAccess/Copilot/Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.csproj b/src/LanguageServer/ExternalAccess/Copilot/Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.csproj new file mode 100644 index 0000000000000..00cc2c94fe23a --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot.csproj @@ -0,0 +1,34 @@ + + + + + Library + Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot + netstandard2.0 + + true + Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot + + A supporting package for the GitHub Copilot in Visual Studio Code. + + + + + + + + + + + + + + + + + + + + diff --git a/src/LanguageServer/ExternalAccess/Copilot/PublicAPI.Shipped.txt b/src/LanguageServer/ExternalAccess/Copilot/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/LanguageServer/ExternalAccess/Copilot/PublicAPI.Unshipped.txt b/src/LanguageServer/ExternalAccess/Copilot/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/src/LanguageServer/ExternalAccess/Copilot/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs index 1cc57db6f508f..e7f9da1ce42ea 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs @@ -86,7 +86,7 @@ public async Task CanFindCodeBaseWhenReservedCharactersInPath(string reservedCha var dllPath = GenerateDll(reservedCharacter, out var assemblyName); - await using var testServer = await TestLspServer.CreateAsync(new Roslyn.LanguageServer.Protocol.ClientCapabilities(), TestOutputLogger, MefCacheDirectory.Path, includeDevKitComponents: true, [dllPath]); + await using var testServer = await TestLspServer.CreateAsync(new Roslyn.LanguageServer.Protocol.ClientCapabilities(), LoggerFactory, MefCacheDirectory.Path, includeDevKitComponents: true, [dllPath]); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var assembly = Assert.Single(assemblies, a => a.GetName().Name == assemblyName); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs index 900cf1a438c89..8cacd0b6bbb71 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs @@ -28,7 +28,7 @@ public class LspFileChangeWatcherTests(ITestOutputHelper testOutputHelper) [Fact] public async Task LspFileWatcherNotSupportedWithoutClientSupport() { - await using var testLspServer = await TestLspServer.CreateAsync(new ClientCapabilities(), TestOutputLogger, MefCacheDirectory.Path); + await using var testLspServer = await TestLspServer.CreateAsync(new ClientCapabilities(), LoggerFactory, MefCacheDirectory.Path); Assert.False(LspFileChangeWatcher.SupportsLanguageServerHost(testLspServer.LanguageServerHost)); } @@ -36,7 +36,7 @@ public async Task LspFileWatcherNotSupportedWithoutClientSupport() [Fact] public async Task LspFileWatcherSupportedWithClientSupport() { - await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, TestOutputLogger, MefCacheDirectory.Path); + await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, LoggerFactory, MefCacheDirectory.Path); Assert.True(LspFileChangeWatcher.SupportsLanguageServerHost(testLspServer.LanguageServerHost)); } @@ -46,7 +46,7 @@ public async Task CreatingDirectoryWatchRequestsDirectoryWatch() { AsynchronousOperationListenerProvider.Enable(enable: true); - await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, TestOutputLogger, MefCacheDirectory.Path); + await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, LoggerFactory, MefCacheDirectory.Path); var lspFileChangeWatcher = new LspFileChangeWatcher( testLspServer.LanguageServerHost, testLspServer.ExportProvider.GetExportedValue()); @@ -76,7 +76,7 @@ public async Task CreatingFileWatchRequestsFileWatch() { AsynchronousOperationListenerProvider.Enable(enable: true); - await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, TestOutputLogger, MefCacheDirectory.Path); + await using var testLspServer = await TestLspServer.CreateAsync(_clientCapabilitiesWithFileWatcherSupport, LoggerFactory, MefCacheDirectory.Path); var lspFileChangeWatcher = new LspFileChangeWatcher( testLspServer.LanguageServerHost, testLspServer.ExportProvider.GetExportedValue()); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj index 1a0f088e2eed0..0f61561851842 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj @@ -15,6 +15,11 @@ + + + + + diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs index 641514d4eff5d..0a678d0b4fd44 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs @@ -15,7 +15,7 @@ public sealed class TelemetryReporterTests(ITestOutputHelper testOutputHelper) private async Task CreateReporterAsync() { var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( - TestOutputLogger.Factory, + LoggerFactory, includeDevKitComponents: true, MefCacheDirectory.Path, [], diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.TestLspClient.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.TestLspClient.cs index d3dcb2f7f6b91..b55dfe6160fdd 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.TestLspClient.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.TestLspClient.cs @@ -26,7 +26,7 @@ internal sealed class TestLspClient : ILspClient, IAsyncDisposable private readonly Process _process; private readonly Dictionary _documents; private readonly Dictionary> _locations; - private readonly TestOutputLogger _logger; + private readonly ILoggerFactory _loggerFactory; private readonly JsonRpc _clientRpc; @@ -37,7 +37,7 @@ internal static async Task CreateAsync( string extensionLogsPath, bool includeDevKitComponents, bool debugLsp, - TestOutputLogger logger, + ILoggerFactory loggerFactory, Dictionary? documents = null, Dictionary>? locations = null) { @@ -47,7 +47,7 @@ internal static async Task CreateAsync( var process = Process.Start(processStartInfo); Assert.NotNull(process); - var lspClient = new TestLspClient(process, pipeName, documents ?? [], locations ?? [], logger); + var lspClient = new TestLspClient(process, pipeName, documents ?? [], locations ?? [], loggerFactory); // We've subscribed to Disconnected, but if the process crashed before that point we might have not seen it if (process.HasExited) @@ -124,11 +124,11 @@ static ProcessStartInfo CreateLspStartInfo(string pipeName, string extensionLogs internal ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("Initialize has not been called"); - private TestLspClient(Process process, string pipeName, Dictionary documents, Dictionary> locations, TestOutputLogger logger) + private TestLspClient(Process process, string pipeName, Dictionary documents, Dictionary> locations, ILoggerFactory loggerFactory) { _documents = documents; _locations = locations; - _logger = logger; + _loggerFactory = loggerFactory; _process = process; _process.EnableRaisingEvents = true; @@ -160,6 +160,8 @@ private TestLspClient(Process process, string pipeName, Dictionary GetMessageLogger(string method) { + var logger = _loggerFactory.CreateLogger($"LSP {method}"); + return (int type, string message) => { var logLevel = (MessageType)type switch @@ -171,19 +173,20 @@ Action GetMessageLogger(string method) MessageType.Debug => LogLevel.Debug, _ => LogLevel.Trace, }; - _logger.Log(logLevel, "[LSP {Method}] {Message}", method, message); + + logger.Log(logLevel, message); }; } } private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) { - _logger.LogInformation("[LSP STDOUT] {Data}", e.Data); + _loggerFactory.CreateLogger("LSP STDOUT").LogInformation(e.Data); } private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) { - _logger.LogCritical("[LSP STDERR] {Data}", e.Data); + _loggerFactory.CreateLogger("LSP STDERR").LogInformation(e.Data); } public async Task ExecuteRequestAsync(string methodName, TRequestType request, CancellationToken cancellationToken) where TRequestType : class @@ -249,9 +252,11 @@ public async ValueTask DisposeAsync() if (Interlocked.CompareExchange(ref _disposed, value: 1, comparand: 0) != 0) return; + var logger = _loggerFactory.CreateLogger("Shutdown"); + if (!_process.HasExited) { - _logger.LogTrace("Sending a Shutdown request to the LSP."); + logger.LogTrace("Sending a Shutdown request to the LSP."); await _clientRpc.InvokeAsync(Methods.ShutdownName); await _clientRpc.NotifyAsync(Methods.ExitName); @@ -262,7 +267,7 @@ public async ValueTask DisposeAsync() _clientRpc.Dispose(); _process.Dispose(); - _logger.LogTrace("Process shut down."); + logger.LogTrace("Process shut down."); } } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.cs index 72fd371bb1504..971033a4d5d12 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerClientTests.cs @@ -8,6 +8,8 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.Extensions.Logging; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -18,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; public abstract partial class AbstractLanguageServerClientTests(ITestOutputHelper testOutputHelper) : IDisposable { - protected TestOutputLogger TestOutputLogger => new(testOutputHelper); + protected ILoggerFactory LoggerFactory => new LoggerFactory([new TestOutputLoggerProvider(testOutputHelper)]); protected TempRoot TempRoot => new(); protected TempDirectory ExtensionLogsDirectory => TempRoot.CreateDirectory(); @@ -69,7 +71,7 @@ await File.WriteAllTextAsync(projectPath, $""" ExtensionLogsDirectory.Path, includeDevKitComponents, debugLsp, - TestOutputLogger, + LoggerFactory, documents: files, locations: annotatedLocations); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs index b543d17c01d00..ae12838c614a2 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs @@ -4,6 +4,8 @@ using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; using Nerdbank.Streams; using Roslyn.LanguageServer.Protocol; @@ -14,20 +16,20 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; public abstract class AbstractLanguageServerHostTests : IDisposable { - protected TestOutputLogger TestOutputLogger { get; } + protected ILoggerFactory LoggerFactory { get; } protected TempRoot TempRoot { get; } protected TempDirectory MefCacheDirectory { get; } protected AbstractLanguageServerHostTests(ITestOutputHelper testOutputHelper) { - TestOutputLogger = new TestOutputLogger(testOutputHelper); + LoggerFactory = new LoggerFactory([new TestOutputLoggerProvider(testOutputHelper)]); TempRoot = new(); MefCacheDirectory = TempRoot.CreateDirectory(); } protected Task CreateLanguageServerAsync(bool includeDevKitComponents = true) { - return TestLspServer.CreateAsync(new ClientCapabilities(), TestOutputLogger, MefCacheDirectory.Path, includeDevKitComponents); + return TestLspServer.CreateAsync(new ClientCapabilities(), LoggerFactory, MefCacheDirectory.Path, includeDevKitComponents); } public void Dispose() @@ -42,11 +44,11 @@ protected sealed class TestLspServer : ILspClient, IAsyncDisposable private ServerCapabilities? _serverCapabilities; - internal static async Task CreateAsync(ClientCapabilities clientCapabilities, TestOutputLogger logger, string cacheDirectory, bool includeDevKitComponents = true, string[]? extensionPaths = null) + internal static async Task CreateAsync(ClientCapabilities clientCapabilities, ILoggerFactory loggerFactory, string cacheDirectory, bool includeDevKitComponents = true, string[]? extensionPaths = null) { var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( - logger.Factory, includeDevKitComponents, cacheDirectory, extensionPaths, out var _, out var assemblyLoader); - var testLspServer = new TestLspServer(exportProvider, logger, assemblyLoader); + loggerFactory, includeDevKitComponents, cacheDirectory, extensionPaths, out var _, out var assemblyLoader); + var testLspServer = new TestLspServer(exportProvider, loggerFactory, assemblyLoader); var initializeResponse = await testLspServer.ExecuteRequestAsync(Methods.InitializeName, new InitializeParams { Capabilities = clientCapabilities }, CancellationToken.None); Assert.NotNull(initializeResponse?.Capabilities); testLspServer._serverCapabilities = initializeResponse!.Capabilities; @@ -61,12 +63,12 @@ internal static async Task CreateAsync(ClientCapabilities clientC internal ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("Initialize has not been called"); - private TestLspServer(ExportProvider exportProvider, TestOutputLogger logger, IAssemblyLoader assemblyLoader) + private TestLspServer(ExportProvider exportProvider, ILoggerFactory loggerFactory, IAssemblyLoader assemblyLoader) { - var typeRefResolver = new ExtensionTypeRefResolver(assemblyLoader, logger.Factory); + var typeRefResolver = new ExtensionTypeRefResolver(assemblyLoader, loggerFactory); var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - LanguageServerHost = new LanguageServerHost(serverStream, serverStream, exportProvider, logger, typeRefResolver); + LanguageServerHost = new LanguageServerHost(serverStream, serverStream, exportProvider, loggerFactory.CreateLogger(), typeRefResolver); var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, messageFormatter)) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLoggerProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLoggerProvider.cs deleted file mode 100644 index 3ebda77fd10db..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLoggerProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Logging; - -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; - -internal class TestLoggerProvider : ILoggerProvider -{ - private readonly ILogger _testLogger; - public TestLoggerProvider(ILogger testLogger) - { - _testLogger = testLogger; - } - - public ILogger CreateLogger(string categoryName) - { - return _testLogger; - } - - public void Dispose() - { - } -} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestOutputLogger.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestOutputLogger.cs deleted file mode 100644 index ea77589b3d0f7..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestOutputLogger.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Logging; -using Xunit.Abstractions; - -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; - -public class TestOutputLogger : ILogger -{ - private readonly ITestOutputHelper _testOutputHelper; - public readonly ILoggerFactory Factory; - - public TestOutputLogger(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - Factory = new LoggerFactory([new TestLoggerProvider(this)]); - } - - public IDisposable BeginScope(TState state) where TState : notnull - { - return new NoOpDisposable(); - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - _testOutputHelper.WriteLine($"[{DateTime.UtcNow:hh:mm:ss.fff}][{logLevel}]{formatter(state, exception)}"); - } - - private sealed class NoOpDisposable : IDisposable - { - public void Dispose() - { - } - } -} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index 781feabf5d46e..9a6376252f3cf 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs index 9822e7020a5ab..f62d05679f3a6 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs @@ -43,7 +43,8 @@ public LanguageServerWorkspaceFactory( var razorSourceGenerator = serverConfigurationFactory?.ServerConfiguration?.RazorSourceGenerator; ProjectSystemHostInfo = new ProjectSystemHostInfo( DynamicFileInfoProviders: [.. dynamicFileInfoProviders], - new HostDiagnosticAnalyzerProvider(razorSourceGenerator)); + new HostDiagnosticAnalyzerProvider(razorSourceGenerator), + AnalyzerAssemblyRedirectors: []); TargetFrameworkManager = projectTargetFrameworkManager; } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs index b064fb31132c3..214a518c3ec98 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj index a76305b4a2923..b2238ec387021 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj @@ -54,6 +54,7 @@ win-x64;win-arm64;linux-x64;linux-arm64;linux-musl-x64;linux-musl-arm64;osx-x64;osx-arm64 true + + + + + + + + $(PublishReadyToRunCrossgen2ExtraArgs);--opt-cross-module:*;--non-local-generics-module:"$(TargetName)" + + + @@ -80,6 +93,7 @@ + diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index f5602d249fbd2..5c80f55e9dde6 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Logging; using Microsoft.CodeAnalysis.LanguageServer.Services; using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions; +using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Roslyn.Utilities; @@ -102,7 +103,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation // LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to // 'automatic' for now. - var globalOptionService = exportProvider.GetExportedValue(); + var globalOptionService = exportProvider.GetExportedValue(); globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); // The log file directory passed to us by VSCode might not exist yet, though its parent directory is guaranteed to exist. @@ -180,79 +181,79 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation } } -static RootCommand CreateCommandLineParser() +static CliRootCommand CreateCommandLineParser() { - var debugOption = new Option("--debug") + var debugOption = new CliOption("--debug") { Description = "Flag indicating if the debugger should be launched on startup.", Required = false, DefaultValueFactory = _ => false, }; - var brokeredServicePipeNameOption = new Option("--brokeredServicePipeName") + var brokeredServicePipeNameOption = new CliOption("--brokeredServicePipeName") { Description = "The name of the pipe used to connect to a remote process (if one exists).", Required = false, }; - var logLevelOption = new Option("--logLevel") + var logLevelOption = new CliOption("--logLevel") { Description = "The minimum log verbosity.", Required = true, }; - var starredCompletionsPathOption = new Option("--starredCompletionComponentPath") + var starredCompletionsPathOption = new CliOption("--starredCompletionComponentPath") { Description = "The location of the starred completion component (if one exists).", Required = false, }; - var telemetryLevelOption = new Option("--telemetryLevel") + var telemetryLevelOption = new CliOption("--telemetryLevel") { Description = "Telemetry level, Defaults to 'off'. Example values: 'all', 'crash', 'error', or 'off'.", Required = false, }; - var extensionLogDirectoryOption = new Option("--extensionLogDirectory") + var extensionLogDirectoryOption = new CliOption("--extensionLogDirectory") { Description = "The directory where we should write log files to", Required = true, }; - var sessionIdOption = new Option("--sessionId") + var sessionIdOption = new CliOption("--sessionId") { Description = "Session Id to use for telemetry", Required = false }; - var extensionAssemblyPathsOption = new Option("--extension") + var extensionAssemblyPathsOption = new CliOption("--extension") { Description = "Full paths of extension assemblies to load (optional).", Required = false }; - var devKitDependencyPathOption = new Option("--devKitDependencyPath") + var devKitDependencyPathOption = new CliOption("--devKitDependencyPath") { Description = "Full path to the Roslyn dependency used with DevKit (optional).", Required = false }; - var razorSourceGeneratorOption = new Option("--razorSourceGenerator") + var razorSourceGeneratorOption = new CliOption("--razorSourceGenerator") { Description = "Full path to the Razor source generator (optional).", Required = false }; - var razorDesignTimePathOption = new Option("--razorDesignTimePath") + var razorDesignTimePathOption = new CliOption("--razorDesignTimePath") { Description = "Full path to the Razor design time target path (optional).", Required = false }; - var serverPipeNameOption = new Option("--pipe") + var serverPipeNameOption = new CliOption("--pipe") { Description = "The name of the pipe the server will connect to.", Required = false }; - var useStdIoOption = new Option("--stdio") + var useStdIoOption = new CliOption("--stdio") { Description = "Use stdio for communication with the client.", Required = false, @@ -260,7 +261,7 @@ static RootCommand CreateCommandLineParser() }; - var rootCommand = new RootCommand() + var rootCommand = new CliRootCommand() { debugOption, brokeredServicePipeNameOption, diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj index 1e3c944072843..6b419a03f3fcf 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj @@ -21,7 +21,5 @@ - - - + diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index a375878201a7e..071edc9649eaa 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -27,6 +27,7 @@ internal abstract class AbstractLanguageServer /// private readonly Lazy> _queue; private readonly Lazy _lspServices; + private readonly Lazy _handlerProvider; public bool IsInitialized { get; private set; } @@ -67,6 +68,13 @@ protected AbstractLanguageServer( _jsonRpc.Disconnected += JsonRpc_Disconnected; _lspServices = new Lazy(() => ConstructLspServices()); _queue = new Lazy>(() => ConstructRequestExecutionQueue()); + _handlerProvider = new Lazy(() => + { + var lspServices = _lspServices.Value; + var handlerProvider = new HandlerProvider(lspServices, TypeRefResolver); + SetupRequestDispatcher(handlerProvider); + return handlerProvider; + }); } /// @@ -89,10 +97,7 @@ protected virtual AbstractHandlerProvider HandlerProvider { get { - var lspServices = _lspServices.Value; - var handlerProvider = new HandlerProvider(lspServices, TypeRefResolver); - SetupRequestDispatcher(handlerProvider); - return handlerProvider; + return _handlerProvider.Value; } } diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj index ce4ed2a289ace..432407fd46ce9 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj @@ -25,10 +25,6 @@ - - - - @@ -43,4 +39,5 @@ contentFiles\cs\$(TargetFramework)\ + diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems index ef9c653643d6e..dd8785f1ae8f5 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems @@ -41,6 +41,5 @@ - \ No newline at end of file diff --git a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 04e23cd3ff207..e2b06c30dfbf6 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics { public class TestDiagnosticAnalyzerDriver { - private readonly DiagnosticAnalyzerService _diagnosticAnalyzerService; + private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; private readonly bool _includeSuppressedDiagnostics; private readonly bool _includeNonLocalDocumentDiagnostics; @@ -29,8 +29,7 @@ public TestDiagnosticAnalyzerDriver(Workspace workspace, bool includeSuppressedD { var mefServices = workspace.Services.SolutionServices.ExportProvider; - _diagnosticAnalyzerService = Assert.IsType(mefServices.GetExportedValue()); - _diagnosticAnalyzerService.CreateIncrementalAnalyzer(workspace); + _diagnosticAnalyzerService = mefServices.GetExportedValue(); _includeSuppressedDiagnostics = includeSuppressedDiagnostics; _includeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; } @@ -49,7 +48,7 @@ private async Task> GetDiagnosticsAsync( { var text = await document.GetTextAsync().ConfigureAwait(false); var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, + project, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); dxs = dxs.WhereAsArray(d => _includeSuppressedDiagnostics || !d.IsSuppressed); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( @@ -63,7 +62,7 @@ filterSpan is null if (getProjectDiagnostics) { var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, + project, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); dxs = dxs.WhereAsArray(d => _includeSuppressedDiagnostics || !d.IsSuppressed); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs similarity index 100% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs similarity index 82% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 29071e6c24179..db01815f0ba8f 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -15,16 +15,12 @@ using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Test; -using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; -using Microsoft.CodeAnalysis.LanguageServer.UnitTests; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -51,10 +47,6 @@ protected AbstractLanguageServerProtocolTests(ITestOutputHelper? testOutputHelpe TestOutputLspLogger = testOutputHelper != null ? new TestOutputLspLogger(testOutputHelper) : NoOpLspLogger.Instance; } - protected static readonly TestComposition EditorFeaturesLspComposition = EditorTestCompositions.LanguageServerProtocolEditorFeatures - .AddParts(typeof(TestDocumentTrackingService)) - .AddParts(typeof(TestWorkspaceRegistrationService)); - protected static readonly TestComposition FeaturesLspComposition = LspTestCompositions.LanguageServerProtocol .AddParts(typeof(TestDocumentTrackingService)) .AddParts(typeof(TestWorkspaceRegistrationService)); @@ -108,7 +100,7 @@ private protected class OrderLocations : Comparer public override int Compare(LSP.Location? x, LSP.Location? y) => CompareLocations(x, y); } - protected virtual TestComposition Composition => EditorFeaturesLspComposition; + protected virtual TestComposition Composition => FeaturesLspComposition; private protected virtual TestAnalyzerReferenceByLanguage CreateTestAnalyzersReference() => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); @@ -180,7 +172,7 @@ private protected static string ApplyTextEdits(LSP.TextEdit[]? edits, SourceText internal static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kind, string name, LSP.Location location, Glyph glyph, string? containerName = null) { - var imageId = glyph.GetImageId(); + var (guid, id) = glyph.GetVsImageData(); #pragma warning disable CS0618 // SymbolInformation is obsolete, need to switch to DocumentSymbol/WorkspaceSymbol var info = new LSP.VSSymbolInformation() @@ -188,7 +180,7 @@ internal static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kin Kind = kind, Name = name, Location = location, - Icon = new LSP.VSImageId { Guid = imageId.Guid, Id = imageId.Id }, + Icon = new LSP.VSImageId { Guid = guid, Id = id }, }; if (containerName != null) @@ -279,7 +271,7 @@ private protected static LSP.CompletionParams CreateCompletionParams( }; if (tags != null) - item.Icon = tags.ToImmutableArray().GetFirstGlyph().GetImageElement().ToLSPImageElement(); + item.Icon = new(tags.ToImmutableArray().GetFirstGlyph().ToLSPImageId()); if (commitCharacters != null) item.CommitCharacters = [.. commitCharacters.Value.Select(c => c.ToString())]; @@ -321,13 +313,13 @@ private protected Task CreateTestLspServerAsync( var workspace = CreateWorkspace(lspOptions, workspaceKind: null, mutatingLspWorkspace, composition); workspace.InitializeDocuments( - TestWorkspace.CreateWorkspaceElement(languageName, files: markups, fileContainingFolders: lspOptions.DocumentFileContainingFolders, sourceGeneratedFiles: lspOptions.SourceGeneratedMarkups, commonReferences: commonReferences), + LspTestWorkspace.CreateWorkspaceElement(languageName, files: markups, fileContainingFolders: lspOptions.DocumentFileContainingFolders, sourceGeneratedFiles: lspOptions.SourceGeneratedMarkups, commonReferences: commonReferences), openDocuments: false); return CreateTestLspServerAsync(workspace, lspOptions, languageName); } - private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace, InitializationOptions initializationOptions, string languageName) + private async Task CreateTestLspServerAsync(LspTestWorkspace workspace, InitializationOptions initializationOptions, string languageName) { var solution = workspace.CurrentSolution; @@ -374,10 +366,10 @@ private protected async Task CreateXmlTestLspServerAsync( return await TestLspServer.CreateAsync(workspace, lspOptions, TestOutputLspLogger); } - internal EditorTestWorkspace CreateWorkspace( + internal LspTestWorkspace CreateWorkspace( InitializationOptions? options, string? workspaceKind, bool mutatingLspWorkspace, TestComposition? composition = null) { - var workspace = new EditorTestWorkspace( + var workspace = new LspTestWorkspace( composition ?? Composition, workspaceKind, configurationOptions: new WorkspaceConfigurationOptions(ValidateCompilationTrackerStates: true), supportsLspMutation: mutatingLspWorkspace); options?.OptionUpdater?.Invoke(workspace.GetService()); @@ -390,13 +382,19 @@ internal EditorTestWorkspace CreateWorkspace( /// Waits for the async operations on the workspace to complete. /// This ensures that events like workspace registration / workspace changes are processed by the time we exit this method. /// - protected static async Task WaitForWorkspaceOperationsAsync(EditorTestWorkspace workspace) + protected static async Task WaitForWorkspaceOperationsAsync(TestWorkspace workspace) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var workspaceWaiter = GetWorkspaceWaiter(workspace); await workspaceWaiter.ExpeditedWaitAsync(); } - private static IAsynchronousOperationWaiter GetWorkspaceWaiter(EditorTestWorkspace workspace) + private static IAsynchronousOperationWaiter GetWorkspaceWaiter(TestWorkspace workspace) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var operations = workspace.ExportProvider.GetExportedValue(); return operations.GetWaiter(FeatureAttribute.Workspace); @@ -419,7 +417,7 @@ protected static void AddMappedDocument(Workspace workspace, string markup) workspace.TryApplyChanges(newSolution); } - protected static async Task AddGeneratorAsync(ISourceGenerator generator, EditorTestWorkspace workspace) + protected static async Task AddGeneratorAsync(ISourceGenerator generator, LspTestWorkspace workspace) { var analyzerReference = new TestGeneratorReference(generator); @@ -433,7 +431,7 @@ protected static async Task AddGeneratorAsync(ISourceGenerato return analyzerReference; } - protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, EditorTestWorkspace workspace) + protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, LspTestWorkspace workspace) { var solution = workspace.CurrentSolution .Projects.Single() @@ -444,7 +442,10 @@ protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, Ed await WaitForWorkspaceOperationsAsync(workspace); } - internal static async Task>> GetAnnotatedLocationsAsync(EditorTestWorkspace workspace, Solution solution) + internal static async Task>> GetAnnotatedLocationsAsync(TestWorkspace workspace, Solution solution) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var locations = new Dictionary>(); foreach (var testDocument in workspace.Documents) @@ -531,44 +532,71 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U } }; - internal sealed class TestLspServer : IAsyncDisposable + /// + /// Implementation of + /// using the workspace. + /// + internal sealed class TestLspServer : AbstractTestLspServer { - public readonly EditorTestWorkspace TestWorkspace; - private readonly Dictionary> _locations; + public TestLspServer(LspTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions initializationOptions, AbstractLspLogger logger) + : base(testWorkspace, locations, initializationOptions, logger) + { + } + + public static async Task CreateAsync(LspTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new TestLspServer(testWorkspace, locations, initializationOptions, logger); + await server.InitializeAsync(); + return server; + } + } + + internal abstract class AbstractTestLspServer : IAsyncDisposable + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution + where TWorkspace : TestWorkspace + { + public readonly TWorkspace TestWorkspace; private readonly JsonRpc _clientRpc; + private readonly Dictionary> _locations; private readonly ICodeAnalysisDiagnosticAnalyzerService _codeAnalysisService; - - private readonly RoslynLanguageServer LanguageServer; + private readonly InitializationOptions _initializationOptions; + private readonly Lazy _languageServer; public LSP.ClientCapabilities ClientCapabilities { get; } - private TestLspServer( - EditorTestWorkspace testWorkspace, + public AbstractTestLspServer( + TWorkspace testWorkspace, Dictionary> locations, - LSP.ClientCapabilities clientCapabilities, - RoslynLanguageServer target, - Stream clientStream, - object? clientTarget = null, - IJsonRpcMessageFormatter? clientMessageFormatter = null) + InitializationOptions initializationOptions, + AbstractLspLogger logger) { TestWorkspace = testWorkspace; - ClientCapabilities = clientCapabilities; + _initializationOptions = initializationOptions; _locations = locations; _codeAnalysisService = testWorkspace.Services.GetRequiredService(); - LanguageServer = target; + ClientCapabilities = initializationOptions.ClientCapabilities; - clientMessageFormatter ??= RoslynLanguageServer.CreateJsonMessageFormatter(); + var clientMessageFormatter = initializationOptions.ClientMessageFormatter ?? RoslynLanguageServer.CreateJsonMessageFormatter(); - _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, clientMessageFormatter), clientTarget) + var (clientStream, serverStream) = FullDuplexStream.CreatePair(); + + _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, clientMessageFormatter), initializationOptions.ClientTarget) { ExceptionStrategy = ExceptionProcessing.ISerializable, }; - // Workspace listener events do not run in tests, so we manually register the lsp misc workspace. - TestWorkspace.GetService().Register(GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); + _languageServer = new(() => + { + var server = CreateLanguageServer(serverStream, serverStream, _initializationOptions.ServerKind, logger); + + InitializeClientRpc(); + return server; + }); - InitializeClientRpc(); } private void InitializeClientRpc() @@ -579,56 +607,39 @@ private void InitializeClientRpc() Assert.False(workspaceWaiter.HasPendingWork); } - internal static async Task CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + internal async Task InitializeAsync() { // Important: We must wait for workspace creation operations to finish. // Otherwise we could have a race where workspace change events triggered by creation are changing the state // created by the initial test steps. This can interfere with the expected test state. - await WaitForWorkspaceOperationsAsync(testWorkspace); + await WaitForWorkspaceOperationsAsync(TestWorkspace); - var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + // Initialize the language server + _ = _languageServer.Value; - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var languageServer = CreateLanguageServer(serverStream, serverStream, testWorkspace, initializationOptions.ServerKind, logger); - - var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, languageServer, clientStream, initializationOptions.ClientTarget, initializationOptions.ClientMessageFormatter); + // Workspace listener events do not run in tests, so we manually register the lsp misc workspace. + // This must be done after the language server is created in order to access the misc workspace off of the LSP workspace manager. + TestWorkspace.GetService().Register(GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); - if (initializationOptions.CallInitialize) + if (_initializationOptions.CallInitialize) { - await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams + await this.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams { - Capabilities = initializationOptions.ClientCapabilities, - Locale = initializationOptions.Locale, + Capabilities = _initializationOptions.ClientCapabilities, + Locale = _initializationOptions.Locale, }, CancellationToken.None); } - if (initializationOptions.CallInitialized) + if (_initializationOptions.CallInitialized) { - await server.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); + await this.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); } - - return server; - } - - internal static async Task CreateAsync(EditorTestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, RoslynLanguageServer target, Stream clientStream) - { - var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); - var server = new TestLspServer(testWorkspace, locations, clientCapabilities, target, clientStream); - - await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams - { - Capabilities = clientCapabilities, - }, CancellationToken.None); - - await server.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); - - return server; } - private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) + protected virtual RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) { - var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var factory = workspace.ExportProvider.GetExportedValue(); + var capabilitiesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + var factory = TestWorkspace.ExportProvider.GetExportedValue(); var jsonMessageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)) @@ -636,7 +647,7 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str ExceptionStrategy = ExceptionProcessing.ISerializable, }; - var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializerOptions, capabilitiesProvider, serverKind, logger, workspace.Services.HostServices); + var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializerOptions, capabilitiesProvider, serverKind, logger, TestWorkspace.Services.HostServices); jsonRpc.StartListening(); return languageServer; @@ -698,6 +709,33 @@ public async Task OpenDocumentAsync(Uri documentUri, string? text = null, string await ExecuteRequestAsync(LSP.Methods.TextDocumentDidOpenName, didOpenParams, CancellationToken.None); } + /// + /// Opens a document in the workspace only, and waits for workspace operations. + /// Use if the document should be opened in LSP"/> + /// + public async Task OpenDocumentInWorkspaceAsync(DocumentId documentId, bool openAllLinkedDocuments, SourceText? text = null) + { + var document = TestWorkspace.CurrentSolution.GetDocument(documentId); + Contract.ThrowIfNull(document); + + text ??= await TestWorkspace.CurrentSolution.GetDocument(documentId)!.GetTextAsync(CancellationToken.None); + + List linkedDocuments = [documentId]; + if (openAllLinkedDocuments) + { + linkedDocuments.AddRange(document.GetLinkedDocumentIds()); + } + + var container = new TestStaticSourceTextContainer(text); + + foreach (var documentIdToOpen in linkedDocuments) + { + TestWorkspace.OnDocumentOpened(documentIdToOpen, container); + } + + await WaitForWorkspaceOperationsAsync(TestWorkspace); + } + public Task ReplaceTextAsync(Uri documentUri, params (LSP.Range Range, string Text)[] changes) { var didChangeParams = CreateDidChangeTextDocumentParams( @@ -741,7 +779,7 @@ public async Task ExitTestServerAsync() // of the request itself since it will throw a ConnectionLostException. // Instead we wait for the server's exit task to be completed. await _clientRpc.NotifyAsync(LSP.Methods.ExitName).ConfigureAwait(false); - await LanguageServer.WaitForExitAsync().ConfigureAwait(false); + await _languageServer.Value.WaitForExitAsync().ConfigureAwait(false); } public IList GetLocations(string locationName) => _locations[locationName]; @@ -772,15 +810,21 @@ internal async Task WaitForDiagnosticsAsync() await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync(); } - internal RequestExecutionQueue.TestAccessor? GetQueueAccessor() => LanguageServer.GetTestAccessor().GetQueueAccessor(); + internal async Task WaitForSourceGeneratorsAsync() + { + var operations = TestWorkspace.ExportProvider.GetExportedValue(); + await operations.WaitAllAsync(TestWorkspace, [FeatureAttribute.Workspace, FeatureAttribute.SourceGenerators]); + } + + internal RequestExecutionQueue.TestAccessor? GetQueueAccessor() => _languageServer.Value.GetTestAccessor().GetQueueAccessor(); internal LspWorkspaceManager.TestAccessor GetManagerAccessor() => GetRequiredLspService().GetTestAccessor(); internal LspWorkspaceManager GetManager() => GetRequiredLspService(); - internal AbstractLanguageServer.TestAccessor GetServerAccessor() => LanguageServer.GetTestAccessor(); + internal AbstractLanguageServer.TestAccessor GetServerAccessor() => _languageServer.Value.GetTestAccessor(); - internal T GetRequiredLspService() where T : class, ILspService => LanguageServer.GetTestAccessor().GetRequiredLspService(); + internal T GetRequiredLspService() where T : class, ILspService => _languageServer.Value.GetTestAccessor().GetRequiredLspService(); internal ImmutableArray GetTrackedTexts() => [.. GetManager().GetTrackedLspText().Values.Select(v => v.Text)]; @@ -794,14 +838,14 @@ public async ValueTask DisposeAsync() // Some tests will manually call shutdown and exit, so attempting to call this during dispose // will fail as the server's jsonrpc instance will be disposed of. - if (!LanguageServer.GetTestAccessor().HasShutdownStarted()) + if (!_languageServer.Value.GetTestAccessor().HasShutdownStarted()) { await ShutdownTestServerAsync(); await ExitTestServerAsync(); } // Wait for all the exit notifications to run to completion. - await LanguageServer.WaitForExitAsync(); + await _languageServer.Value.WaitForExitAsync(); TestWorkspace.Dispose(); _clientRpc.Dispose(); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs similarity index 97% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs index 30726fc3aaa9b..0b09f4041b37e 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs @@ -22,7 +22,7 @@ public abstract class AbstractLspBuildOnlyDiagnosticsTests [Fact] public void TestExportedDiagnosticIds() { - var attribute = this.LspBuildOnlyDiagnosticsType.GetCustomAttribute(); + var attribute = this.LspBuildOnlyDiagnosticsType.GetCustomAttribute()!; var actualDiagnosticCodes = attribute.BuildOnlyDiagnostics; var missing = ExpectedDiagnosticCodes.Except(actualDiagnosticCodes).OrderBy(k => k).ToList(); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs similarity index 95% rename from src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs index c8ba5e8bdbd99..8b7dfa57e4db3 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs @@ -3,10 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CommonLanguageServerProtocol.Framework; using Xunit.Abstractions; -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; +namespace Roslyn.Test.Utilities; internal sealed class TestOutputLspLogger : AbstractLspLogger, ILspService { diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs similarity index 50% rename from src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs index f83897f0c6930..db393fa2ad034 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs @@ -10,15 +10,9 @@ namespace Roslyn.Test.Utilities; -public abstract partial class AbstractLanguageServerProtocolTests +[Export(typeof(LspWorkspaceRegistrationService)), Shared, PartNotDiscoverable] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class TestWorkspaceRegistrationService() : LspWorkspaceRegistrationService { - [Export(typeof(LspWorkspaceRegistrationService)), Shared, PartNotDiscoverable] - internal class TestWorkspaceRegistrationService : LspWorkspaceRegistrationService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestWorkspaceRegistrationService() - { - } - } } diff --git a/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj b/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj index 81266477a3850..91693179de616 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj +++ b/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj @@ -10,15 +10,16 @@ true true - - - - + + + + + \ No newline at end of file diff --git a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs index 96830c508dd90..5695456e45a13 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs +++ b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs b/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs new file mode 100644 index 0000000000000..40b0f2bd9a904 --- /dev/null +++ b/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Test.Utilities; + +/// +/// Various tests often need a source text container to simulate workspace OnDocumentOpened calls. +/// +internal class TestStaticSourceTextContainer(SourceText text) : SourceTextContainer +{ + public override SourceText CurrentText => text; + + public override event EventHandler TextChanged + { + add { } + remove { } + } +} diff --git a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs index 9d084b8076d00..5c30d938549e2 100644 --- a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs +++ b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs @@ -107,7 +107,7 @@ private static LSP.VSDiagnostic CreateLspDiagnostic( Code = diagnosticData.Id, CodeDescription = ProtocolConversions.HelpLinkToCodeDescription(diagnosticData.GetValidHelpLinkUri()), Message = diagnosticData.Message, - Severity = ConvertDiagnosticSeverity(diagnosticData.Severity, supportsVisualStudioExtensions), + Severity = ConvertDiagnosticSeverity(diagnosticData.Severity), Tags = ConvertTags(diagnosticData, isLiveSource, potentialDuplicate), DiagnosticRank = ConvertRank(diagnosticData), Range = GetRange(diagnosticData.DataLocation) @@ -207,14 +207,13 @@ private static bool ShouldIncludeHiddenDiagnostic(DiagnosticData diagnosticData, return null; } - private static LSP.DiagnosticSeverity ConvertDiagnosticSeverity(DiagnosticSeverity severity, bool supportsVisualStudioExtensions) + private static LSP.DiagnosticSeverity ConvertDiagnosticSeverity(DiagnosticSeverity severity) => severity switch { // Hidden is translated in ConvertTags to pass along appropriate _ms tags // that will hide the item in a client that knows about those tags. DiagnosticSeverity.Hidden => LSP.DiagnosticSeverity.Hint, - // VSCode shows information diagnostics as blue squiggles, and hint diagnostics as 3 dots. We prefer the latter rendering so we return hint diagnostics in vscode. - DiagnosticSeverity.Info => supportsVisualStudioExtensions ? LSP.DiagnosticSeverity.Information : LSP.DiagnosticSeverity.Hint, + DiagnosticSeverity.Info => LSP.DiagnosticSeverity.Information, DiagnosticSeverity.Warning => LSP.DiagnosticSeverity.Warning, DiagnosticSeverity.Error => LSP.DiagnosticSeverity.Error, _ => throw ExceptionUtilities.UnexpectedValue(severity), diff --git a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs index 6f546fa963e47..16ee27f71f880 100644 --- a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs +++ b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs @@ -202,7 +202,6 @@ private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( // Compute diagnostics for everything that is not an IDE analyzer var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), - includeCompilerDiagnostics: true, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs index 55bde7157b61e..46e18b82466e5 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs +++ b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs @@ -12,74 +12,72 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.CodeFixes +namespace Microsoft.CodeAnalysis.CodeFixes; + +internal partial class CodeFixService { - internal partial class CodeFixService + private sealed class FixAllDiagnosticProvider : FixAllContext.SpanBasedDiagnosticProvider { - private sealed class FixAllDiagnosticProvider : FixAllContext.SpanBasedDiagnosticProvider + private readonly IDiagnosticAnalyzerService _diagnosticService; + private readonly ImmutableHashSet? _diagnosticIds; + private readonly bool _includeSuppressedDiagnostics; + + public FixAllDiagnosticProvider(IDiagnosticAnalyzerService diagnosticService, ImmutableHashSet diagnosticIds) { - private readonly IDiagnosticAnalyzerService _diagnosticService; - private readonly ImmutableHashSet? _diagnosticIds; - private readonly bool _includeSuppressedDiagnostics; + _diagnosticService = diagnosticService; - public FixAllDiagnosticProvider(IDiagnosticAnalyzerService diagnosticService, ImmutableHashSet diagnosticIds) + // When computing FixAll for unnecessary pragma suppression diagnostic, + // we need to include suppressed diagnostics, as well as reported compiler and analyzer diagnostics. + // A null value for '_diagnosticIds' ensures the latter. + if (diagnosticIds.Contains(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId)) { - _diagnosticService = diagnosticService; - - // When computing FixAll for unnecessary pragma suppression diagnostic, - // we need to include suppressed diagnostics, as well as reported compiler and analyzer diagnostics. - // A null value for '_diagnosticIds' ensures the latter. - if (diagnosticIds.Contains(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId)) - { - _diagnosticIds = null; - _includeSuppressedDiagnostics = true; - } - else - { - _diagnosticIds = diagnosticIds; - _includeSuppressedDiagnostics = false; - } + _diagnosticIds = null; + _includeSuppressedDiagnostics = true; } - - private ImmutableArray Filter(ImmutableArray diagnostics) - => diagnostics.WhereAsArray(d => _includeSuppressedDiagnostics || !d.IsSuppressed); - - public override async Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) + else { - var solution = document.Project.Solution; - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( - solution, projectId: null, document.Id, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); - Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); - return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); + _diagnosticIds = diagnosticIds; + _includeSuppressedDiagnostics = false; } + } - public override async Task> GetDocumentSpanDiagnosticsAsync(Document document, TextSpan fixAllSpan, CancellationToken cancellationToken) - { - bool shouldIncludeDiagnostic(string id) => _diagnosticIds == null || _diagnosticIds.Contains(id); - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForSpanAsync( - document, fixAllSpan, shouldIncludeDiagnostic, includeCompilerDiagnostics: true, - priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false)); - Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); - return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); - } + private ImmutableArray Filter(ImmutableArray diagnostics) + => diagnostics.WhereAsArray(d => _includeSuppressedDiagnostics || !d.IsSuppressed); - public override async Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) - { - // Get all diagnostics for the entire project, including document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId: null, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); - return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); - } + public override async Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) + { + var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + document.Project, document.Id, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); + Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); + return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); + } - public override async Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) - { - // Get all no-location diagnostics for the project, doesn't include document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetProjectDiagnosticsForIdsAsync( - project.Solution, project.Id, _diagnosticIds, shouldIncludeAnalyzer: null, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); - Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId == null)); - return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); - } + public override async Task> GetDocumentSpanDiagnosticsAsync(Document document, TextSpan fixAllSpan, CancellationToken cancellationToken) + { + bool shouldIncludeDiagnostic(string id) => _diagnosticIds == null || _diagnosticIds.Contains(id); + var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForSpanAsync( + document, fixAllSpan, shouldIncludeDiagnostic, + priorityProvider: new DefaultCodeActionRequestPriorityProvider(), + DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false)); + Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); + return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); + } + + public override async Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + // Get all diagnostics for the entire project, including document diagnostics. + var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + project, documentId: null, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); + return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); + } + + public override async Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + // Get all no-location diagnostics for the project, doesn't include document diagnostics. + var diagnostics = Filter(await _diagnosticService.GetProjectDiagnosticsForIdsAsync( + project, _diagnosticIds, shouldIncludeAnalyzer: null, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); + Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId == null)); + return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index c78d9bd421e96..d4e282074b310 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -109,7 +109,7 @@ public CodeFixService( { allDiagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), - includeCompilerDiagnostics: true, priorityProvider, DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); + priorityProvider, DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); // NOTE(cyrusn): We do not include suppressed diagnostics here as they are effectively hidden from the // user in the editor. As far as the user is concerned, there is no squiggle for it and no lightbulb @@ -199,7 +199,7 @@ public async IAsyncEnumerable StreamFixesAsync( { diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), - includeCompilerDiagnostics: true, priorityProvider, DiagnosticKind.All, isExplicit: true, cancellationToken).ConfigureAwait(false); + priorityProvider, DiagnosticKind.All, isExplicit: true, cancellationToken).ConfigureAwait(false); if (!includeSuppressionFixes) diagnostics = diagnostics.WhereAsArray(d => !d.IsSuppressed); } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index fbef8d15b4f76..79595a195b782 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Runtime.CompilerServices; @@ -11,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -21,8 +19,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics { - [Export(typeof(IDiagnosticAnalyzerService))] - [Shared] + [Export(typeof(IDiagnosticAnalyzerService)), Shared] internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { private static readonly Option2 s_crashOnAnalyzerException = new("dotnet_crash_on_analyzer_exception", defaultValue: false); @@ -30,7 +27,7 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; private set; } public IAsynchronousOperationListener Listener { get; } - public IGlobalOptionService GlobalOptions { get; } + private IGlobalOptionService GlobalOptions { get; } private readonly ConditionalWeakTable _map = new(); private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; @@ -59,6 +56,9 @@ public DiagnosticAnalyzerService( }); } + public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) + => project.GetDependentVersionAsync(cancellationToken); + public bool CrashOnAnalyzerException => GlobalOptions.GetOption(s_crashOnAnalyzerException); @@ -71,13 +71,12 @@ public static bool IsGlobalOptionAffectingDiagnostics(IOption2 option) option == s_crashOnAnalyzerException; public void RequestDiagnosticRefresh() - => _diagnosticsRefresher?.RequestWorkspaceRefresh(); + => _diagnosticsRefresher.RequestWorkspaceRefresh(); public async Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, - bool includeCompilerDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKinds, bool isExplicit, @@ -90,36 +89,40 @@ public async Task> GetDiagnosticsForSpanAsync( priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); return await analyzer.GetDiagnosticsForSpanAsync( - document, range, shouldIncludeDiagnostic, includeCompilerDiagnostics, - priorityProvider, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); - } - - public Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(workspace); - return analyzer.GetCachedDiagnosticsAsync(workspace.CurrentSolution, projectId, documentId, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); } - public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + return await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); + return analyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, + Project project, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetProjectDiagnosticsForIdsAsync(solution, projectId, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); + var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); + return analyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); + } + + public TestAccessor GetTestAccessor() + => new(this); + + public readonly struct TestAccessor(DiagnosticAnalyzerService service) + { + public Task> GetAnalyzersAsync(Project project, CancellationToken cancellationToken) + { + return service.CreateIncrementalAnalyzer(project.Solution.Workspace).GetAnalyzersForTestingPurposesOnlyAsync(project, cancellationToken); + } } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index 99d9760bc40ca..9a0b3fa16fdb9 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -2,26 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; -using Microsoft.CodeAnalysis.Host.Mef; - namespace Microsoft.CodeAnalysis.Diagnostics; internal partial class DiagnosticAnalyzerService { - public DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) + private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) { return _map.GetValue(workspace, _createIncrementalAnalyzer); } - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspace workspace) { // subscribe to active context changed event for new workspace workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; - return new DiagnosticIncrementalAnalyzer(this, workspace, AnalyzerInfoCache); + return new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); } private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs e) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs index eecd062437cec..ef96728725dcb 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs @@ -5,12 +5,8 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; @@ -128,84 +124,6 @@ static string GetLanguageSpecificId(string? language, string noLanguageId, strin language: language); } - public static async Task CreateCompilationWithAnalyzersAsync( - Project project, - ImmutableArray projectAnalyzers, - ImmutableArray hostAnalyzers, - bool crashOnAnalyzerException, - CancellationToken cancellationToken) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - if (compilation == null) - { - // project doesn't support compilation - return null; - } - - // Create driver that holds onto compilation and associated analyzers - var filteredProjectAnalyzers = projectAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); - var filteredHostAnalyzers = hostAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); - var filteredProjectSuppressors = filteredProjectAnalyzers.WhereAsArray(static a => a is DiagnosticSuppressor); - filteredHostAnalyzers = filteredHostAnalyzers.AddRange(filteredProjectSuppressors); - - // PERF: there is no analyzers for this compilation. - // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. - if (filteredProjectAnalyzers.IsEmpty && filteredHostAnalyzers.IsEmpty) - { - return null; - } - - Contract.ThrowIfFalse(project.SupportsCompilation); - AssertCompilation(project, compilation); - - // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to - // async being used with synchronous blocking concurrency. - var projectAnalyzerOptions = new CompilationWithAnalyzersOptions( - options: project.AnalyzerOptions, - onAnalyzerException: null, - analyzerExceptionFilter: GetAnalyzerExceptionFilter(), - concurrentAnalysis: false, - logAnalyzerExecutionTime: true, - reportSuppressedDiagnostics: true); - var hostAnalyzerOptions = new CompilationWithAnalyzersOptions( - options: project.HostAnalyzerOptions, - onAnalyzerException: null, - analyzerExceptionFilter: GetAnalyzerExceptionFilter(), - concurrentAnalysis: false, - logAnalyzerExecutionTime: true, - reportSuppressedDiagnostics: true); - - // Create driver that holds onto compilation and associated analyzers - return new CompilationWithAnalyzersPair( - filteredProjectAnalyzers.Any() ? compilation.WithAnalyzers(filteredProjectAnalyzers, projectAnalyzerOptions) : null, - filteredHostAnalyzers.Any() ? compilation.WithAnalyzers(filteredHostAnalyzers, hostAnalyzerOptions) : null); - - Func GetAnalyzerExceptionFilter() - { - return ex => - { - if (ex is not OperationCanceledException && crashOnAnalyzerException) - { - // report telemetry - FatalError.ReportAndPropagate(ex); - - // force fail fast (the host might not crash when reporting telemetry): - FailFast.OnFatalException(ex); - } - - return true; - }; - } - } - - [Conditional("DEBUG")] - private static void AssertCompilation(Project project, Compilation compilation1) - { - // given compilation must be from given project. - Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2)); - Contract.ThrowIfFalse(compilation1 == compilation2); - } - /// /// Return true if the given is not suppressed for the given project. /// NOTE: This API is intended to be used only for performance optimization. diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs deleted file mode 100644 index 80b3249e48739..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// state that is responsible to hold onto local diagnostics data regarding active/opened files (depends on host) - /// in memory. - /// - private sealed class ActiveFileState - { - private readonly object _gate = new(); - - // file state this is for - public readonly DocumentId DocumentId; - - // analysis data for each kind - private DocumentAnalysisData _syntax = DocumentAnalysisData.Empty; - private DocumentAnalysisData _semantic = DocumentAnalysisData.Empty; - - public ActiveFileState(DocumentId documentId) - => DocumentId = documentId; - - public bool IsEmpty - { - get - { - lock (_gate) - { - return _syntax.Items.IsEmpty && _semantic.Items.IsEmpty; - } - } - } - - public void ResetVersion() - { - lock (_gate) - { - // reset version of cached data so that we can recalculate new data (ex, OnDocumentReset) - _syntax = new DocumentAnalysisData(VersionStamp.Default, _syntax.LineCount, _syntax.Items); - _semantic = new DocumentAnalysisData(VersionStamp.Default, _semantic.LineCount, _semantic.Items); - } - } - - public DocumentAnalysisData GetAnalysisData(AnalysisKind kind) - { - lock (_gate) - { - return kind switch - { - AnalysisKind.Syntax => _syntax, - AnalysisKind.Semantic => _semantic, - _ => throw ExceptionUtilities.UnexpectedValue(kind) - }; - } - } - - public void Save(AnalysisKind kind, DocumentAnalysisData data) - { - Contract.ThrowIfFalse(data.OldItems.IsDefault); - - lock (_gate) - { - switch (kind) - { - case AnalysisKind.Syntax: - _syntax = data; - return; - - case AnalysisKind.Semantic: - _semantic = data; - return; - - default: - throw ExceptionUtilities.UnexpectedValue(kind); - } - } - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs deleted file mode 100644 index 3be6f73e60c14..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Workspaces.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// Simple data holder for local diagnostics for an analyzer - /// - private readonly struct DocumentAnalysisData - { - public static readonly DocumentAnalysisData Empty = new(VersionStamp.Default, lineCount: 0, []); - - /// - /// Version of the diagnostic data. - /// - public readonly VersionStamp Version; - - /// - /// Number of lines in the document. - /// - public readonly int LineCount; - - /// - /// Current data that matches the version. - /// - public readonly ImmutableArray Items; - - /// - /// Last set of data we broadcasted to outer world, or . - /// - public readonly ImmutableArray OldItems; - - public DocumentAnalysisData(VersionStamp version, int lineCount, ImmutableArray items) - { - Debug.Assert(!items.IsDefault); - - Version = version; - LineCount = lineCount; - Items = items; - OldItems = default; - } - - public DocumentAnalysisData(VersionStamp version, int lineCount, ImmutableArray oldItems, ImmutableArray newItems) - : this(version, lineCount, newItems) - { - Debug.Assert(!oldItems.IsDefault); - OldItems = oldItems; - } - } - - /// - /// Data holder for all diagnostics for a project for an analyzer - /// - private readonly struct ProjectAnalysisData - { - /// - /// ProjectId of this data - /// - public readonly ProjectId ProjectId; - - /// - /// Version of the Items - /// - public readonly VersionStamp Version; - - /// - /// Current data that matches the version - /// - public readonly ImmutableDictionary Result; - - /// - /// When present, holds onto last data we broadcasted to outer world. - /// - public readonly ImmutableDictionary? OldResult; - - public ProjectAnalysisData(ProjectId projectId, VersionStamp version, ImmutableDictionary result) - { - ProjectId = projectId; - Version = version; - Result = result; - - OldResult = null; - } - - public ProjectAnalysisData( - ProjectId projectId, - VersionStamp version, - ImmutableDictionary oldResult, - ImmutableDictionary newResult) - : this(projectId, version, newResult) - { - OldResult = oldResult; - } - - public DiagnosticAnalysisResult GetResult(DiagnosticAnalyzer analyzer) - => GetResultOrEmpty(Result, analyzer, ProjectId, Version); - - public bool TryGetResult(DiagnosticAnalyzer analyzer, out DiagnosticAnalysisResult result) - => Result.TryGetValue(analyzer, out result); - - public static async Task CreateAsync(Project project, IEnumerable stateSets, bool avoidLoadingData, CancellationToken cancellationToken) - { - VersionStamp? version = null; - - var builder = ImmutableDictionary.CreateBuilder(); - foreach (var stateSet in stateSets) - { - var state = stateSet.GetOrCreateProjectState(project.Id); - var result = await state.GetAnalysisDataAsync(project, avoidLoadingData, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfFalse(project.Id == result.ProjectId); - - if (!version.HasValue) - { - version = result.Version; - } - else if (version.Value != VersionStamp.Default && version.Value != result.Version) - { - // if not all version is same, set version as default. - // this can happen at the initial data loading or - // when document is closed and we put active file state to project state - version = VersionStamp.Default; - } - - builder.Add(stateSet.Analyzer, result); - } - - if (!version.HasValue) - { - // there is no saved data to return. - return new ProjectAnalysisData(project.Id, VersionStamp.Default, ImmutableDictionary.Empty); - } - - return new ProjectAnalysisData(project.Id, version.Value, builder.ToImmutable()); - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 9775aef42f602..52dde0ab537aa 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -2,14 +2,129 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2; +namespace Microsoft.CodeAnalysis.Diagnostics; -internal partial class DiagnosticIncrementalAnalyzer +internal partial class DiagnosticAnalyzerService { - private static Task CreateCompilationWithAnalyzersAsync(Project project, ImmutableArray stateSets, bool crashOnAnalyzerException, CancellationToken cancellationToken) - => DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, stateSets.SelectAsArray(s => !s.IsHostAnalyzer, s => s.Analyzer), stateSets.SelectAsArray(s => s.IsHostAnalyzer, s => s.Analyzer), crashOnAnalyzerException, cancellationToken); + /// + /// Cached data from a to the last instance + /// created for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of s passed along with the project. As such, we might not be able to use a prior cached + /// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the + /// next caller. + /// + private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); + + private static async Task GetOrCreateCompilationWithAnalyzersAsync( + Project project, + ImmutableArray analyzers, + HostAnalyzerInfo hostAnalyzerInfo, + bool crashOnAnalyzerException, + CancellationToken cancellationToken) + { + if (!project.SupportsCompilation) + return null; + + var projectState = project.State; + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); + + // Make sure the cached pair was computed with at least the same state sets we're asking about. if not, + // recompute and cache with the new state sets. + if (!s_projectToCompilationWithAnalyzers.TryGetValue(projectState, out var tupleBox) || + tupleBox.Value.checksum != checksum || + !analyzers.IsSubsetOf(tupleBox.Value.analyzers)) + { + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilationWithAnalyzersPair = CreateCompilationWithAnalyzers(projectState, compilation); + tupleBox = new((checksum, analyzers, compilationWithAnalyzersPair)); + +#if NET + s_projectToCompilationWithAnalyzers.AddOrUpdate(projectState, tupleBox); +#else + // Make a best effort attempt to store the latest computed value against these state sets. If this + // fails (because another thread interleaves with this), that's ok. We still return the pair we + // computed, so our caller will still see the right data + s_projectToCompilationWithAnalyzers.Remove(projectState); + + // Intentionally ignore the result of this. We still want to use the value we computed above, even if + // another thread interleaves and sets a different value. + s_projectToCompilationWithAnalyzers.GetValue(projectState, _ => tupleBox); +#endif + } + + return tupleBox.Value.compilationWithAnalyzersPair; + + // + // Should only be called on a that . + // + CompilationWithAnalyzersPair? CreateCompilationWithAnalyzers( + ProjectState project, Compilation compilation) + { + var projectAnalyzers = analyzers.WhereAsArray(static (s, info) => !info.IsHostAnalyzer(s), hostAnalyzerInfo); + var hostAnalyzers = analyzers.WhereAsArray(static (s, info) => info.IsHostAnalyzer(s), hostAnalyzerInfo); + + // Create driver that holds onto compilation and associated analyzers + var filteredProjectAnalyzers = projectAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); + var filteredHostAnalyzers = hostAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); + var filteredProjectSuppressors = filteredProjectAnalyzers.WhereAsArray(static a => a is DiagnosticSuppressor); + filteredHostAnalyzers = filteredHostAnalyzers.AddRange(filteredProjectSuppressors); + + // PERF: there is no analyzers for this compilation. + // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. + if (filteredProjectAnalyzers.IsEmpty && filteredHostAnalyzers.IsEmpty) + { + return null; + } + + var exceptionFilter = (Exception ex) => + { + if (ex is not OperationCanceledException && crashOnAnalyzerException) + { + // report telemetry + FatalError.ReportAndPropagate(ex); + + // force fail fast (the host might not crash when reporting telemetry): + FailFast.OnFatalException(ex); + } + + return true; + }; + + // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to + // async being used with synchronous blocking concurrency. + var projectCompilation = !filteredProjectAnalyzers.Any() + ? null + : compilation.WithAnalyzers(filteredProjectAnalyzers, new CompilationWithAnalyzersOptions( + options: project.ProjectAnalyzerOptions, + onAnalyzerException: null, + analyzerExceptionFilter: exceptionFilter, + concurrentAnalysis: false, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: true)); + + var hostCompilation = !filteredHostAnalyzers.Any() + ? null + : compilation.WithAnalyzers(filteredHostAnalyzers, new CompilationWithAnalyzersOptions( + options: project.HostAnalyzerOptions, + onAnalyzerException: null, + analyzerExceptionFilter: exceptionFilter, + concurrentAnalysis: false, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: true)); + + // Create driver that holds onto compilation and associated analyzers + return new CompilationWithAnalyzersPair(projectCompilation, hostCompilation); + } + } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 82f948660ab2d..9083f2b77e0c4 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -11,342 +11,234 @@ using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { /// - /// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them + /// Return all diagnostics that belong to given project for the given either + /// from cache or by calculating them. /// - private async Task GetProjectAnalysisDataAsync( - CompilationWithAnalyzersPair? compilationWithAnalyzers, Project project, ImmutableArray stateSets, CancellationToken cancellationToken) + private async Task> ComputeDiagnosticAnalysisResultsAsync( + CompilationWithAnalyzersPair? compilationWithAnalyzers, + Project project, + ImmutableArray analyzers, + CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken)) + using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, analyzers, cancellationToken)) { try { - // PERF: We need to flip this to false when we do actual diffing. - var avoidLoadingData = true; - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var existingData = await ProjectAnalysisData.CreateAsync(project, stateSets, avoidLoadingData, cancellationToken).ConfigureAwait(false); - - if (existingData.Version == version) - return existingData; - - var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, stateSets, existingData.Result, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsForIDEAnalyzersAsync(analyzers).ConfigureAwait(false); // If project is not loaded successfully, get rid of any semantic errors from compiler analyzer. // Note: In the past when project was not loaded successfully we did not run any analyzers on the project. // Now we run analyzers but filter out some information. So on such projects, there will be some perf degradation. - result = await RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync(result, project, cancellationToken).ConfigureAwait(false); + result = await RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync(result).ConfigureAwait(false); - return new ProjectAnalysisData(project.Id, version, existingData.Result, result); + return result; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable(); } } - } - private static async Task> RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync( - ImmutableDictionary result, Project project, CancellationToken cancellationToken) - { - // see whether solution is loaded successfully - var projectLoadedSuccessfully = await project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); - if (projectLoadedSuccessfully) + async Task> RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync( + ImmutableDictionary result) { - return result; - } + // see whether solution is loaded successfully + var projectLoadedSuccessfully = await project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); + if (projectLoadedSuccessfully) + { + return result; + } - var compilerAnalyzer = project.Solution.SolutionState.Analyzers.GetCompilerDiagnosticAnalyzer(project.Language); - if (compilerAnalyzer == null) - { - // this language doesn't support compiler analyzer - return result; - } + var compilerAnalyzer = project.Solution.SolutionState.Analyzers.GetCompilerDiagnosticAnalyzer(project.Language); + if (compilerAnalyzer == null) + { + // this language doesn't support compiler analyzer + return result; + } - if (!result.TryGetValue(compilerAnalyzer, out var analysisResult)) - { - // no result from compiler analyzer - return result; - } + if (!result.TryGetValue(compilerAnalyzer, out var analysisResult)) + { + // no result from compiler analyzer + return result; + } - Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"Failed to Load Successfully ({p.FilePath ?? p.Name})", project); + Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"Failed to Load Successfully ({p.FilePath ?? p.Name})", project); - // get rid of any result except syntax from compiler analyzer result - var newCompilerAnalysisResult = analysisResult.DropExceptSyntax(); + // get rid of any result except syntax from compiler analyzer result + var newCompilerAnalysisResult = analysisResult.DropExceptSyntax(); - // return new result - return result.SetItem(compilerAnalyzer, newCompilerAnalysisResult); - } + // return new result + return result.SetItem(compilerAnalyzer, newCompilerAnalysisResult); + } - /// - /// Calculate all diagnostics for a given project using analyzers referenced by the project and specified IDE analyzers. - /// - private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzersPair? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, CancellationToken cancellationToken) - { - try + // + // Calculate all diagnostics for a given project using analyzers referenced by the project and specified IDE analyzers. + // + async Task> ComputeDiagnosticsForAnalyzersAsync( + ImmutableArray ideAnalyzers) { - var result = ImmutableDictionary.Empty; - - // can be null if given project doesn't support compilation. - if (compilationWithAnalyzers?.ProjectAnalyzers.Length > 0 - || compilationWithAnalyzers?.HostAnalyzers.Length > 0) + try { - // calculate regular diagnostic analyzers diagnostics - var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync( - project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); - - result = resultMap.AnalysisResult; + var result = ImmutableDictionary.Empty; - // record telemetry data - UpdateAnalyzerTelemetryData(resultMap.TelemetryInfo); - } + // can be null if given project doesn't support compilation. + if (compilationWithAnalyzers?.ProjectAnalyzers.Length > 0 + || compilationWithAnalyzers?.HostAnalyzers.Length > 0) + { + // calculate regular diagnostic analyzers diagnostics + var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync( + project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); - // check whether there is IDE specific project diagnostic analyzer - Debug.Assert(ideAnalyzers.All(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer)); - return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(project, ideAnalyzers, compilationWithAnalyzers?.HostCompilation, result, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); - } - } + result = resultMap.AnalysisResult; - private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzersPair? compilationWithAnalyzers, Project project, ImmutableArray stateSets, - ImmutableDictionary existing, CancellationToken cancellationToken) - { - try - { - // PERF: check whether we can reduce number of analyzers we need to run. - // this can happen since caller could have created the driver with different set of analyzers that are different - // than what we used to create the cache. - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - - var ideAnalyzers = stateSets.Select(s => s.Analyzer).Where(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer).ToImmutableArrayOrEmpty(); + // record telemetry data + UpdateAnalyzerTelemetryData(resultMap.TelemetryInfo); + } - if (compilationWithAnalyzers != null && TryReduceAnalyzersToRun(compilationWithAnalyzers, version, existing, out var projectAnalyzersToRun, out var hostAnalyzersToRun)) + // check whether there is IDE specific project diagnostic analyzer + Debug.Assert(ideAnalyzers.All(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer)); + return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(ideAnalyzers, result).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { - // it looks like we can reduce the set. create new CompilationWithAnalyzer. - // if we reduced to 0, we just pass in null for analyzer drvier. it could be reduced to 0 - // since we might have up to date results for analyzers from compiler but not for - // workspace analyzers. - - var compilationWithReducedAnalyzers = (projectAnalyzersToRun.Length == 0 && hostAnalyzersToRun.Length == 0) ? null : - await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync( - project, - projectAnalyzersToRun, - hostAnalyzersToRun, - AnalyzerService.CrashOnAnalyzerException, - cancellationToken).ConfigureAwait(false); - - var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); - return MergeExistingDiagnostics(version, existing, result); + throw ExceptionUtilities.Unreachable(); } - - // we couldn't reduce the set. - return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); } - } - private static ImmutableDictionary MergeExistingDiagnostics( - VersionStamp version, ImmutableDictionary existing, ImmutableDictionary result) - { - // quick bail out. - if (existing.IsEmpty) + async Task> ComputeDiagnosticsForIDEAnalyzersAsync( + ImmutableArray analyzers) { - return result; - } + try + { + var ideAnalyzers = analyzers.WhereAsArray(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer); - foreach (var (analyzer, results) in existing) - { - if (results.Version != version) + return await ComputeDiagnosticsForAnalyzersAsync(ideAnalyzers).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { - continue; + throw ExceptionUtilities.Unreachable(); } - - result = result.SetItem(analyzer, results); } - return result; - } - - private static bool TryReduceAnalyzersToRun( - CompilationWithAnalyzersPair compilationWithAnalyzers, VersionStamp version, - ImmutableDictionary existing, - out ImmutableArray projectAnalyzers, - out ImmutableArray hostAnalyzers) - { - projectAnalyzers = compilationWithAnalyzers.ProjectAnalyzers.WhereAsArray( - static (analyzer, arg) => + async Task> MergeProjectDiagnosticAnalyzerDiagnosticsAsync( + ImmutableArray ideAnalyzers, + ImmutableDictionary result) + { + try { - if (arg.existing.TryGetValue(analyzer, out var analysisResult) && - analysisResult.Version == arg.version) - { - // we already have up to date result. - return false; - } + var compilation = compilationWithAnalyzers?.HostCompilation; - // analyzer that is out of date. - // open file only analyzer is always out of date for project wide data - return true; - }, - (existing, version)); + (result, var failedDocuments) = await UpdateWithDocumentLoadAndGeneratorFailuresAsync(result).ConfigureAwait(false); - hostAnalyzers = compilationWithAnalyzers.HostAnalyzers.WhereAsArray( - static (analyzer, arg) => - { - if (arg.existing.TryGetValue(analyzer, out var analysisResult) && - analysisResult.Version == arg.version) + foreach (var analyzer in ideAnalyzers) { - // we already have up to date result. - return false; - } - - // analyzer that is out of date. - // open file only analyzer is always out of date for project wide data - return true; - }, - (existing, version)); - - if (projectAnalyzers.Length == compilationWithAnalyzers.ProjectAnalyzers.Length - && hostAnalyzers.Length == compilationWithAnalyzers.HostAnalyzers.Length) - { - // all of analyzers are out of date. - projectAnalyzers = default; - hostAnalyzers = default; - return false; - } + var builder = new DiagnosticAnalysisResultBuilder(project); - return true; - } - - private async Task> MergeProjectDiagnosticAnalyzerDiagnosticsAsync( - Project project, - ImmutableArray ideAnalyzers, - Compilation? compilation, - ImmutableDictionary result, - CancellationToken cancellationToken) - { - try - { - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - - (result, var failedDocuments) = await UpdateWithDocumentLoadAndGeneratorFailuresAsync(result, project, version, cancellationToken).ConfigureAwait(false); - - foreach (var analyzer in ideAnalyzers) - { - var builder = new DiagnosticAnalysisResultBuilder(project, version); - - switch (analyzer) - { - case DocumentDiagnosticAnalyzer documentAnalyzer: - foreach (var document in project.Documents) - { - // don't analyze documents whose content failed to load - if (failedDocuments == null || !failedDocuments.Contains(document)) + switch (analyzer) + { + case DocumentDiagnosticAnalyzer documentAnalyzer: + foreach (var document in project.Documents) { - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - if (tree != null) - { - builder.AddSyntaxDiagnostics(tree, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Syntax, compilation, cancellationToken).ConfigureAwait(false)); - builder.AddSemanticDiagnostics(tree, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Semantic, compilation, cancellationToken).ConfigureAwait(false)); - } - else + // don't analyze documents whose content failed to load + if (failedDocuments == null || !failedDocuments.Contains(document)) { - builder.AddExternalSyntaxDiagnostics(document.Id, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Syntax, compilation, cancellationToken).ConfigureAwait(false)); - builder.AddExternalSemanticDiagnostics(document.Id, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Semantic, compilation, cancellationToken).ConfigureAwait(false)); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree != null) + { + builder.AddSyntaxDiagnostics(tree, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Syntax, compilation, cancellationToken).ConfigureAwait(false)); + builder.AddSemanticDiagnostics(tree, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Semantic, compilation, cancellationToken).ConfigureAwait(false)); + } + else + { + builder.AddExternalSyntaxDiagnostics(document.Id, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Syntax, compilation, cancellationToken).ConfigureAwait(false)); + builder.AddExternalSemanticDiagnostics(document.Id, await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, document, AnalysisKind.Semantic, compilation, cancellationToken).ConfigureAwait(false)); + } } } - } - break; + break; - case ProjectDiagnosticAnalyzer projectAnalyzer: - builder.AddCompilationDiagnostics(await DocumentAnalysisExecutor.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(projectAnalyzer, project, compilation, cancellationToken).ConfigureAwait(false)); - break; + case ProjectDiagnosticAnalyzer projectAnalyzer: + builder.AddCompilationDiagnostics(await DocumentAnalysisExecutor.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(projectAnalyzer, project, compilation, cancellationToken).ConfigureAwait(false)); + break; + } + + // merge the result to existing one. + // there can be existing one from compiler driver with empty set. overwrite it with + // ide one. + result = result.SetItem(analyzer, DiagnosticAnalysisResult.CreateFromBuilder(builder)); } - // merge the result to existing one. - // there can be existing one from compiler driver with empty set. overwrite it with - // ide one. - result = result.SetItem(analyzer, DiagnosticAnalysisResult.CreateFromBuilder(builder)); + return result; + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) + { + throw ExceptionUtilities.Unreachable(); } - - return result; - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); } - } - - private async Task<(ImmutableDictionary results, ImmutableHashSet? failedDocuments)> UpdateWithDocumentLoadAndGeneratorFailuresAsync( - ImmutableDictionary results, - Project project, - VersionStamp version, - CancellationToken cancellationToken) - { - ImmutableHashSet.Builder? failedDocuments = null; - ImmutableDictionary>.Builder? lazyLoadDiagnostics = null; - foreach (var document in project.Documents) + async Task<(ImmutableDictionary results, ImmutableHashSet? failedDocuments)> UpdateWithDocumentLoadAndGeneratorFailuresAsync( + ImmutableDictionary results) { - var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); - if (loadDiagnostic != null) + ImmutableHashSet.Builder? failedDocuments = null; + ImmutableDictionary>.Builder? lazyLoadDiagnostics = null; + + foreach (var document in project.Documents) { - lazyLoadDiagnostics ??= ImmutableDictionary.CreateBuilder>(); - lazyLoadDiagnostics.Add(document.Id, [DiagnosticData.Create(loadDiagnostic, document)]); + var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + if (loadDiagnostic != null) + { + lazyLoadDiagnostics ??= ImmutableDictionary.CreateBuilder>(); + lazyLoadDiagnostics.Add(document.Id, [DiagnosticData.Create(loadDiagnostic, document)]); - failedDocuments ??= ImmutableHashSet.CreateBuilder(); - failedDocuments.Add(document); + failedDocuments ??= ImmutableHashSet.CreateBuilder(); + failedDocuments.Add(document); + } } - } - results = results.SetItem( - FileContentLoadAnalyzer.Instance, - DiagnosticAnalysisResult.Create( - project, - version, - syntaxLocalMap: lazyLoadDiagnostics?.ToImmutable() ?? ImmutableDictionary>.Empty, - semanticLocalMap: ImmutableDictionary>.Empty, - nonLocalMap: ImmutableDictionary>.Empty, - others: [], - documentIds: null)); - - var generatorDiagnostics = await _diagnosticAnalyzerRunner.GetSourceGeneratorDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); - var diagnosticResultBuilder = new DiagnosticAnalysisResultBuilder(project, version); - foreach (var generatorDiagnostic in generatorDiagnostics) - { - // We'll always treat generator diagnostics that are associated with a tree as a local diagnostic, because - // we want that to be refreshed and deduplicated with regular document analysis. - diagnosticResultBuilder.AddDiagnosticTreatedAsLocalSemantic(generatorDiagnostic); - } + results = results.SetItem( + FileContentLoadAnalyzer.Instance, + DiagnosticAnalysisResult.Create( + project, + syntaxLocalMap: lazyLoadDiagnostics?.ToImmutable() ?? ImmutableDictionary>.Empty, + semanticLocalMap: ImmutableDictionary>.Empty, + nonLocalMap: ImmutableDictionary>.Empty, + others: [], + documentIds: null)); + + var generatorDiagnostics = await _diagnosticAnalyzerRunner.GetSourceGeneratorDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); + var diagnosticResultBuilder = new DiagnosticAnalysisResultBuilder(project); + foreach (var generatorDiagnostic in generatorDiagnostics) + { + // We'll always treat generator diagnostics that are associated with a tree as a local diagnostic, because + // we want that to be refreshed and deduplicated with regular document analysis. + diagnosticResultBuilder.AddDiagnosticTreatedAsLocalSemantic(generatorDiagnostic); + } - results = results.SetItem( - GeneratorDiagnosticsPlaceholderAnalyzer.Instance, - DiagnosticAnalysisResult.CreateFromBuilder(diagnosticResultBuilder)); + results = results.SetItem( + GeneratorDiagnosticsPlaceholderAnalyzer.Instance, + DiagnosticAnalysisResult.CreateFromBuilder(diagnosticResultBuilder)); - return (results, failedDocuments?.ToImmutable()); - } + return (results, failedDocuments?.ToImmutable()); + } - private void UpdateAnalyzerTelemetryData(ImmutableDictionary telemetry) - { - foreach (var (analyzer, telemetryInfo) in telemetry) + void UpdateAnalyzerTelemetryData(ImmutableDictionary telemetry) { - var isTelemetryCollectionAllowed = DiagnosticAnalyzerInfoCache.IsTelemetryCollectionAllowed(analyzer); - _telemetry.UpdateAnalyzerActionsTelemetry(analyzer, telemetryInfo, isTelemetryCollectionAllowed); + foreach (var (analyzer, telemetryInfo) in telemetry) + { + var isTelemetryCollectionAllowed = DiagnosticAnalyzerInfoCache.IsTelemetryCollectionAllowed(analyzer); + _telemetry.UpdateAnalyzerActionsTelemetry(analyzer, telemetryInfo, isTelemetryCollectionAllowed); + } } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs new file mode 100644 index 0000000000000..9a917b7ff7ef5 --- /dev/null +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService +{ + private partial class DiagnosticIncrementalAnalyzer + { + private partial class StateManager + { + private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo( + SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo) + { + var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences); + // Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the + // Host fallback options. These ids will be used when building up the Host and Project analyzer collections. + var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project); + var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect)); + return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers); + + static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet ReferenceIdsToRedirect) state) + { + var language = arg.Language; + var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language); + + var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect); + var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true); + + return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers); + } + + static (IEnumerable> HostAnalyzerCollection, IEnumerable> ProjectAnalyzerCollection) GetAnalyzerCollections( + ImmutableDictionary> analyzersPerReference, + ImmutableHashSet referenceIdsToRedirectAsProjectAnalyzers) + { + if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty) + { + return (analyzersPerReference.Values, []); + } + + var hostAnalyzerCollection = ArrayBuilder>.GetInstance(); + var projectAnalyzerCollection = ArrayBuilder>.GetInstance(); + + foreach (var (referenceId, analyzers) in analyzersPerReference) + { + if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId)) + { + projectAnalyzerCollection.Add(analyzers); + } + else + { + hostAnalyzerCollection.Add(analyzers); + } + } + + return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree()); + } + } + + private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers( + SolutionState solution, ProjectState project) + { + if (project.HasSdkCodeStyleAnalyzers) + { + // When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the + // Features analyzers. We need to then treat the Features analyzers as Project analyzers so + // they do not get access to the Host fallback options. + return GetFeaturesAnalyzerReferenceIds(solution.Analyzers); + } + + return []; + + static ImmutableHashSet GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers) + { + var builder = ImmutableHashSet.CreateBuilder(); + + foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences) + { + if (analyzerReference.IsFeaturesAnalyzer()) + builder.Add(analyzerReference.Id); + } + + return builder.ToImmutable(); + } + } + } + } + + private sealed class HostAnalyzerInfo + { + private const int FileContentLoadAnalyzerPriority = -4; + private const int GeneratorDiagnosticsPlaceholderAnalyzerPriority = -3; + private const int BuiltInCompilerPriority = -2; + private const int RegularDiagnosticAnalyzerPriority = -1; + + private readonly ImmutableHashSet _hostAnalyzers; + private readonly ImmutableHashSet _allAnalyzers; + public readonly ImmutableArray OrderedAllAnalyzers; + + public HostAnalyzerInfo( + ImmutableHashSet hostAnalyzers, + ImmutableHashSet allAnalyzers) + { + _hostAnalyzers = hostAnalyzers; + _allAnalyzers = allAnalyzers; + + // order analyzers. + // order will be in this order + // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers + OrderedAllAnalyzers = [.. _allAnalyzers.OrderBy(PriorityComparison)]; + } + + public bool IsHostAnalyzer(DiagnosticAnalyzer analyzer) + => _hostAnalyzers.Contains(analyzer); + + public HostAnalyzerInfo WithExcludedAnalyzers(ImmutableHashSet excludedAnalyzers) + { + if (excludedAnalyzers.IsEmpty) + { + return this; + } + + return new(_hostAnalyzers, _allAnalyzers.Except(excludedAnalyzers)); + } + + private int PriorityComparison(DiagnosticAnalyzer state1, DiagnosticAnalyzer state2) + => GetPriority(state1) - GetPriority(state2); + + private static int GetPriority(DiagnosticAnalyzer state) + { + // compiler gets highest priority + if (state.IsCompilerAnalyzer()) + { + return BuiltInCompilerPriority; + } + + return state switch + { + FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority, + GeneratorDiagnosticsPlaceholderAnalyzer _ => GeneratorDiagnosticsPlaceholderAnalyzerPriority, + DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), + ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), + _ => RegularDiagnosticAnalyzerPriority, + }; + } + } +} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs deleted file mode 100644 index ca96681acd08d..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; -using System.Collections.Immutable; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - private static class InMemoryStorage - { - // the reason using nested map rather than having tuple as key is so that I dont have a gigantic map - private static readonly ConcurrentDictionary> s_map = - new(concurrencyLevel: 2, capacity: 10); - - public static bool TryGetValue(DiagnosticAnalyzer analyzer, (object key, string stateKey) key, out CacheEntry entry) - { - AssertKey(key); - - entry = default; - return s_map.TryGetValue(analyzer, out var analyzerMap) && - analyzerMap.TryGetValue(key, out entry); - } - - public static void Cache(DiagnosticAnalyzer analyzer, (object key, string stateKey) key, CacheEntry entry) - { - AssertKey(key); - - // add new cache entry - var analyzerMap = s_map.GetOrAdd(analyzer, _ => new ConcurrentDictionary<(object key, string stateKey), CacheEntry>(concurrencyLevel: 2, capacity: 10)); - analyzerMap[key] = entry; - } - - public static void Remove(DiagnosticAnalyzer analyzer, (object key, string stateKey) key) - { - AssertKey(key); - // remove the entry - if (!s_map.TryGetValue(analyzer, out var analyzerMap)) - { - return; - } - - analyzerMap.TryRemove(key, out _); - - if (analyzerMap.IsEmpty) - { - s_map.TryRemove(analyzer, out _); - } - } - - public static void DropCache(DiagnosticAnalyzer analyzer) - { - // drop any cache related to given analyzer - s_map.TryRemove(analyzer, out _); - } - - // make sure key is either documentId or projectId - private static void AssertKey((object key, string stateKey) key) - => Contract.ThrowIfFalse(key.key is DocumentId or ProjectId); - } - - // in memory cache entry - private readonly struct CacheEntry - { - public readonly VersionStamp Version; - public readonly ImmutableArray Diagnostics; - - public CacheEntry(VersionStamp version, ImmutableArray diagnostics) - { - Version = version; - Diagnostics = diagnostics; - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs index de59475e77e05..dd609587e37cc 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs @@ -16,9 +16,11 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { /// /// This type performs incremental analysis in presence of edits to only a single member inside a document. @@ -45,10 +47,8 @@ public void UpdateDocumentWithCachedDiagnostics(Document document) public async Task>> ComputeDiagnosticsAsync( DocumentAnalysisExecutor executor, - ImmutableArray analyzersWithState, + ImmutableArray analyzers, VersionStamp version, - Func>> computeAnalyzerDiagnosticsAsync, - Func>>> computeDiagnosticsNonIncrementallyAsync, CancellationToken cancellationToken) { var analysisScope = executor.AnalysisScope; @@ -57,7 +57,7 @@ public async Task stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); + Debug.Assert(analyzers.All(analyzer => analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); var document = (Document)analysisScope.TextDocument; var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -66,7 +66,7 @@ public async Task.GetInstance(out var spanBasedAnalyzers); - using var _2 = ArrayBuilder.GetInstance(out var documentBasedAnalyzers); - (AnalyzerWithState analyzerWithState, bool spanBased)? compilerAnalyzerData = null; - foreach (var analyzerWithState in analyzersWithState) + using var _1 = ArrayBuilder.GetInstance(out var spanBasedAnalyzers); + using var _2 = ArrayBuilder.GetInstance(out var documentBasedAnalyzers); + (DiagnosticAnalyzer analyzer, bool spanBased)? compilerAnalyzerData = null; + foreach (var analyzer in analyzers) { // Check if we have existing cached diagnostics for this analyzer whose version matches the // old document version. If so, we can perform span based incremental analysis for the changed member. // Otherwise, we have to perform entire document analysis. - var state = analyzerWithState.State; - var existingData = analyzerWithState.ExistingData; - if (oldDocumentVersion == existingData.Version) + if (oldDocumentVersion == VersionStamp.Default) { - if (!compilerAnalyzerData.HasValue && analyzerWithState.Analyzer.IsCompilerAnalyzer()) - compilerAnalyzerData = (analyzerWithState, spanBased: true); + if (!compilerAnalyzerData.HasValue && analyzer.IsCompilerAnalyzer()) + compilerAnalyzerData = (analyzer, spanBased: true); else - spanBasedAnalyzers.Add(analyzerWithState); + spanBasedAnalyzers.Add(analyzer); } else { - var analyzerWithStateAndEmptyData = new AnalyzerWithState(analyzerWithState.Analyzer, analyzerWithState.IsHostAnalyzer, analyzerWithState.State, DocumentAnalysisData.Empty); - if (!compilerAnalyzerData.HasValue && analyzerWithState.Analyzer.IsCompilerAnalyzer()) - compilerAnalyzerData = (analyzerWithStateAndEmptyData, spanBased: false); + if (!compilerAnalyzerData.HasValue && analyzer.IsCompilerAnalyzer()) + compilerAnalyzerData = (analyzer, spanBased: false); else - documentBasedAnalyzers.Add(analyzerWithStateAndEmptyData); + documentBasedAnalyzers.Add(analyzer); } } if (spanBasedAnalyzers.Count == 0 && (!compilerAnalyzerData.HasValue || !compilerAnalyzerData.Value.spanBased)) { // No incremental span based-analysis to be performed. - return await computeDiagnosticsNonIncrementallyAsync(executor, cancellationToken).ConfigureAwait(false); + return await ComputeDocumentDiagnosticsCoreAsync(executor, cancellationToken).ConfigureAwait(false); } // Get or create the member spans for all member nodes in the old document. @@ -127,73 +124,59 @@ public async Task oldMemberSpans, - PooledDictionary> builder) + Dictionary> builder) { if (!compilerAnalyzerData.HasValue) return; - var (analyzerWithState, spanBased) = compilerAnalyzerData.Value; + var (analyzer, spanBased) = compilerAnalyzerData.Value; var span = spanBased ? changedMember.FullSpan : (TextSpan?)null; executor = executor.With(analysisScope.WithSpan(span)); - using var _ = ArrayBuilder.GetInstance(1, analyzerWithState, out var analyzersWithState); - await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(1, analyzer, out var analyzers); + await ExecuteAnalyzersAsync(executor, analyzers, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteSpanBasedAnalyzersAsync( - ArrayBuilder analyzersWithState, + ArrayBuilder analyzers, ImmutableArray oldMemberSpans, - PooledDictionary> builder) + Dictionary> builder) { - if (analyzersWithState.Count == 0) + if (analyzers.Count == 0) return; executor = executor.With(analysisScope.WithSpan(changedMember.FullSpan)); - await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); + await ExecuteAnalyzersAsync(executor, analyzers, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteDocumentBasedAnalyzersAsync( - ArrayBuilder analyzersWithState, + ArrayBuilder analyzers, ImmutableArray oldMemberSpans, - PooledDictionary> builder) + Dictionary> builder) { - if (analyzersWithState.Count == 0) + if (analyzers.Count == 0) return; executor = executor.With(analysisScope.WithSpan(null)); - await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); + await ExecuteAnalyzersAsync(executor, analyzers, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteAnalyzersAsync( DocumentAnalysisExecutor executor, - ArrayBuilder analyzersWithState, + ArrayBuilder analyzers, ImmutableArray oldMemberSpans, - PooledDictionary> builder) + Dictionary> builder) { var analysisScope = executor.AnalysisScope; Debug.Assert(changedMember != null); Debug.Assert(analysisScope.Kind == AnalysisKind.Semantic); - foreach (var analyzerWithState in analyzersWithState) + foreach (var analyzer in analyzers) { - var diagnostics = await computeAnalyzerDiagnosticsAsync(analyzerWithState.Analyzer, executor, cancellationToken).ConfigureAwait(false); - - // If we computed the diagnostics just for a span, then we are performing incremental analysis. - // We need to compute the full document diagnostics by re-using diagnostics outside the changed - // member and using the above computed latest diagnostics for the edited member span. - if (analysisScope.Span.HasValue) - { - Debug.Assert(analysisScope.Span.Value == changedMember.FullSpan); - - diagnostics = await GetUpdatedDiagnosticsForMemberEditAsync( - diagnostics, analyzerWithState.ExistingData, analyzerWithState.Analyzer, - executor, changedMember, changedMemberId, - oldMemberSpans, computeAnalyzerDiagnosticsAsync, cancellationToken).ConfigureAwait(false); - } - - builder.Add(analyzerWithState.Analyzer, diagnostics); + var diagnostics = await ComputeDocumentDiagnosticsForAnalyzerCoreAsync(analyzer, executor, cancellationToken).ConfigureAwait(false); + builder.Add(analyzer, diagnostics); } } } @@ -217,8 +200,10 @@ async Task ExecuteAnalyzersAsync( } var syntaxFacts = document.GetRequiredLanguageService(); - using var pooledMembers = syntaxFacts.GetMethodLevelMembers(root); - var members = pooledMembers.Object; + + // Specifies false for discardLargeInstances as these objects commonly exceed the default ArrayBuilder capacity threshold. + using var _ = ArrayBuilder.GetInstance(discardLargeInstances: false, out var members); + syntaxFacts.AddMethodLevelMembers(root, members); var memberSpans = members.SelectAsArray(member => member.FullSpan); var changedMemberId = members.IndexOf(changedMember); @@ -232,169 +217,6 @@ async Task ExecuteAnalyzersAsync( return (changedMember, changedMemberId, memberSpans, lastDocument); } - - private static async Task> GetUpdatedDiagnosticsForMemberEditAsync( - ImmutableArray diagnostics, - DocumentAnalysisData existingData, - DiagnosticAnalyzer analyzer, - DocumentAnalysisExecutor executor, - SyntaxNode changedMember, - int changedMemberId, - ImmutableArray oldMemberSpans, - Func>> computeAnalyzerDiagnosticsAsync, - CancellationToken cancellationToken) - { - // We are performing semantic span-based analysis for member-only edit scenario. - // Instead of computing the analyzer diagnostics for the entire document, - // we have computed the new diagnostics just for the edited member span. - Debug.Assert(executor.AnalysisScope.Span.HasValue); - Debug.Assert(executor.AnalysisScope.Span.Value == changedMember.FullSpan); - - // We now try to get the new document diagnostics by performing an incremental update: - // 1. Re-using all the old cached diagnostics outside the edited member node from a prior - // document snapshot, but with updated diagnostic spans. - // AND - // 2. Replacing old diagnostics for the edited member node in a prior document snapshot - // with the new diagnostics for this member node in the latest document snaphot. - // If we are unable to perform this incremental diagnostics update, - // we fallback to computing the diagnostics for the entire document. - var tree = changedMember.SyntaxTree; - var text = tree.GetText(cancellationToken); - if (TryGetUpdatedDocumentDiagnostics(existingData, oldMemberSpans, diagnostics, tree, text, changedMember, changedMemberId, out var updatedDiagnostics)) - { -#if DEBUG_INCREMENTAL_ANALYSIS - await ValidateMemberDiagnosticsAsync(executor, analyzer, updatedDiagnostics, cancellationToken).ConfigureAwait(false); -#endif - return updatedDiagnostics; - } - else - { - // Incremental diagnostics update failed. - // Fallback to computing the diagnostics for the entire document. - var documentExecutor = executor.With(executor.AnalysisScope.WithSpan(null)); - return await computeAnalyzerDiagnosticsAsync(analyzer, documentExecutor, cancellationToken).ConfigureAwait(false); - } - -#if DEBUG_INCREMENTAL_ANALYSIS - static async Task ValidateMemberDiagnosticsAsync(DocumentAnalysisExecutor executor, DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, CancellationToken cancellationToken) - { - executor = executor.With(executor.AnalysisScope.WithSpan(null)); - var expected = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false); - Debug.Assert(diagnostics.SetEquals(expected)); - } -#endif - } - - private static bool TryGetUpdatedDocumentDiagnostics( - DocumentAnalysisData existingData, - ImmutableArray oldMemberSpans, - ImmutableArray memberDiagnostics, - SyntaxTree tree, - SourceText text, - SyntaxNode member, - int memberId, - out ImmutableArray updatedDiagnostics) - { - // get old span - var oldSpan = oldMemberSpans[memberId]; - - // get old diagnostics - var diagnostics = existingData.Items; - - // check quick exit cases - if (diagnostics.Length == 0 && memberDiagnostics.Length == 0) - { - updatedDiagnostics = diagnostics; - return true; - } - - // simple case - if (diagnostics.Length == 0 && memberDiagnostics.Length > 0) - { - updatedDiagnostics = memberDiagnostics; - return true; - } - - // regular case - using var _ = ArrayBuilder.GetInstance(out var resultBuilder); - - // update member location - Contract.ThrowIfFalse(member.FullSpan.Start == oldSpan.Start); - var delta = member.FullSpan.End - oldSpan.End; - - var replaced = false; - foreach (var diagnostic in diagnostics) - { - if (diagnostic.DocumentId is null) - { - resultBuilder.Add(diagnostic); - continue; - } - - var diagnosticSpan = diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text); - if (diagnosticSpan.Start < oldSpan.Start) - { - // Bail out if the diagnostic has any additional locations that we don't know how to handle. - if (diagnostic.AdditionalLocations.Any(l => l.DocumentId != null && l.UnmappedFileSpan.GetClampedTextSpan(text).Start >= oldSpan.Start)) - { - updatedDiagnostics = default; - return false; - } - - resultBuilder.Add(diagnostic); - continue; - } - - if (!replaced) - { - resultBuilder.AddRange(memberDiagnostics); - replaced = true; - } - - if (oldSpan.End <= diagnosticSpan.Start) - { - // Bail out if the diagnostic has any additional locations that we don't know how to handle. - if (diagnostic.AdditionalLocations.Any(l => l.DocumentId != null && oldSpan.End > l.UnmappedFileSpan.GetClampedTextSpan(text).Start)) - { - updatedDiagnostics = default; - return false; - } - - resultBuilder.Add(UpdateLocations(diagnostic, tree, text, delta)); - continue; - } - } - - // if it haven't replaced, replace it now - if (!replaced) - { - resultBuilder.AddRange(memberDiagnostics); - } - - updatedDiagnostics = resultBuilder.ToImmutableArray(); - return true; - - static DiagnosticData UpdateLocations(DiagnosticData diagnostic, SyntaxTree tree, SourceText text, int delta) - { - Debug.Assert(diagnostic.DataLocation != null); - var location = UpdateLocation(diagnostic.DataLocation); - var additionalLocations = diagnostic.AdditionalLocations.SelectAsArray(UpdateLocation); - return diagnostic.WithLocations(location, additionalLocations); - - DiagnosticDataLocation UpdateLocation(DiagnosticDataLocation location) - { - var diagnosticSpan = location.UnmappedFileSpan.GetClampedTextSpan(text); - var start = Math.Max(diagnosticSpan.Start + delta, 0); - var end = start + diagnosticSpan.Length; - if (start >= tree.Length) - start = tree.Length - 1; - if (end >= tree.Length) - end = tree.Length - 1; - var newSpan = new TextSpan(start, end - start); - return location.WithSpan(newSpan, tree); - } - } - } } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs index c76750e900a4c..5258d00725e38 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs @@ -7,13 +7,16 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { private sealed partial class IncrementalMemberEditAnalyzer { @@ -47,8 +50,9 @@ static async Task> CreateMemberSpansAsync(Document docu var service = document.GetRequiredLanguageService(); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - using var pooledMembers = service.GetMethodLevelMembers(root); - var members = pooledMembers.Object; + // Specifies false for discardLargeInstances as these objects commonly exceed the default ArrayBuilder capacity threshold. + using var _ = ArrayBuilder.GetInstance(discardLargeInstances: false, out var members); + service.AddMethodLevelMembers(root, members); return members.SelectAsArray(m => m.FullSpan); } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs deleted file mode 100644 index f5b259f4e6372..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// EventArgs for - /// - /// this event args contains information such as the has changed - /// and what has changed. - /// - private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs - { - public readonly Project Project; - public readonly ImmutableArray Added; - public readonly ImmutableArray Removed; - - public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray added, ImmutableArray removed) - { - Project = project; - Added = added; - Removed = removed; - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs deleted file mode 100644 index d738aa7ec02de..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ /dev/null @@ -1,506 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Workspaces.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// State for diagnostics that belong to a project at given time. - /// - private sealed class ProjectState - { - private const string SyntaxStateName = nameof(SyntaxStateName); - private const string SemanticStateName = nameof(SemanticStateName); - private const string NonLocalStateName = nameof(NonLocalStateName); - - // project id of this state - private readonly StateSet _owner; - - // last aggregated analysis result for this project saved - private DiagnosticAnalysisResult _lastResult; - - public ProjectState(StateSet owner, ProjectId projectId) - { - _owner = owner; - _lastResult = DiagnosticAnalysisResult.CreateInitialResult(projectId); - } - - public bool FromBuild => _lastResult.FromBuild; - - public ImmutableHashSet GetDocumentsWithDiagnostics() - => _lastResult.DocumentIdsOrEmpty; - - public bool IsEmpty() - => _lastResult.IsEmpty; - - public bool IsEmpty(DocumentId documentId) - => IsEmpty(_lastResult, documentId); - - /// - /// Return all diagnostics for the given project stored in this state - /// - public async Task GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) - { - // make a copy of last result. - var lastResult = _lastResult; - Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); - - if (lastResult.IsDefault) - { - return await LoadInitialAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); - } - - RoslynDebug.Assert(lastResult.DocumentIds != null); - - // PERF: avoid loading data if version is not right one. - // avoid loading data flag is there as a strictly perf optimization. - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - if (avoidLoadingData && lastResult.Version != version) - { - return lastResult; - } - - // if given project doesnt have any diagnostics, return empty. - if (lastResult.IsEmpty) - { - return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); - } - - // loading data can be canceled any time. - var serializerVersion = lastResult.Version; - var builder = new Builder(project, lastResult.Version, lastResult.DocumentIds); - - foreach (var documentId in lastResult.DocumentIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var document = project.GetDocument(documentId); - if (document == null) - continue; - - if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) - { - Debug.Assert(lastResult.Version == VersionStamp.Default); - - // this can happen if we merged back active file diagnostics back to project state but - // project state didn't have diagnostics for the file yet. (since project state was staled) - continue; - } - } - - if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, 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. - } - - return builder.ToResult(); - } - - /// - /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document - /// - public async Task GetAnalysisDataAsync(TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) - { - // make a copy of last result. - var lastResult = _lastResult; - Contract.ThrowIfFalse(lastResult.ProjectId == document.Project.Id); - - if (lastResult.IsDefault) - { - return await LoadInitialAnalysisDataAsync(document, cancellationToken).ConfigureAwait(false); - } - - var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); - if (avoidLoadingData && lastResult.Version != version) - { - return lastResult; - } - - // if given document doesnt have any diagnostics, return empty. - if (IsEmpty(lastResult, document.Id)) - { - return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); - } - - // loading data can be canceled any time. - var serializerVersion = lastResult.Version; - var builder = new Builder(document.Project, lastResult.Version); - - if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) - { - Debug.Assert(lastResult.Version == VersionStamp.Default); - - // this can happen if we merged back active file diagnostics back to project state but - // project state didn't have diagnostics for the file yet. (since project state was staled) - } - - return builder.ToResult(); - } - - /// - /// Return all no location diagnostics for the given project stored in this state - /// - public async Task GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) - { - // make a copy of last result. - var lastResult = _lastResult; - Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); - - if (lastResult.IsDefault) - { - return await LoadInitialProjectAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); - } - - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - if (avoidLoadingData && lastResult.Version != version) - { - return lastResult; - } - - // if given document doesn't have any diagnostics, return empty. - if (lastResult.IsEmpty) - { - return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); - } - - // loading data can be canceled any time. - var serializerVersion = lastResult.Version; - var builder = new Builder(project, lastResult.Version); - - if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, 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. - } - - return builder.ToResult(); - } - - public async ValueTask SaveToInMemoryStorageAsync(Project project, DiagnosticAnalysisResult result) - { - Contract.ThrowIfTrue(result.IsAggregatedForm); - Contract.ThrowIfNull(result.DocumentIds); - - RemoveInMemoryCache(_lastResult); - - using var _ = PooledHashSet.GetInstance(out var documentIdsToProcess); - documentIdsToProcess.AddRange(_lastResult.DocumentIdsOrEmpty); - documentIdsToProcess.AddRange(result.DocumentIdsOrEmpty); - - // save last aggregated form of analysis result - _lastResult = result.ToAggregatedForm(); - - // serialization can't be canceled. - var serializerVersion = result.Version; - - foreach (var documentId in documentIdsToProcess) - { - var document = project.GetTextDocument(documentId); - - // If we couldn't find a normal document, and all features are enabled for source generated - // documents, attempt to locate a matching source generated document in the project. - if (document is null - && project.Solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true) - { - document = await project.GetSourceGeneratedDocumentAsync(documentId, CancellationToken.None).ConfigureAwait(false); - } - - if (document == null) - { - // it can happen with build synchronization since, in build case, - // we don't have actual snapshot (we have no idea what sources out of proc build has picked up) - // so we might be out of sync. - // example of such cases will be changing anything about solution while building is going on. - // it can be user explicit actions such as unloading project, deleting a file, but also it can be - // something project system or roslyn workspace does such as populating workspace right after - // solution is loaded. - continue; - } - - AddToInMemoryStorage(serializerVersion, project, document, document.Id, SyntaxStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax)); - AddToInMemoryStorage(serializerVersion, project, document, document.Id, SemanticStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic)); - AddToInMemoryStorage(serializerVersion, project, document, document.Id, NonLocalStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.NonLocal)); - } - - AddToInMemoryStorage(serializerVersion, project, document: null, result.ProjectId, NonLocalStateName, result.GetOtherDiagnostics()); - } - - public void ResetVersion() - { - // reset version of cached data so that we can recalculate new data (ex, OnDocumentReset) - _lastResult = _lastResult.Reset(); - } - - public async ValueTask MergeAsync(ActiveFileState state, TextDocument document, IGlobalOptionService globalOptions) - { - Contract.ThrowIfFalse(state.DocumentId == document.Id); - - // merge active file state to project state - var lastResult = _lastResult; - - var syntax = state.GetAnalysisData(AnalysisKind.Syntax); - var semantic = state.GetAnalysisData(AnalysisKind.Semantic); - - var project = document.Project; - - // if project didn't successfully loaded, then it is same as FSA off - var fullAnalysis = _owner.Analyzer.IsFullSolutionAnalysisEnabled(globalOptions, project.Language) && - await project.HasSuccessfullyLoadedAsync(CancellationToken.None).ConfigureAwait(false); - - // keep from build flag if full analysis is off - var fromBuild = fullAnalysis ? false : lastResult.FromBuild; - - // if it is allowed to keep project state, check versions and if they are same, bail out. - // if full solution analysis is off or we are asked to reset document state, we always merge. - if (fullAnalysis && - syntax.Version != VersionStamp.Default && - syntax.Version == semantic.Version && - syntax.Version == lastResult.Version) - { - // all data is in sync already. - return; - } - - // we have mixed versions or full analysis is off, set it to default so that it can be re-calculated next time so data can be in sync. - var version = VersionStamp.Default; - - // serialization can't be canceled. - var serializerVersion = version; - - // save active file diagnostics back to project state - AddToInMemoryStorage(serializerVersion, project, document, document.Id, SyntaxStateName, syntax.Items); - AddToInMemoryStorage(serializerVersion, project, document, document.Id, SemanticStateName, semantic.Items); - - // save last aggregated form of analysis result - _lastResult = _lastResult.UpdateAggregatedResult(version, state.DocumentId, fromBuild); - } - - public bool OnDocumentRemoved(DocumentId id) - { - RemoveInMemoryCacheEntries(id); - return !IsEmpty(id); - } - - public bool OnProjectRemoved(ProjectId id) - { - RemoveInMemoryCacheEntry(id, NonLocalStateName); - return !IsEmpty(); - } - - private async Task LoadInitialAnalysisDataAsync(Project project, CancellationToken cancellationToken) - { - // loading data can be canceled any time. - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializerVersion = version; - var builder = new Builder(project, version); - - foreach (var document in project.Documents) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) - continue; - } - - if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) - return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); - - return builder.ToResult(); - } - - private async Task LoadInitialAnalysisDataAsync(TextDocument document, CancellationToken cancellationToken) - { - // loading data can be canceled any time. - var project = document.Project; - - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializerVersion = version; - var builder = new Builder(project, version); - - if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) - { - return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); - } - - return builder.ToResult(); - } - - private async Task LoadInitialProjectAnalysisDataAsync(Project project, CancellationToken cancellationToken) - { - // loading data can be canceled any time. - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializerVersion = version; - var builder = new Builder(project, version); - - if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) - return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); - - return builder.ToResult(); - } - - private void AddToInMemoryStorage( - VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) - { - Contract.ThrowIfFalse(document == null || document.Project == project); - - // if serialization fail, hold it in the memory - InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializerVersion, diagnostics)); - } - - private async ValueTask TryGetDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, TextDocument document, Builder builder, CancellationToken cancellationToken) - { - var success = true; - var project = document.Project; - var documentId = document.Id; - - var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, SyntaxStateName, cancellationToken).ConfigureAwait(false); - if (!diagnostics.IsDefault) - { - builder.AddSyntaxLocals(documentId, diagnostics); - } - else - { - success = false; - } - - diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, SemanticStateName, cancellationToken).ConfigureAwait(false); - if (!diagnostics.IsDefault) - { - builder.AddSemanticLocals(documentId, diagnostics); - } - else - { - success = false; - } - - diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, NonLocalStateName, cancellationToken).ConfigureAwait(false); - if (!diagnostics.IsDefault) - { - builder.AddNonLocals(documentId, diagnostics); - } - else - { - success = false; - } - - return success; - } - - private async ValueTask TryGetProjectDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, Project project, Builder builder, CancellationToken cancellationToken) - { - var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document: null, project.Id, NonLocalStateName, cancellationToken).ConfigureAwait(false); - if (!diagnostics.IsDefault) - { - builder.AddOthers(diagnostics); - return true; - } - - return false; - } - - private ValueTask> GetDiagnosticsFromInMemoryStorageAsync( - VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, CancellationToken _) - { - Contract.ThrowIfFalse(document == null || document.Project == project); - - return InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializerVersion == entry.Version - ? new(entry.Diagnostics) - : default; - } - - private void RemoveInMemoryCache(DiagnosticAnalysisResult lastResult) - { - // remove old cache - foreach (var documentId in lastResult.DocumentIdsOrEmpty) - { - RemoveInMemoryCacheEntries(documentId); - } - } - - private void RemoveInMemoryCacheEntries(DocumentId id) - { - RemoveInMemoryCacheEntry(id, SyntaxStateName); - RemoveInMemoryCacheEntry(id, SemanticStateName); - RemoveInMemoryCacheEntry(id, NonLocalStateName); - } - - private void RemoveInMemoryCacheEntry(object key, string stateKey) - { - // remove in memory cache if entry exist - InMemoryStorage.Remove(_owner.Analyzer, (key, stateKey)); - } - - private static bool IsEmpty(DiagnosticAnalysisResult result, DocumentId documentId) - => !result.DocumentIdsOrEmpty.Contains(documentId); - - // we have this builder to avoid allocating collections unnecessarily. - private sealed class Builder - { - private readonly Project _project; - private readonly VersionStamp _version; - private readonly ImmutableHashSet? _documentIds; - - private ImmutableDictionary>.Builder? _syntaxLocals; - private ImmutableDictionary>.Builder? _semanticLocals; - private ImmutableDictionary>.Builder? _nonLocals; - private ImmutableArray _others; - - public Builder(Project project, VersionStamp version, ImmutableHashSet? documentIds = null) - { - _project = project; - _version = version; - _documentIds = documentIds; - } - - public void AddSyntaxLocals(DocumentId documentId, ImmutableArray diagnostics) - => Add(ref _syntaxLocals, documentId, diagnostics); - - public void AddSemanticLocals(DocumentId documentId, ImmutableArray diagnostics) - => Add(ref _semanticLocals, documentId, diagnostics); - - public void AddNonLocals(DocumentId documentId, ImmutableArray diagnostics) - => Add(ref _nonLocals, documentId, diagnostics); - - public void AddOthers(ImmutableArray diagnostics) - => _others = diagnostics; - - private void Add(ref ImmutableDictionary>.Builder? locals, DocumentId documentId, ImmutableArray diagnostics) - { - if (_project.GetDocument(documentId)?.SupportsDiagnostics() == false) - { - return; - } - - locals ??= ImmutableDictionary.CreateBuilder>(); - locals.Add(documentId, diagnostics); - } - - public DiagnosticAnalysisResult ToResult() - { - return DiagnosticAnalysisResult.Create(_project, _version, - _syntaxLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, - _semanticLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, - _nonLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, - _others.NullToEmpty(), - _documentIds); - } - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs deleted file mode 100644 index 0cb12b892ce0c..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - private partial class StateManager - { - public IEnumerable GetAllHostStateSets() - { - var analyzerReferences = _workspace.CurrentSolution.SolutionState.Analyzers.HostAnalyzerReferences; - foreach (var (key, value) in _hostAnalyzerStateMap) - { - if (key.AnalyzerReferences == analyzerReferences) - { - foreach (var stateSet in value.OrderedStateSets) - { - yield return stateSet; - } - } - } - } - - private HostAnalyzerStateSets GetOrCreateHostStateSets(Project project, ProjectAnalyzerStateSets projectStateSets) - { - var key = new HostAnalyzerStateSetKey(project.Language, project.State.HasSdkCodeStyleAnalyzers, project.Solution.SolutionState.Analyzers.HostAnalyzerReferences); - // Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the - // Host fallback options. These ids will be used when building up the Host and Project analyzer collections. - var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(project); - var hostStateSets = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (project.Solution.SolutionState.Analyzers, referenceIdsToRedirect)); - return hostStateSets.WithExcludedAnalyzers(projectStateSets.SkippedAnalyzersInfo.SkippedAnalyzers); - - static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(HostAnalyzerStateSetKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet ReferenceIdsToRedirect) state) - { - var language = arg.Language; - var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language); - - var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect); - var analyzerMap = CreateStateSetMap(language, projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true); - - return new HostAnalyzerStateSets(analyzerMap); - } - - static (IEnumerable> HostAnalyzerCollection, IEnumerable> ProjectAnalyzerCollection) GetAnalyzerCollections( - ImmutableDictionary> analyzersPerReference, - ImmutableHashSet referenceIdsToRedirectAsProjectAnalyzers) - { - if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty) - { - return (analyzersPerReference.Values, []); - } - - var hostAnalyzerCollection = ArrayBuilder>.GetInstance(); - var projectAnalyzerCollection = ArrayBuilder>.GetInstance(); - - foreach (var (referenceId, analyzers) in analyzersPerReference) - { - if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId)) - { - projectAnalyzerCollection.Add(analyzers); - } - else - { - hostAnalyzerCollection.Add(analyzers); - } - } - - return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree()); - } - } - - private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers(Project project) - { - if (project.State.HasSdkCodeStyleAnalyzers) - { - // When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the - // Features analyzers. We need to then treat the Features analyzers as Project analyzers so - // they do not get access to the Host fallback options. - return GetFeaturesAnalyzerReferenceIds(project.Solution.SolutionState.Analyzers); - } - - return []; - - static ImmutableHashSet GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers) - { - var builder = ImmutableHashSet.CreateBuilder(); - - foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences) - { - if (analyzerReference.IsFeaturesAnalyzer()) - builder.Add(analyzerReference.Id); - } - - return builder.ToImmutable(); - } - } - - private sealed class HostAnalyzerStateSets - { - private const int FileContentLoadAnalyzerPriority = -4; - private const int GeneratorDiagnosticsPlaceholderAnalyzerPriority = -3; - private const int BuiltInCompilerPriority = -2; - private const int RegularDiagnosticAnalyzerPriority = -1; - - // ordered by priority - public readonly ImmutableArray OrderedStateSets; - - public readonly ImmutableDictionary StateSetMap; - - private HostAnalyzerStateSets(ImmutableDictionary stateSetMap, ImmutableArray orderedStateSets) - { - StateSetMap = stateSetMap; - OrderedStateSets = orderedStateSets; - } - - public HostAnalyzerStateSets(ImmutableDictionary analyzerMap) - { - StateSetMap = analyzerMap; - - // order statesets - // order will be in this order - // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers - OrderedStateSets = [.. StateSetMap.Values.OrderBy(PriorityComparison)]; - } - - public HostAnalyzerStateSets WithExcludedAnalyzers(ImmutableHashSet excludedAnalyzers) - { - if (excludedAnalyzers.IsEmpty) - { - return this; - } - - var stateSetMap = StateSetMap.Where(kvp => !excludedAnalyzers.Contains(kvp.Key)).ToImmutableDictionary(); - var orderedStateSets = OrderedStateSets.WhereAsArray(stateSet => !excludedAnalyzers.Contains(stateSet.Analyzer)); - return new HostAnalyzerStateSets(stateSetMap, orderedStateSets); - } - - private int PriorityComparison(StateSet state1, StateSet state2) - => GetPriority(state1) - GetPriority(state2); - - private static int GetPriority(StateSet state) - { - // compiler gets highest priority - if (state.Analyzer.IsCompilerAnalyzer()) - { - return BuiltInCompilerPriority; - } - - return state.Analyzer switch - { - FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority, - GeneratorDiagnosticsPlaceholderAnalyzer _ => GeneratorDiagnosticsPlaceholderAnalyzerPriority, - DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), - ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), - _ => RegularDiagnosticAnalyzerPriority, - }; - } - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index 38990e7851c12..3c52425994a08 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -4,56 +4,44 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { private partial class StateManager { - private readonly struct ProjectAnalyzerStateSets + private readonly struct ProjectAnalyzerInfo { - public static readonly ProjectAnalyzerStateSets Default = new( - [], - ImmutableDictionary>.Empty, - ImmutableDictionary.Empty, + public static readonly ProjectAnalyzerInfo Default = new( + analyzerReferences: [], + analyzers: [], SkippedHostAnalyzersInfo.Empty); public readonly IReadOnlyList AnalyzerReferences; - // maps analyzer reference id to list of analyzers loaded from the reference - public readonly ImmutableDictionary> MapPerReferences; - - public readonly ImmutableDictionary StateSetMap; + public readonly ImmutableHashSet Analyzers; public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo; - internal ProjectAnalyzerStateSets( + internal ProjectAnalyzerInfo( IReadOnlyList analyzerReferences, - ImmutableDictionary> mapPerReferences, - ImmutableDictionary stateSetMap, + ImmutableHashSet analyzers, SkippedHostAnalyzersInfo skippedAnalyzersInfo) { AnalyzerReferences = analyzerReferences; - MapPerReferences = mapPerReferences; - StateSetMap = stateSetMap; + Analyzers = analyzers; SkippedAnalyzersInfo = skippedAnalyzersInfo; } } - public IEnumerable GetAllProjectStateSets() - { - // return existing state sets - // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap - return _projectAnalyzerStateMap.Values.SelectManyAsArray(e => e.StateSetMap.Values); - } - - private ProjectAnalyzerStateSets? TryGetProjectStateSets(Project project) + private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project) { // check if the analyzer references have changed since the last time we updated the map: // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap @@ -66,127 +54,55 @@ public IEnumerable GetAllProjectStateSets() return null; } - private async Task GetOrCreateProjectStateSetsAsync(Project project, CancellationToken cancellationToken) - => TryGetProjectStateSets(project) ?? await UpdateProjectStateSetsAsync(project, cancellationToken).ConfigureAwait(false); + private async Task GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken) + => TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - /// - /// Creates a new project state sets. - /// - private ProjectAnalyzerStateSets CreateProjectStateSets(Project project) + private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project) { if (project.AnalyzerReferences.Count == 0) { - return ProjectAnalyzerStateSets.Default; + return ProjectAnalyzerInfo.Default; } - var hostAnalyzers = project.Solution.SolutionState.Analyzers; - var analyzersPerReference = hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); + var solutionAnalyzers = solution.Analyzers; + var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); if (analyzersPerReference.Count == 0) { - return ProjectAnalyzerStateSets.Default; + return ProjectAnalyzerInfo.Default; } - var newMap = CreateStateSetMap(project.Language, analyzersPerReference.Values, [], includeWorkspacePlaceholderAnalyzers: false); - var skippedAnalyzersInfo = project.GetSkippedAnalyzersInfo(_analyzerInfoCache); - return new ProjectAnalyzerStateSets(project.AnalyzerReferences, analyzersPerReference, newMap, skippedAnalyzersInfo); + var (newHostAnalyzers, newAllAnalyzers) = PartitionAnalyzers( + analyzersPerReference.Values, hostAnalyzerCollection: [], includeWorkspacePlaceholderAnalyzers: false); + + // We passed an empty array for 'hostAnalyzeCollection' above, and we specifically asked to not include + // workspace placeholder analyzers. So we should never get host analyzers back here. + Contract.ThrowIfTrue(newHostAnalyzers.Count > 0); + + var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); + return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo); } /// /// Updates the map to the given project snapshot. /// - private async Task UpdateProjectStateSetsAsync(Project project, CancellationToken cancellationToken) + private async Task UpdateProjectAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { - ProjectAnalyzerReferenceChangedEventArgs? analyzerReferenceChangedEventArgs = null; - ProjectAnalyzerStateSets? projectStateSets; - // This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets. using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - projectStateSets = TryGetProjectStateSets(project); + var projectAnalyzerInfo = TryGetProjectAnalyzerInfo(project); - if (projectStateSets == null) + if (projectAnalyzerInfo == null) { - projectStateSets = CreateProjectStateSets(project); - - analyzerReferenceChangedEventArgs = GetProjectAnalyzerReferenceChangedEventArgs(project, projectStateSets.Value.MapPerReferences, projectStateSets.Value.StateSetMap); + projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project); // update cache. - _projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectStateSets.Value); - } - } - - if (analyzerReferenceChangedEventArgs != null) - RaiseProjectAnalyzerReferenceChanged(analyzerReferenceChangedEventArgs); - - return projectStateSets.Value; - } - - private ProjectAnalyzerReferenceChangedEventArgs? GetProjectAnalyzerReferenceChangedEventArgs( - Project project, - ImmutableDictionary> newMapPerReference, - ImmutableDictionary newMap) - { - // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap - if (!_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry)) - { - // no previous references and we still don't have any references - if (newMap.Count == 0) - { - return null; + _projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value); } - // new reference added - return new ProjectAnalyzerReferenceChangedEventArgs(project, newMap.Values.ToImmutableArrayOrEmpty(), []); + return projectAnalyzerInfo.Value; } - - Debug.Assert(!entry.AnalyzerReferences.Equals(project.AnalyzerReferences)); - - // there has been change. find out what has changed - var addedStates = DiffStateSets(project.AnalyzerReferences.Except(entry.AnalyzerReferences), newMapPerReference, newMap); - var removedStates = DiffStateSets(entry.AnalyzerReferences.Except(project.AnalyzerReferences), entry.MapPerReferences, entry.StateSetMap); - - // nothing has changed - if (addedStates.Length == 0 && removedStates.Length == 0) - { - return null; - } - - return new ProjectAnalyzerReferenceChangedEventArgs(project, addedStates, removedStates); - } - - private static ImmutableArray DiffStateSets( - IEnumerable references, - ImmutableDictionary> mapPerReference, - ImmutableDictionary map) - { - if (mapPerReference.Count == 0 || map.Count == 0) - { - // nothing to diff - return []; - } - - var builder = ImmutableArray.CreateBuilder(); - foreach (var reference in references) - { - // check duplication - if (!mapPerReference.TryGetValue(reference.Id, out var analyzers)) - { - continue; - } - - // okay, this is real reference. get stateset - foreach (var analyzer in analyzers) - { - if (!map.TryGetValue(analyzer, out var set)) - { - continue; - } - - builder.Add(set); - } - } - - return builder.ToImmutableAndClear(); } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 78e1d431cdbe2..2a46388f88f88 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -2,146 +2,76 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { /// - /// This is in charge of anything related to + /// This is in charge of anything related to /// - private partial class StateManager + private partial class StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache) { - private readonly Workspace _workspace; - private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache; + private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache = analyzerInfoCache; /// /// Analyzers supplied by the host (IDE). These are built-in to the IDE, the compiler, or from an installed IDE extension (VSIX). /// Maps language name to the analyzers and their state. /// - private ImmutableDictionary _hostAnalyzerStateMap; + private ImmutableDictionary _hostAnalyzerStateMap = ImmutableDictionary.Empty; /// /// Analyzers referenced by the project via a PackageReference. Updates are protected by _projectAnalyzerStateMapGuard. /// ImmutableDictionary used to present a safe, non-immutable view to users. /// - private ImmutableDictionary _projectAnalyzerStateMap; + private ImmutableDictionary _projectAnalyzerStateMap = ImmutableDictionary.Empty; /// /// Guard around updating _projectAnalyzerStateMap. This is used in UpdateProjectStateSets to avoid /// duplicated calculations for a project during contentious calls. /// - private readonly SemaphoreSlim _projectAnalyzerStateMapGuard = new(1); - - /// - /// This will be raised whenever finds change - /// - public event EventHandler? ProjectAnalyzerReferenceChanged; - - public StateManager(Workspace workspace, DiagnosticAnalyzerInfoCache analyzerInfoCache) - { - _workspace = workspace; - _analyzerInfoCache = analyzerInfoCache; - - _hostAnalyzerStateMap = ImmutableDictionary.Empty; - _projectAnalyzerStateMap = ImmutableDictionary.Empty; - } + private readonly SemaphoreSlim _projectAnalyzerStateMapGuard = new(initialCount: 1); /// - /// Return s for the given . - /// This will never create new but will return ones already created. + /// Return s for the given . /// - public IEnumerable GetStateSets(ProjectId projectId) + public async Task> GetOrCreateAnalyzersAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { - var hostStateSets = GetAllHostStateSets(); - - // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap - return _projectAnalyzerStateMap.TryGetValue(projectId, out var entry) - ? hostStateSets.Concat(entry.StateSetMap.Values) - : hostStateSets; + var hostAnalyzerInfo = await GetOrCreateHostAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return hostAnalyzerInfo.OrderedAllAnalyzers.AddRange(projectAnalyzerInfo.Analyzers); } - /// - /// Return s for the given . - /// This will never create new but will return ones already created. - /// Difference with is that - /// this will only return s that have same language as . - /// - public IEnumerable GetStateSets(Project project) - => GetStateSets(project.Id).Where(s => s.Language == project.Language); - - /// - /// Return s for the given . - /// This will either return already created s for the specific snapshot of or - /// it will create new s for the and update internal state. - /// - public async Task> GetOrCreateStateSetsAsync(Project project, CancellationToken cancellationToken) + public async Task GetOrCreateHostAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { - var projectStateSets = await GetOrCreateProjectStateSetsAsync(project, cancellationToken).ConfigureAwait(false); - return GetOrCreateHostStateSets(project, projectStateSets).OrderedStateSets.AddRange(projectStateSets.StateSetMap.Values); + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return GetOrCreateHostAnalyzerInfo(solution, project, projectAnalyzerInfo); } - /// - /// Return for the given in the context of . - /// This will either return already created for the specific snapshot of or - /// it will create new for the . and update internal state. - /// - public async Task GetOrCreateStateSetAsync(Project project, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) - { - var projectStateSets = await GetOrCreateProjectStateSetsAsync(project, cancellationToken).ConfigureAwait(false); - if (projectStateSets.StateSetMap.TryGetValue(analyzer, out var stateSet)) - { - return stateSet; - } - - var hostStateSetMap = GetOrCreateHostStateSets(project, projectStateSets).StateSetMap; - if (hostStateSetMap.TryGetValue(analyzer, out stateSet)) - { - return stateSet; - } - - return null; - } - - public bool OnProjectRemoved(IEnumerable stateSets, ProjectId projectId) - { - var removed = false; - foreach (var stateSet in stateSets) - { - removed |= stateSet.OnProjectRemoved(projectId); - } - - lock (_projectAnalyzerStateMap) - { - _projectAnalyzerStateMap = _projectAnalyzerStateMap.Remove(projectId); - } - - return removed; - } - - private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChangedEventArgs args) - => ProjectAnalyzerReferenceChanged?.Invoke(this, args); - - private static ImmutableDictionary CreateStateSetMap( - string language, + private static (ImmutableHashSet hostAnalyzers, ImmutableHashSet allAnalyzers) PartitionAnalyzers( IEnumerable> projectAnalyzerCollection, IEnumerable> hostAnalyzerCollection, bool includeWorkspacePlaceholderAnalyzers) { - var builder = ImmutableDictionary.CreateBuilder(); + using var _1 = PooledHashSet.GetInstance(out var hostAnalyzers); + using var _2 = PooledHashSet.GetInstance(out var allAnalyzers); if (includeWorkspacePlaceholderAnalyzers) { - builder.Add(FileContentLoadAnalyzer.Instance, new StateSet(language, FileContentLoadAnalyzer.Instance, isHostAnalyzer: true)); - builder.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance, new StateSet(language, GeneratorDiagnosticsPlaceholderAnalyzer.Instance, isHostAnalyzer: true)); + hostAnalyzers.Add(FileContentLoadAnalyzer.Instance); + hostAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); + allAnalyzers.Add(FileContentLoadAnalyzer.Instance); + allAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); } foreach (var analyzers in projectAnalyzerCollection) @@ -149,17 +79,7 @@ private static ImmutableDictionary CreateStateSetM foreach (var analyzer in analyzers) { Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - - // TODO: - // #1, all de-duplication should move to DiagnosticAnalyzerInfoCache - // #2, not sure whether de-duplication of analyzer itself makes sense. this can only happen - // if user deliberately put same analyzer twice. - if (builder.ContainsKey(analyzer)) - { - continue; - } - - builder.Add(analyzer, new StateSet(language, analyzer, isHostAnalyzer: false)); + allAnalyzers.Add(analyzer); } } @@ -168,48 +88,16 @@ private static ImmutableDictionary CreateStateSetM foreach (var analyzer in analyzers) { Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - - // TODO: - // #1, all de-duplication should move to DiagnosticAnalyzerInfoCache - // #2, not sure whether de-duplication of analyzer itself makes sense. this can only happen - // if user deliberately put same analyzer twice. - if (builder.ContainsKey(analyzer)) - { - continue; - } - - builder.Add(analyzer, new StateSet(language, analyzer, isHostAnalyzer: true)); + allAnalyzers.Add(analyzer); + hostAnalyzers.Add(analyzer); } } - return builder.ToImmutable(); + return (hostAnalyzers.ToImmutableHashSet(), allAnalyzers.ToImmutableHashSet()); } - private readonly struct HostAnalyzerStateSetKey : IEquatable - { - public HostAnalyzerStateSetKey(string language, bool hasSdkCodeStyleAnalyzers, IReadOnlyList analyzerReferences) - { - Language = language; - HasSdkCodeStyleAnalyzers = hasSdkCodeStyleAnalyzers; - AnalyzerReferences = analyzerReferences; - } - - public string Language { get; } - public bool HasSdkCodeStyleAnalyzers { get; } - public IReadOnlyList AnalyzerReferences { get; } - - public bool Equals(HostAnalyzerStateSetKey other) - => Language == other.Language && - HasSdkCodeStyleAnalyzers == other.HasSdkCodeStyleAnalyzers && - AnalyzerReferences == other.AnalyzerReferences; - - public override bool Equals(object? obj) - => obj is HostAnalyzerStateSetKey key && Equals(key); - - public override int GetHashCode() - => Hash.Combine(Language.GetHashCode(), - Hash.Combine(HasSdkCodeStyleAnalyzers.GetHashCode(), AnalyzerReferences.GetHashCode())); - } + private readonly record struct HostAnalyzerInfoKey( + string Language, bool HasSdkCodeStyleAnalyzers, IReadOnlyList AnalyzerReferences); } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs deleted file mode 100644 index 3a6afd3e5633a..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// this contains all states regarding a - /// - private sealed class StateSet - { - public readonly string Language; - public readonly DiagnosticAnalyzer Analyzer; - public readonly bool IsHostAnalyzer; - - private readonly ConcurrentDictionary _activeFileStates; - private readonly ConcurrentDictionary _projectStates; - - public StateSet(string language, DiagnosticAnalyzer analyzer, bool isHostAnalyzer) - { - Language = language; - Analyzer = analyzer; - IsHostAnalyzer = isHostAnalyzer; - - _activeFileStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); - _projectStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 1); - } - - public IEnumerable GetProjectsWithDiagnostics() - { - // quick bail out - if (_activeFileStates.IsEmpty && _projectStates.IsEmpty) - return []; - - if (_activeFileStates.Count == 1 && _projectStates.IsEmpty) - { - // see whether we actually have diagnostics - var (documentId, state) = _activeFileStates.First(); - if (state.IsEmpty) - return []; - - // we do have diagnostics - return [documentId.ProjectId]; - } - - return new HashSet( - _activeFileStates.Where(kv => !kv.Value.IsEmpty) - .Select(kv => kv.Key.ProjectId) - .Concat(_projectStates.Where(kv => !kv.Value.IsEmpty()) - .Select(kv => kv.Key))); - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/34761", AllowCaptures = false, AllowGenericEnumeration = false)] - public void CollectDocumentsWithDiagnostics(ProjectId projectId, HashSet set) - { - RoslynDebug.Assert(set != null); - - // Collect active documents with diagnostics - - foreach (var (documentId, state) in _activeFileStates) - { - if (documentId.ProjectId == projectId && !state.IsEmpty) - { - set.Add(documentId); - } - } - - if (_projectStates.TryGetValue(projectId, out var projectState) && !projectState.IsEmpty()) - { - set.UnionWith(projectState.GetDocumentsWithDiagnostics()); - } - } - - public bool IsActiveFile(DocumentId documentId) - => _activeFileStates.ContainsKey(documentId); - - public bool FromBuild(ProjectId projectId) - => _projectStates.TryGetValue(projectId, out var projectState) && projectState.FromBuild; - - public bool TryGetActiveFileState(DocumentId documentId, [NotNullWhen(true)] out ActiveFileState? state) - => _activeFileStates.TryGetValue(documentId, out state); - - public bool TryGetProjectState(ProjectId projectId, [NotNullWhen(true)] out ProjectState? state) - => _projectStates.TryGetValue(projectId, out state); - - public ActiveFileState GetOrCreateActiveFileState(DocumentId documentId) - => _activeFileStates.GetOrAdd(documentId, id => new ActiveFileState(id)); - - public ProjectState GetOrCreateProjectState(ProjectId projectId) - => _projectStates.GetOrAdd(projectId, static (id, self) => new ProjectState(self, id), this); - - public async Task OnDocumentOpenedAsync(TextDocument document) - { - // can not be cancelled - if (!TryGetProjectState(document.Project.Id, out var projectState) || - projectState.IsEmpty(document.Id)) - { - // nothing to do - return false; - } - - var result = await projectState.GetAnalysisDataAsync(document, avoidLoadingData: false, CancellationToken.None).ConfigureAwait(false); - var text = await document.GetValueTextAsync(CancellationToken.None).ConfigureAwait(false); - - // store analysis result to active file state: - var activeFileState = GetOrCreateActiveFileState(document.Id); - - activeFileState.Save(AnalysisKind.Syntax, new DocumentAnalysisData(result.Version, text.Lines.Count, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax))); - activeFileState.Save(AnalysisKind.Semantic, new DocumentAnalysisData(result.Version, text.Lines.Count, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic))); - - return true; - } - - public async Task OnDocumentClosedAsync(TextDocument document, IGlobalOptionService globalOptions) - { - // can not be cancelled - // remove active file state and put it in project state - if (!_activeFileStates.TryRemove(document.Id, out var activeFileState)) - { - return false; - } - - // active file exist, put it in the project state - var projectState = GetOrCreateProjectState(document.Project.Id); - await projectState.MergeAsync(activeFileState, document, globalOptions).ConfigureAwait(false); - return true; - } - - public bool OnDocumentReset(TextDocument document) - { - var changed = false; - // can not be cancelled - // remove active file state and put it in project state - if (TryGetActiveFileState(document.Id, out var activeFileState)) - { - activeFileState.ResetVersion(); - changed |= true; - } - - if (TryGetProjectState(document.Project.Id, out var projectState)) - { - projectState.ResetVersion(); - changed |= true; - } - - return changed; - } - - public bool OnDocumentRemoved(DocumentId id) - { - // remove active file state for removed document - var removed = false; - if (_activeFileStates.TryRemove(id, out _)) - { - removed = true; - } - // remove state for the file that got removed. - if (_projectStates.TryGetValue(id.ProjectId, out var state)) - { - removed |= state.OnDocumentRemoved(id); - } - - return removed; - } - - public bool OnProjectRemoved(ProjectId id) - { - // remove state for project that got removed. - if (_projectStates.TryRemove(id, out var state)) - { - return state.OnProjectRemoved(id); - } - - return false; - } - - public void OnRemoved() - { - // ths stateset is being removed. - // TODO: we do this since InMemoryCache is static type. we might consider making it instance object - // of something. - InMemoryStorage.DropCache(Analyzer); - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 50c31315f85ca..65beecdfe360d 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -3,25 +3,24 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { /// /// Diagnostic Analyzer Engine V2 /// /// This one follows pattern compiler has set for diagnostic analyzer. /// - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { private readonly DiagnosticAnalyzerTelemetry _telemetry = new(); private readonly StateManager _stateManager; @@ -29,67 +28,31 @@ internal partial class DiagnosticIncrementalAnalyzer private readonly IncrementalMemberEditAnalyzer _incrementalMemberEditAnalyzer = new(); internal DiagnosticAnalyzerService AnalyzerService { get; } - internal Workspace Workspace { get; } - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService analyzerService, - Workspace workspace, - DiagnosticAnalyzerInfoCache analyzerInfoCache) + DiagnosticAnalyzerInfoCache analyzerInfoCache, + IGlobalOptionService globalOptionService) { Contract.ThrowIfNull(analyzerService); AnalyzerService = analyzerService; - Workspace = workspace; + GlobalOptions = globalOptionService; - _stateManager = new StateManager(workspace, analyzerInfoCache); - _stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; + _stateManager = new StateManager(analyzerInfoCache); - var enabled = this.AnalyzerService.GlobalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler); + var enabled = globalOptionService.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler); _diagnosticAnalyzerRunner = new InProcOrRemoteHostAnalyzerRunner( enabled, analyzerInfoCache, analyzerService.Listener); } - internal IGlobalOptionService GlobalOptions => AnalyzerService.GlobalOptions; + internal IGlobalOptionService GlobalOptions { get; } internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache; - private void OnProjectAnalyzerReferenceChanged(object? sender, ProjectAnalyzerReferenceChangedEventArgs e) - { - if (e.Removed.Length == 0) - { - // nothing to refresh - return; - } - - // make sure we drop cache related to the analyzers - foreach (var stateSet in e.Removed) - stateSet.OnRemoved(); - } - - public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) - => project.GetDependentVersionAsync(cancellationToken); - - private static DiagnosticAnalysisResult GetResultOrEmpty(ImmutableDictionary map, DiagnosticAnalyzer analyzer, ProjectId projectId, VersionStamp version) - { - if (map.TryGetValue(analyzer, out var result)) - { - return result; - } - - return DiagnosticAnalysisResult.CreateEmpty(projectId, version); - } - - internal async Task> GetAnalyzersTestOnlyAsync(Project project, CancellationToken cancellationToken) - { - var analyzers = await _stateManager.GetOrCreateStateSetsAsync(project, cancellationToken).ConfigureAwait(false); - - return analyzers.Select(s => s.Analyzer); - } - - private static string GetProjectLogMessage(Project project, ImmutableArray stateSets) - => $"project: ({project.Id}), ({string.Join(Environment.NewLine, stateSets.Select(s => s.Analyzer.ToString()))})"; + public Task> GetAnalyzersForTestingPurposesOnlyAsync(Project project, CancellationToken cancellationToken) + => _stateManager.GetOrCreateAnalyzersAsync(project.Solution.SolutionState, project.State, cancellationToken); - private static string GetOpenLogMessage(TextDocument document) - => $"document open: ({document.FilePath ?? document.Name})"; + private static string GetProjectLogMessage(Project project, ImmutableArray analyzers) + => $"project: ({project.Id}), ({string.Join(Environment.NewLine, analyzers.Select(a => a.ToString()))})"; } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 0aaa78c089dd6..78746d3e3c66d 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -8,277 +8,142 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { - public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - - public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, getDocuments: null, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); - - private abstract class DiagnosticGetter( - DiagnosticIncrementalAnalyzer owner, - Solution solution, - ProjectId? projectId, - DocumentId? documentId, - Func>? getDocuments, - bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics) + public async Task> GetDiagnosticsForIdsAsync(Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - protected readonly DiagnosticIncrementalAnalyzer Owner = owner; - - protected readonly Solution Solution = solution; - protected readonly ProjectId? ProjectId = projectId ?? documentId?.ProjectId; - protected readonly DocumentId? DocumentId = documentId; - protected readonly bool IncludeLocalDocumentDiagnostics = includeLocalDocumentDiagnostics; - protected readonly bool IncludeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; - - private readonly Func> _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); - - protected StateManager StateManager => Owner._stateManager; - - protected virtual bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) => true; - - protected abstract Task ProduceDiagnosticsAsync( - Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, Action callback, CancellationToken cancellationToken); - - public async Task> GetDiagnosticsAsync(CancellationToken cancellationToken) - { - if (ProjectId != null) - { - var project = Solution.GetProject(ProjectId); - if (project == null) - return []; - - // return diagnostics specific to one project or document - var includeProjectNonLocalResult = DocumentId == null; - return await ProduceProjectDiagnosticsAsync( - [project], project => _getDocuments(project, DocumentId), includeProjectNonLocalResult, cancellationToken).ConfigureAwait(false); - } - - return await ProduceSolutionDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); - } - - protected Task> ProduceSolutionDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) - => ProduceProjectDiagnosticsAsync(solution.Projects, static project => project.DocumentIds, includeProjectNonLocalResult: true, cancellationToken); - - protected async Task> ProduceProjectDiagnosticsAsync( - IEnumerable projects, Func> getDocumentIds, - bool includeProjectNonLocalResult, CancellationToken cancellationToken) - { - // PERF: run projects in parallel rather than running CompilationWithAnalyzer with concurrency == true. - // We do this to not get into thread starvation causing hundreds of threads to be spawned. - return await ProducerConsumer.RunParallelAsync( - source: projects, - produceItems: static (project, callback, args, cancellationToken) => args.@this.ProduceDiagnosticsAsync( - project, args.getDocumentIds(project), args.includeProjectNonLocalResult, callback, cancellationToken), - args: (@this: this, getDocumentIds, includeProjectNonLocalResult), - cancellationToken).ConfigureAwait(false); - } + return await ProduceProjectDiagnosticsAsync( + project, diagnosticIds, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this + // project if no specific document id was requested. + documentId != null ? [documentId] : [.. project.DocumentIds, .. project.AdditionalDocumentIds], + includeLocalDocumentDiagnostics, + includeNonLocalDocumentDiagnostics, + // return diagnostics specific to one project or document + includeProjectNonLocalResult: documentId == null, + cancellationToken).ConfigureAwait(false); + } - protected void InvokeCallback(Action callback, ImmutableArray diagnostics) - { - foreach (var diagnostic in diagnostics) - { - if (ShouldIncludeDiagnostic(diagnostic)) - callback(diagnostic); - } - } + public async Task> GetProjectDiagnosticsForIdsAsync( + Project project, + ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, + bool includeNonLocalDocumentDiagnostics, + CancellationToken cancellationToken) + { + return await ProduceProjectDiagnosticsAsync( + project, diagnosticIds, shouldIncludeAnalyzer, + documentIds: [], + includeLocalDocumentDiagnostics: false, + includeNonLocalDocumentDiagnostics: includeNonLocalDocumentDiagnostics, + includeProjectNonLocalResult: true, + cancellationToken).ConfigureAwait(false); } - private sealed class IdeCachedDiagnosticGetter( - DiagnosticIncrementalAnalyzer owner, - Solution solution, - ProjectId? projectId, - DocumentId? documentId, + private async Task> ProduceProjectDiagnosticsAsync( + Project project, + ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, + IReadOnlyList documentIds, bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics) : DiagnosticGetter( - owner, solution, projectId, documentId, getDocuments: null, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + bool includeNonLocalDocumentDiagnostics, + bool includeProjectNonLocalResult, + CancellationToken cancellationToken) { - protected override async Task ProduceDiagnosticsAsync( - Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, - Action callback, CancellationToken cancellationToken) - { - foreach (var stateSet in StateManager.GetStateSets(project.Id)) - { - foreach (var documentId in documentIds) - { - if (IncludeLocalDocumentDiagnostics) - { - InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false)); - InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false)); - } - - if (IncludeNonLocalDocumentDiagnostics) - InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); - } - - if (includeProjectNonLocalResult) - { - // include project diagnostics if there is no target document - InvokeCallback(callback, await GetProjectStateDiagnosticsAsync(stateSet, project, documentId: null, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); - } - } - } - - public async Task> GetSpecificDiagnosticsAsync(DiagnosticAnalyzer analyzer, AnalysisKind analysisKind, CancellationToken cancellationToken) - { - var project = Solution.GetProject(ProjectId); - if (project == null) - { - // when we return cached result, make sure we at least return something that exist in current solution - return []; - } - - var stateSet = await StateManager.GetOrCreateStateSetAsync(project, analyzer, cancellationToken).ConfigureAwait(false); - if (stateSet == null) - { - return []; - } - - var diagnostics = await GetDiagnosticsAsync(stateSet, project, DocumentId, analysisKind, cancellationToken).ConfigureAwait(false); - - return diagnostics; - } - - private static async Task> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + using var _ = ArrayBuilder.GetInstance(out var builder); - // active file diagnostics: - if (documentId != null && kind != AnalysisKind.NonLocal && stateSet.TryGetActiveFileState(documentId, out var state)) - { - return state.GetAnalysisData(kind).Items; - } + var solution = project.Solution; + var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync( + solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync( + solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); + var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a)); - // project diagnostics: - return await GetProjectStateDiagnosticsAsync(stateSet, project, documentId, kind, cancellationToken).ConfigureAwait(false); - } + var result = await GetOrComputeDiagnosticAnalysisResultsAsync(analyzers).ConfigureAwait(false); - private static async Task> GetProjectStateDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) + foreach (var analyzer in analyzers) { - if (!stateSet.TryGetProjectState(project.Id, out var state)) - { - // never analyzed this project yet. - return []; - } + if (!result.TryGetValue(analyzer, out var analysisResult)) + continue; - if (documentId != null) + foreach (var documentId in documentIds) { - // file doesn't exist in current solution - var document = await project.Solution.GetTextDocumentAsync( - documentId, - cancellationToken).ConfigureAwait(false); - - if (document == null) + if (includeLocalDocumentDiagnostics) { - return []; + AddIncludedDiagnostics(builder, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Syntax)); + AddIncludedDiagnostics(builder, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Semantic)); } - var result = await state.GetAnalysisDataAsync(document, avoidLoadingData: false, cancellationToken).ConfigureAwait(false); - return result.GetDocumentDiagnostics(documentId, kind); + if (includeNonLocalDocumentDiagnostics) + AddIncludedDiagnostics(builder, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.NonLocal)); } - Contract.ThrowIfFalse(kind == AnalysisKind.NonLocal); - var nonLocalResult = await state.GetProjectAnalysisDataAsync(project, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); - return nonLocalResult.GetOtherDiagnostics(); + // include project diagnostics if there is no target document + if (includeProjectNonLocalResult) + AddIncludedDiagnostics(builder, analysisResult.GetOtherDiagnostics()); } - } - private sealed class IdeLatestDiagnosticGetter( - DiagnosticIncrementalAnalyzer owner, - Solution solution, - ProjectId? projectId, - DocumentId? documentId, - ImmutableHashSet? diagnosticIds, - Func? shouldIncludeAnalyzer, - Func>? getDocuments, - bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics) : DiagnosticGetter( - owner, solution, projectId, documentId, getDocuments, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) - { - private readonly ImmutableHashSet? _diagnosticIds = diagnosticIds; - private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; + return builder.ToImmutableAndClear(); - public async Task> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) + bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) + => diagnosticIds == null || diagnosticIds.Contains(diagnostic.Id); + + void AddIncludedDiagnostics(ArrayBuilder builder, ImmutableArray diagnostics) { - if (ProjectId != null) + foreach (var diagnostic in diagnostics) { - var project = Solution.GetProject(ProjectId); - if (project is null) - return []; - - return await ProduceProjectDiagnosticsAsync( - [project], static _ => [], includeProjectNonLocalResult: true, cancellationToken).ConfigureAwait(false); + if (ShouldIncludeDiagnostic(diagnostic)) + builder.Add(diagnostic); } - - return await ProduceSolutionDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); } - protected override bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) - => _diagnosticIds == null || _diagnosticIds.Contains(diagnostic.Id); - - protected override async Task ProduceDiagnosticsAsync( - Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, - Action callback, CancellationToken cancellationToken) + async Task> GetOrComputeDiagnosticAnalysisResultsAsync( + ImmutableArray analyzers) { - // get analyzers that are not suppressed. - var stateSetsForProject = await StateManager.GetOrCreateStateSetsAsync(project, cancellationToken).ConfigureAwait(false); - var stateSets = stateSetsForProject.Where(s => ShouldIncludeStateSet(project, s)).ToImmutableArrayOrEmpty(); - - // unlike the suppressed (disabled) analyzer, we will include hidden diagnostic only analyzers here. - var compilation = await CreateCompilationWithAnalyzersAsync(project, stateSets, Owner.AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - - var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, stateSets, cancellationToken).ConfigureAwait(false); - - foreach (var stateSet in stateSets) + // If there was a 'ForceAnalyzeProjectAsync' run for this project, we can piggy back off of the + // prior computed/cached results as they will be a superset of the results we want. + // + // Note: the caller will loop over *its* analyzers, grabbing from the full set of data we've cached for + // this project, and filtering down further. So it's ok to return this potentially larger set. + // + // Note: While ForceAnalyzeProjectAsync should always run with a larger set of analyzers than us + // (since it runs all analyzers), we still run a paranoia check that the analyzers we care about are + // a subset of that call so that we don't accidentally reuse results that would not correspond to + // what we are computing ourselves. + if (s_projectToForceAnalysisData.TryGetValue(project.State, out var box) && + analyzers.IsSubsetOf(box.Value.analyzers)) { - var analysisResult = result.GetResult(stateSet.Analyzer); - - foreach (var documentId in documentIds) - { - if (IncludeLocalDocumentDiagnostics) - { - InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Syntax)); - InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Semantic)); - } + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); + if (box.Value.checksum == checksum) + return box.Value.diagnosticAnalysisResults; + } - if (IncludeNonLocalDocumentDiagnostics) - InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.NonLocal)); - } + // Otherwise, just compute for the analyzers we care about. + var compilation = await GetOrCreateCompilationWithAnalyzersAsync( + project, analyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - if (includeProjectNonLocalResult) - { - // include project diagnostics if there is no target document - InvokeCallback(callback, analysisResult.GetOtherDiagnostics()); - } - } + var result = await ComputeDiagnosticAnalysisResultsAsync(compilation, project, analyzers, cancellationToken).ConfigureAwait(false); + return result; } - private bool ShouldIncludeStateSet(Project project, StateSet stateSet) + bool ShouldIncludeAnalyzer(Project project, DiagnosticAnalyzer analyzer) { - if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, project, Owner.GlobalOptions)) - { + if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(analyzer, project, this.GlobalOptions)) return false; - } - if (_shouldIncludeAnalyzer != null && !_shouldIncludeAnalyzer(stateSet.Analyzer)) - { + if (shouldIncludeAnalyzer != null && !shouldIncludeAnalyzer(analyzer)) return false; - } - if (_diagnosticIds != null && Owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(stateSet.Analyzer).All(d => !_diagnosticIds.Contains(d.Id))) - { + if (diagnosticIds != null && this.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer).All(d => !diagnosticIds.Contains(d.Id))) return false; - } return true; } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.ProjectAndCompilationWithAnalyzers.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.ProjectAndCompilationWithAnalyzers.cs deleted file mode 100644 index df33398ea07c7..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.ProjectAndCompilationWithAnalyzers.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - private sealed class ProjectAndCompilationWithAnalyzers - { - public Project Project { get; } - public CompilationWithAnalyzersPair? CompilationWithAnalyzers { get; } - - public ProjectAndCompilationWithAnalyzers(Project project, CompilationWithAnalyzersPair? compilationWithAnalyzers) - { - Project = project; - CompilationWithAnalyzers = compilationWithAnalyzers; - } - } - } -} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 2f48d82960e42..48da4ffee222a 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -18,231 +17,144 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { + private static async Task>> ComputeDocumentDiagnosticsCoreAsync( + DocumentAnalysisExecutor executor, + CancellationToken cancellationToken) + { + using var _ = PooledDictionary>.GetInstance(out var builder); + foreach (var analyzer in executor.AnalysisScope.ProjectAnalyzers.ConcatFast(executor.AnalysisScope.HostAnalyzers)) + { + var diagnostics = await ComputeDocumentDiagnosticsForAnalyzerCoreAsync(analyzer, executor, cancellationToken).ConfigureAwait(false); + builder.Add(analyzer, diagnostics); + } + + return builder.ToImmutableDictionary(); + } + + private static async Task> ComputeDocumentDiagnosticsForAnalyzerCoreAsync( + DiagnosticAnalyzer analyzer, + DocumentAnalysisExecutor executor, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var diagnostics = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false); + return diagnostics?.ToImmutableArrayOrEmpty() ?? []; + } + public async Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, - bool includeCompilerDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, - DiagnosticKind diagnosticKinds, + DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var list); + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + + var project = document.Project; + var solutionState = project.Solution.SolutionState; + var unfilteredAnalyzers = await _stateManager + .GetOrCreateAnalyzersAsync(solutionState, project.State, cancellationToken) + .ConfigureAwait(false); + var analyzers = unfilteredAnalyzers + .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, document.Project, GlobalOptions)); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); + + // Note that some callers, such as diagnostic tagger, might pass in a range equal to the entire document span. + // We clear out range for such cases as we are computing full document diagnostics. + if (range == new TextSpan(0, text.Length)) + range = null; + + // We log performance info when we are computing diagnostics for a span + var logPerformanceInfo = range.HasValue; + var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync( + document.Project, analyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); + + // If we are computing full document diagnostics, we will attempt to perform incremental + // member edit analysis. This analysis is currently only enabled with LSP pull diagnostics. + var incrementalAnalysis = !range.HasValue + && document is Document { SupportsSyntaxTree: true }; - var getter = await LatestDiagnosticsForSpanGetter.CreateAsync( - this, document, range, includeCompilerDiagnostics, - priorityProvider, shouldIncludeDiagnostic, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); - await getter.GetAsync(list, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var list); + await GetAsync(list).ConfigureAwait(false); return list.ToImmutableAndClear(); - } - - /// - /// Get diagnostics for given span either by using cache or calculating it on the spot. - /// - private sealed class LatestDiagnosticsForSpanGetter - { - // PERF: Cache the last Project and corresponding CompilationWithAnalyzers used to compute analyzer diagnostics for span. - // This is now required as async lightbulb will query and execute different priority buckets of analyzers with multiple - // calls, and we want to reuse CompilationWithAnalyzers instance if possible. - private static readonly WeakReference s_lastProjectAndCompilationWithAnalyzers = new(null); - - private readonly DiagnosticIncrementalAnalyzer _owner; - private readonly TextDocument _document; - private readonly SourceText _text; - - private readonly ImmutableArray _stateSets; - private readonly CompilationWithAnalyzersPair? _compilationWithAnalyzers; - - private readonly TextSpan? _range; - private readonly ICodeActionRequestPriorityProvider _priorityProvider; - private readonly Func? _shouldIncludeDiagnostic; - private readonly bool _includeCompilerDiagnostics; - private readonly bool _isExplicit; - private readonly bool _logPerformanceInfo; - private readonly bool _incrementalAnalysis; - private readonly DiagnosticKind _diagnosticKind; - - private delegate Task> DiagnosticsGetterAsync(DiagnosticAnalyzer analyzer, DocumentAnalysisExecutor executor, CancellationToken cancellationToken); - - public static async Task CreateAsync( - DiagnosticIncrementalAnalyzer owner, - TextDocument document, - TextSpan? range, - bool includeCompilerDiagnostics, - ICodeActionRequestPriorityProvider priorityProvider, - Func? shouldIncludeDiagnostic, - DiagnosticKind diagnosticKinds, - bool isExplicit, - CancellationToken cancellationToken) - { - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - var unfilteredStateSets = await owner._stateManager - .GetOrCreateStateSetsAsync(document.Project, cancellationToken) - .ConfigureAwait(false); - var stateSets = unfilteredStateSets - .Where(s => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(s.Analyzer, document.Project, owner.GlobalOptions)) - .ToImmutableArray(); - - // Note that some callers, such as diagnostic tagger, might pass in a range equal to the entire document span. - // We clear out range for such cases as we are computing full document diagnostics. - if (range == new TextSpan(0, text.Length)) - range = null; - - // We log performance info when we are computing diagnostics for a span - var logPerformanceInfo = range.HasValue; - var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(document.Project, stateSets, owner.AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - - // If we are computing full document diagnostics, we will attempt to perform incremental - // member edit analysis. This analysis is currently only enabled with LSP pull diagnostics. - var incrementalAnalysis = !range.HasValue - && document is Document { SupportsSyntaxTree: true }; - - return new LatestDiagnosticsForSpanGetter( - owner, compilationWithAnalyzers, document, text, stateSets, shouldIncludeDiagnostic, includeCompilerDiagnostics, - range, priorityProvider, isExplicit, logPerformanceInfo, incrementalAnalysis, diagnosticKinds); - } - - private static async Task GetOrCreateCompilationWithAnalyzersAsync( - Project project, - ImmutableArray stateSets, - bool crashOnAnalyzerException, - CancellationToken cancellationToken) - { - if (s_lastProjectAndCompilationWithAnalyzers.TryGetTarget(out var projectAndCompilationWithAnalyzers) && - projectAndCompilationWithAnalyzers?.Project == project) - { - if (projectAndCompilationWithAnalyzers.CompilationWithAnalyzers == null) - { - return null; - } - - if (HasAllAnalyzers(stateSets, projectAndCompilationWithAnalyzers.CompilationWithAnalyzers)) - { - return projectAndCompilationWithAnalyzers.CompilationWithAnalyzers; - } - } - - var compilationWithAnalyzers = await CreateCompilationWithAnalyzersAsync(project, stateSets, crashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - s_lastProjectAndCompilationWithAnalyzers.SetTarget(new ProjectAndCompilationWithAnalyzers(project, compilationWithAnalyzers)); - return compilationWithAnalyzers; - - static bool HasAllAnalyzers(IEnumerable stateSets, CompilationWithAnalyzersPair compilationWithAnalyzers) - { - foreach (var stateSet in stateSets) - { - if (stateSet.IsHostAnalyzer && !compilationWithAnalyzers.HostAnalyzers.Contains(stateSet.Analyzer)) - return false; - else if (!stateSet.IsHostAnalyzer && !compilationWithAnalyzers.ProjectAnalyzers.Contains(stateSet.Analyzer)) - return false; - } - return true; - } - } - - private LatestDiagnosticsForSpanGetter( - DiagnosticIncrementalAnalyzer owner, - CompilationWithAnalyzersPair? compilationWithAnalyzers, - TextDocument document, - SourceText text, - ImmutableArray stateSets, - Func? shouldIncludeDiagnostic, - bool includeCompilerDiagnostics, - TextSpan? range, - ICodeActionRequestPriorityProvider priorityProvider, - bool isExplicit, - bool logPerformanceInfo, - bool incrementalAnalysis, - DiagnosticKind diagnosticKind) - { - _owner = owner; - _compilationWithAnalyzers = compilationWithAnalyzers; - _document = document; - _text = text; - _stateSets = stateSets; - _shouldIncludeDiagnostic = shouldIncludeDiagnostic; - _includeCompilerDiagnostics = includeCompilerDiagnostics; - _range = range; - _priorityProvider = priorityProvider; - _isExplicit = isExplicit; - _logPerformanceInfo = logPerformanceInfo; - _incrementalAnalysis = incrementalAnalysis; - _diagnosticKind = diagnosticKind; - } - - public async Task GetAsync(ArrayBuilder list, CancellationToken cancellationToken) + async Task GetAsync(ArrayBuilder list) { try { - // Try to get cached diagnostics, and also compute non-cached state sets that need diagnostic computation. - using var _1 = ArrayBuilder.GetInstance(out var syntaxAnalyzers); + using var _1 = ArrayBuilder.GetInstance(out var syntaxAnalyzers); // If we are performing incremental member edit analysis to compute diagnostics incrementally, // we divide the analyzers into those that support span-based incremental analysis and // those that do not support incremental analysis and must be executed for the entire document. // Otherwise, if we are not performing incremental analysis, all semantic analyzers are added // to the span-based analyzer set as we want to compute diagnostics only for the given span. - using var _2 = ArrayBuilder.GetInstance(out var semanticSpanBasedAnalyzers); - using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); + using var _2 = ArrayBuilder.GetInstance(out var semanticSpanBasedAnalyzers); + using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); - using var _4 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{_priorityProvider.Priority.GetPriorityInt()}"); + using var _4 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}"); - foreach (var stateSet in _stateSets) + foreach (var analyzer in analyzers) { - var analyzer = stateSet.Analyzer; - if (!ShouldIncludeAnalyzer(analyzer, _shouldIncludeDiagnostic, _priorityProvider, _owner)) + if (!ShouldIncludeAnalyzer(analyzer, shouldIncludeDiagnostic, priorityProvider, this)) continue; bool includeSyntax = true, includeSemantic = true; - if (_diagnosticKind != DiagnosticKind.All) + if (diagnosticKind != DiagnosticKind.All) { var isCompilerAnalyzer = analyzer.IsCompilerAnalyzer(); includeSyntax = isCompilerAnalyzer - ? _diagnosticKind == DiagnosticKind.CompilerSyntax - : _diagnosticKind == DiagnosticKind.AnalyzerSyntax; + ? diagnosticKind == DiagnosticKind.CompilerSyntax + : diagnosticKind == DiagnosticKind.AnalyzerSyntax; includeSemantic = isCompilerAnalyzer - ? _diagnosticKind == DiagnosticKind.CompilerSemantic - : _diagnosticKind == DiagnosticKind.AnalyzerSemantic; + ? diagnosticKind == DiagnosticKind.CompilerSemantic + : diagnosticKind == DiagnosticKind.AnalyzerSemantic; } includeSyntax = includeSyntax && analyzer.SupportAnalysisKind(AnalysisKind.Syntax); - includeSemantic = includeSemantic && analyzer.SupportAnalysisKind(AnalysisKind.Semantic) && _document is Document; + includeSemantic = includeSemantic && analyzer.SupportAnalysisKind(AnalysisKind.Semantic) && document is Document; if (includeSyntax || includeSemantic) { - var state = stateSet.GetOrCreateActiveFileState(_document.Id); - if (includeSyntax) { - var existingData = state.GetAnalysisData(AnalysisKind.Syntax); - if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Syntax, existingData, list, cancellationToken).ConfigureAwait(false)) - syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, stateSet.IsHostAnalyzer, state, existingData)); + syntaxAnalyzers.Add(analyzer); } if (includeSemantic) { - var existingData = state.GetAnalysisData(AnalysisKind.Semantic); - if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Semantic, existingData, list, cancellationToken).ConfigureAwait(false)) + if (!incrementalAnalysis) { - var stateSets = GetSemanticAnalysisSelectedStates( - stateSet.Analyzer, _incrementalAnalysis, - semanticSpanBasedAnalyzers, semanticDocumentBasedAnalyzers); - - stateSets.Add(new AnalyzerWithState(stateSet.Analyzer, stateSet.IsHostAnalyzer, state, existingData)); + // For non-incremental analysis, we always attempt to compute all + // analyzer diagnostics for the requested span. + semanticSpanBasedAnalyzers.Add(analyzer); + } + else if (analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis()) + { + // We can perform incremental analysis only for analyzers that support + // span-based semantic diagnostic analysis. + semanticSpanBasedAnalyzers.Add(analyzer); + } + else + { + semanticDocumentBasedAnalyzers.Add(analyzer); } } } } - // Compute diagnostics for non-cached state sets. - await ComputeDocumentDiagnosticsAsync(syntaxAnalyzers.ToImmutable(), AnalysisKind.Syntax, _range, list, incrementalAnalysis: false, cancellationToken).ConfigureAwait(false); - await ComputeDocumentDiagnosticsAsync(semanticSpanBasedAnalyzers.ToImmutable(), AnalysisKind.Semantic, _range, list, _incrementalAnalysis, cancellationToken).ConfigureAwait(false); + await ComputeDocumentDiagnosticsAsync(syntaxAnalyzers.ToImmutable(), AnalysisKind.Syntax, range, list, incrementalAnalysis: false, cancellationToken).ConfigureAwait(false); + await ComputeDocumentDiagnosticsAsync(semanticSpanBasedAnalyzers.ToImmutable(), AnalysisKind.Semantic, range, list, incrementalAnalysis, cancellationToken).ConfigureAwait(false); await ComputeDocumentDiagnosticsAsync(semanticDocumentBasedAnalyzers.ToImmutable(), AnalysisKind.Semantic, span: null, list, incrementalAnalysis: false, cancellationToken).ConfigureAwait(false); return; @@ -251,95 +163,44 @@ public async Task GetAsync(ArrayBuilder list, CancellationToken { throw ExceptionUtilities.Unreachable(); } - - // Local functions - static bool ShouldIncludeAnalyzer( - DiagnosticAnalyzer analyzer, - Func? shouldIncludeDiagnostic, - ICodeActionRequestPriorityProvider priorityProvider, - DiagnosticIncrementalAnalyzer owner) - { - // Skip executing analyzer if its priority does not match the request priority. - if (!priorityProvider.MatchesPriority(analyzer)) - return false; - - // Special case DocumentDiagnosticAnalyzer to never skip these document analyzers - // based on 'shouldIncludeDiagnostic' predicate. More specifically, TS has special document - // analyzer which report 0 supported diagnostics, but we always want to execute it. - if (analyzer is DocumentDiagnosticAnalyzer) - return true; - - // Special case GeneratorDiagnosticsPlaceholderAnalyzer to never skip it based on - // 'shouldIncludeDiagnostic' predicate. More specifically, this is a placeholder analyzer - // for threading through all source generator reported diagnostics, but this special analyzer - // reports 0 supported diagnostics, and we always want to execute it. - if (analyzer is GeneratorDiagnosticsPlaceholderAnalyzer) - return true; - - // Skip analyzer if none of its reported diagnostics should be included. - if (shouldIncludeDiagnostic != null && - !owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer).Any(static (a, shouldIncludeDiagnostic) => shouldIncludeDiagnostic(a.Id), shouldIncludeDiagnostic)) - { - return false; - } - - return true; - } - - static ArrayBuilder GetSemanticAnalysisSelectedStates( - DiagnosticAnalyzer analyzer, - bool incrementalAnalysis, - ArrayBuilder semanticSpanBasedAnalyzers, - ArrayBuilder semanticDocumentBasedAnalyzers) - { - if (!incrementalAnalysis) - { - // For non-incremental analysis, we always attempt to compute all - // analyzer diagnostics for the requested span. - return semanticSpanBasedAnalyzers; - } - else - { - // We can perform incremental analysis only for analyzers that support - // span-based semantic diagnostic analysis. - return analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis() - ? semanticSpanBasedAnalyzers - : semanticDocumentBasedAnalyzers; - } - } } - /// - /// Returns if we were able to add the cached diagnostics and we do not need to compute them fresh. - /// - private async Task TryAddCachedDocumentDiagnosticsAsync( + // Local functions + static bool ShouldIncludeAnalyzer( DiagnosticAnalyzer analyzer, - AnalysisKind kind, - DocumentAnalysisData existingData, - ArrayBuilder list, - CancellationToken cancellationToken) + Func? shouldIncludeDiagnostic, + ICodeActionRequestPriorityProvider priorityProvider, + DiagnosticIncrementalAnalyzer owner) { - Debug.Assert(analyzer.SupportAnalysisKind(kind)); - Debug.Assert(_priorityProvider.MatchesPriority(analyzer)); - - // see whether we can use existing info - var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); - if (existingData.Version == version) - { - foreach (var item in existingData.Items) - { - if (ShouldInclude(item)) - list.Add(item); - } + // Skip executing analyzer if its priority does not match the request priority. + if (!priorityProvider.MatchesPriority(analyzer)) + return false; + + // Special case DocumentDiagnosticAnalyzer to never skip these document analyzers + // based on 'shouldIncludeDiagnostic' predicate. More specifically, TS has special document + // analyzer which report 0 supported diagnostics, but we always want to execute it. + if (analyzer is DocumentDiagnosticAnalyzer) + return true; + // Special case GeneratorDiagnosticsPlaceholderAnalyzer to never skip it based on + // 'shouldIncludeDiagnostic' predicate. More specifically, this is a placeholder analyzer + // for threading through all source generator reported diagnostics, but this special analyzer + // reports 0 supported diagnostics, and we always want to execute it. + if (analyzer is GeneratorDiagnosticsPlaceholderAnalyzer) return true; + + // Skip analyzer if none of its reported diagnostics should be included. + if (shouldIncludeDiagnostic != null && + !owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer).Any(static (a, shouldIncludeDiagnostic) => shouldIncludeDiagnostic(a.Id), shouldIncludeDiagnostic)) + { + return false; } - return false; + return true; } - private async Task ComputeDocumentDiagnosticsAsync( - ImmutableArray analyzersWithState, + async Task ComputeDocumentDiagnosticsAsync( + ImmutableArray analyzers, AnalysisKind kind, TextSpan? span, ArrayBuilder builder, @@ -347,188 +208,137 @@ private async Task ComputeDocumentDiagnosticsAsync( CancellationToken cancellationToken) { Debug.Assert(!incrementalAnalysis || kind == AnalysisKind.Semantic); - Debug.Assert(!incrementalAnalysis || analyzersWithState.All(analyzerWithState => analyzerWithState.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); + Debug.Assert(!incrementalAnalysis || analyzers.All(analyzer => analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); - using var _ = ArrayBuilder.GetInstance(analyzersWithState.Length, out var filteredAnalyzersWithStateBuilder); - foreach (var analyzerWithState in analyzersWithState) + using var _ = ArrayBuilder.GetInstance(analyzers.Length, out var filteredAnalyzers); + foreach (var analyzer in analyzers) { - Debug.Assert(_priorityProvider.MatchesPriority(analyzerWithState.Analyzer)); + Debug.Assert(priorityProvider.MatchesPriority(analyzer)); // Check if this is an expensive analyzer that needs to be de-prioritized to a lower priority bucket. // If so, we skip this analyzer from execution in the current priority bucket. // We will subsequently execute this analyzer in the lower priority bucket. - if (await TryDeprioritizeAnalyzerAsync(analyzerWithState.Analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) + if (await TryDeprioritizeAnalyzerAsync(analyzer, kind, span).ConfigureAwait(false)) { continue; } - filteredAnalyzersWithStateBuilder.Add(analyzerWithState); + filteredAnalyzers.Add(analyzer); } - if (filteredAnalyzersWithStateBuilder.Count == 0) + if (filteredAnalyzers.Count == 0) return; - analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); + analyzers = filteredAnalyzers.ToImmutable(); + + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); - var projectAnalyzers = analyzersWithState.SelectAsArray(stateSet => !stateSet.IsHostAnalyzer, stateSet => stateSet.Analyzer); - var hostAnalyzers = analyzersWithState.SelectAsArray(stateSet => stateSet.IsHostAnalyzer, stateSet => stateSet.Analyzer); - var analysisScope = new DocumentAnalysisScope(_document, span, projectAnalyzers, hostAnalyzers, kind); - var executor = new DocumentAnalysisExecutor(analysisScope, _compilationWithAnalyzers, _owner._diagnosticAnalyzerRunner, _isExplicit, _logPerformanceInfo); - var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); + var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo); + var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo); + var analysisScope = new DocumentAnalysisScope(document, span, projectAnalyzers, hostAnalyzers, kind); + var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, isExplicit, logPerformanceInfo); + var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); ImmutableDictionary> diagnosticsMap; if (incrementalAnalysis) { - using var _2 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{_priorityProvider.Priority.GetPriorityInt()}.Incremental"); + using var _2 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.Incremental"); - diagnosticsMap = await _owner._incrementalMemberEditAnalyzer.ComputeDiagnosticsAsync( + diagnosticsMap = await _incrementalMemberEditAnalyzer.ComputeDiagnosticsAsync( executor, - analyzersWithState, + analyzers, version, - ComputeDocumentDiagnosticsForAnalyzerCoreAsync, - ComputeDocumentDiagnosticsCoreAsync, cancellationToken).ConfigureAwait(false); } else { - using var _2 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{_priorityProvider.Priority.GetPriorityInt()}.Document"); + using var _2 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.Document"); diagnosticsMap = await ComputeDocumentDiagnosticsCoreAsync(executor, cancellationToken).ConfigureAwait(false); } - foreach (var analyzerWithState in analyzersWithState) + foreach (var analyzer in analyzers) { - var diagnostics = diagnosticsMap[analyzerWithState.Analyzer]; + var diagnostics = diagnosticsMap[analyzer]; builder.AddRange(diagnostics.Where(ShouldInclude)); } if (incrementalAnalysis) - _owner._incrementalMemberEditAnalyzer.UpdateDocumentWithCachedDiagnostics((Document)_document); + _incrementalMemberEditAnalyzer.UpdateDocumentWithCachedDiagnostics((Document)document); + } - async Task TryDeprioritizeAnalyzerAsync(DiagnosticAnalyzer analyzer, DocumentAnalysisData existingData) + async Task TryDeprioritizeAnalyzerAsync( + DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? span) + { + // PERF: In order to improve lightbulb performance, we perform de-prioritization optimization for certain analyzers + // that moves the analyzer to a lower priority bucket. However, to ensure that de-prioritization happens for very rare cases, + // we only perform this optimizations when following conditions are met: + // 1. We are performing semantic span-based analysis. + // 2. We are processing 'CodeActionRequestPriority.Normal' priority request. + // 3. Analyzer registers certain actions that are known to lead to high performance impact due to its broad analysis scope, + // such as SymbolStart/End actions and SemanticModel actions. + // 4. Analyzer did not report a diagnostic on the same line in prior document snapshot. + + // Conditions 1. and 2. + if (kind != AnalysisKind.Semantic || + !span.HasValue || + priorityProvider.Priority != CodeActionRequestPriority.Default) { - // PERF: In order to improve lightbulb performance, we perform de-prioritization optimization for certain analyzers - // that moves the analyzer to a lower priority bucket. However, to ensure that de-prioritization happens for very rare cases, - // we only perform this optimizations when following conditions are met: - // 1. We are performing semantic span-based analysis. - // 2. We are processing 'CodeActionRequestPriority.Normal' priority request. - // 3. Analyzer registers certain actions that are known to lead to high performance impact due to its broad analysis scope, - // such as SymbolStart/End actions and SemanticModel actions. - // 4. Analyzer did not report a diagnostic on the same line in prior document snapshot. - - // Conditions 1. and 2. - if (kind != AnalysisKind.Semantic || - !span.HasValue || - _priorityProvider.Priority != CodeActionRequestPriority.Default) - { - return false; - } - - Debug.Assert(span.Value.Length < _text.Length); - - // Condition 3. - // Check if this is a candidate analyzer that can be de-prioritized into a lower priority bucket based on registered actions. - if (!await IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(analyzer).ConfigureAwait(false)) - { - return false; - } - - // Condition 4. - // We do not want to de-prioritize this analyzer if it reported a diagnostic on a prior document snapshot, - // such that diagnostic's start/end lines intersect the current analysis span's start/end lines. - // If an analyzer reported such a diagnostic, it is highly likely that the user intends to invoke the code fix - // for this diagnostic. Additionally, it is also highly likely that this analyzer will report a diagnostic - // on the current snapshot. So, we deem this as an important analyzer that should not be de-prioritized here. - // Note that we only perform this analysis if the prior document, whose existingData is cached, had same number - // of source lines as the current document snapshot. Otherwise, the start/end lines comparison across - // snapshots is not meaningful. - if (existingData.LineCount == _text.Lines.Count && - !existingData.Items.IsEmpty) - { - _text.GetLinesAndOffsets(span.Value, out var startLineNumber, out var _, out var endLineNumber, out var _); - - foreach (var diagnostic in existingData.Items) - { - if (diagnostic.DataLocation.UnmappedFileSpan.StartLinePosition.Line <= endLineNumber && - diagnostic.DataLocation.UnmappedFileSpan.EndLinePosition.Line >= startLineNumber) - { - return false; - } - } - } - - // 'LightbulbSkipExecutingDeprioritizedAnalyzers' option determines if we want to execute this analyzer - // in low priority bucket or skip it completely. If the option is not set, track the de-prioritized - // analyzer to be executed in low priority bucket. - // Note that 'AddDeprioritizedAnalyzerWithLowPriority' call below mutates the state in the provider to - // track this analyzer. This ensures that when the owner of this provider calls us back to execute - // the low priority bucket, we can still get back to this analyzer and execute it that time. - if (!_owner.GlobalOptions.GetOption(DiagnosticOptionsStorage.LightbulbSkipExecutingDeprioritizedAnalyzers)) - _priorityProvider.AddDeprioritizedAnalyzerWithLowPriority(analyzer); - - return true; + return false; } - // Returns true if this is an analyzer that is a candidate to be de-prioritized to - // 'CodeActionRequestPriority.Low' priority for improvement in analyzer - // execution performance for priority buckets above 'Low' priority. - // Based on performance measurements, currently only analyzers which register SymbolStart/End actions - // or SemanticModel actions are considered candidates to be de-prioritized. However, these semantics - // could be changed in future based on performance measurements. - async Task IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(DiagnosticAnalyzer analyzer) + Debug.Assert(span.Value.Length < text.Length); + + // Condition 3. + // Check if this is a candidate analyzer that can be de-prioritized into a lower priority bucket based on registered actions. + if (!await IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(analyzer).ConfigureAwait(false)) { - // We deprioritize SymbolStart/End and SemanticModel analyzers from 'Normal' to 'Low' priority bucket, - // as these are computationally more expensive. - // Note that we never de-prioritize compiler analyzer, even though it registers a SemanticModel action. - if (_compilationWithAnalyzers == null || - analyzer.IsWorkspaceDiagnosticAnalyzer() || - analyzer.IsCompilerAnalyzer()) - { - return false; - } + return false; + } - var telemetryInfo = await _compilationWithAnalyzers.GetAnalyzerTelemetryInfoAsync(analyzer, cancellationToken).ConfigureAwait(false); - if (telemetryInfo == null) - return false; + // 'LightbulbSkipExecutingDeprioritizedAnalyzers' option determines if we want to execute this analyzer + // in low priority bucket or skip it completely. If the option is not set, track the de-prioritized + // analyzer to be executed in low priority bucket. + // Note that 'AddDeprioritizedAnalyzerWithLowPriority' call below mutates the state in the provider to + // track this analyzer. This ensures that when the owner of this provider calls us back to execute + // the low priority bucket, we can still get back to this analyzer and execute it that time. + if (!this.GlobalOptions.GetOption(DiagnosticOptionsStorage.LightbulbSkipExecutingDeprioritizedAnalyzers)) + priorityProvider.AddDeprioritizedAnalyzerWithLowPriority(analyzer); - return telemetryInfo.SymbolStartActionsCount > 0 || telemetryInfo.SemanticModelActionsCount > 0; - } + return true; } - private async Task>> ComputeDocumentDiagnosticsCoreAsync( - DocumentAnalysisExecutor executor, - CancellationToken cancellationToken) + // Returns true if this is an analyzer that is a candidate to be de-prioritized to + // 'CodeActionRequestPriority.Low' priority for improvement in analyzer + // execution performance for priority buckets above 'Low' priority. + // Based on performance measurements, currently only analyzers which register SymbolStart/End actions + // or SemanticModel actions are considered candidates to be de-prioritized. However, these semantics + // could be changed in future based on performance measurements. + async Task IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(DiagnosticAnalyzer analyzer) { - using var _ = PooledDictionary>.GetInstance(out var builder); - foreach (var analyzer in executor.AnalysisScope.ProjectAnalyzers.ConcatFast(executor.AnalysisScope.HostAnalyzers)) + // We deprioritize SymbolStart/End and SemanticModel analyzers from 'Normal' to 'Low' priority bucket, + // as these are computationally more expensive. + // Note that we never de-prioritize compiler analyzer, even though it registers a SemanticModel action. + if (compilationWithAnalyzers == null || + analyzer.IsWorkspaceDiagnosticAnalyzer() || + analyzer.IsCompilerAnalyzer()) { - var diagnostics = await ComputeDocumentDiagnosticsForAnalyzerCoreAsync(analyzer, executor, cancellationToken).ConfigureAwait(false); - builder.Add(analyzer, diagnostics); + return false; } - return builder.ToImmutableDictionary(); - } - - private async Task> ComputeDocumentDiagnosticsForAnalyzerCoreAsync( - DiagnosticAnalyzer analyzer, - DocumentAnalysisExecutor executor, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + var telemetryInfo = await compilationWithAnalyzers.GetAnalyzerTelemetryInfoAsync(analyzer, cancellationToken).ConfigureAwait(false); + if (telemetryInfo == null) + return false; - var diagnostics = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false); - return diagnostics?.ToImmutableArrayOrEmpty() ?? []; + return telemetryInfo.SymbolStartActionsCount > 0 || telemetryInfo.SemanticModelActionsCount > 0; } - private bool ShouldInclude(DiagnosticData diagnostic) + bool ShouldInclude(DiagnosticData diagnostic) { - return diagnostic.DocumentId == _document.Id && - (_range == null || _range.Value.IntersectsWith(diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(_text))) - && (_includeCompilerDiagnostics || !diagnostic.CustomTags.Any(static t => t is WellKnownDiagnosticTags.Compiler)) - && (_shouldIncludeDiagnostic == null || _shouldIncludeDiagnostic(diagnostic.Id)); + return diagnostic.DocumentId == document.Id && + (range == null || range.Value.IntersectsWith(diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))) + && (shouldIncludeDiagnostic == null || shouldIncludeDiagnostic(diagnostic.Id)); } } - - private sealed record class AnalyzerWithState(DiagnosticAnalyzer Analyzer, bool IsHostAnalyzer, ActiveFileState State, DocumentAnalysisData ExistingData); } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 93ba079476f40..889161e4e2647 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -4,50 +4,65 @@ using System; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Workspaces.Diagnostics; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +namespace Microsoft.CodeAnalysis.Diagnostics; + +internal partial class DiagnosticAnalyzerService { - internal partial class DiagnosticIncrementalAnalyzer + private partial class DiagnosticIncrementalAnalyzer { + /// + /// Cached data from a real instance to the cached diagnostic data produced by + /// all the analyzers for the project. This data can then be used by to speed up subsequent calls through the normal entry points as long as the project hasn't changed at all. + /// + /// + /// This table is keyed off of but stores data from on + /// it. Specifically . Normally keying off a ProjectState would not be ok + /// as the ProjectState might stay the same while the SolutionState changed. However, that can't happen as + /// SolutionState has the data for Analyzers computed prior to Projects being added, and then never changes. + /// Practically, solution analyzers are the core Roslyn analyzers themselves we distribute, or analyzers shipped + /// by vsix (not nuget). These analyzers do not get loaded after changing *until* VS restarts. + /// + private static readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> s_projectToForceAnalysisData = new(); + public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { + var projectState = project.State; + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); + try { - var stateSetsForProject = await _stateManager.GetOrCreateStateSetsAsync(project, cancellationToken).ConfigureAwait(false); - var stateSets = GetStateSetsForFullSolutionAnalysis(stateSetsForProject, project); - - // PERF: get analyzers that are not suppressed and marked as open file only - // this is perf optimization. we cache these result since we know the result. (no diagnostics) - var activeProjectAnalyzers = stateSets.SelectAsArray(s => !s.IsHostAnalyzer, s => s.Analyzer); - var activeHostAnalyzers = stateSets.SelectAsArray(s => s.IsHostAnalyzer, s => s.Analyzer); - - CompilationWithAnalyzersPair? compilationWithAnalyzers = null; - - compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync( - project, activeProjectAnalyzers, activeHostAnalyzers, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - - var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, stateSets, cancellationToken).ConfigureAwait(false); + if (!s_projectToForceAnalysisData.TryGetValue(projectState, out var box) || + box.Value.checksum != checksum) + { + box = new(await ComputeForceAnalyzeProjectAsync().ConfigureAwait(false)); + + // Try to add the new computed data to the CWT. But use any existing value that another thread + // might have beaten us to storing in it. +#if NET + if (!s_projectToForceAnalysisData.TryAdd(projectState, box)) + Contract.ThrowIfFalse(s_projectToForceAnalysisData.TryGetValue(projectState, out box)); +#else + box = s_projectToForceAnalysisData.GetValue(projectState, _ => box); +#endif + } using var _ = ArrayBuilder.GetInstance(out var diagnostics); - // no cancellation after this point. - foreach (var stateSet in stateSets) + var (_, analyzers, projectAnalysisData) = box.Value; + foreach (var analyzer in analyzers) { - var state = stateSet.GetOrCreateProjectState(project.Id); - - if (result.TryGetResult(stateSet.Analyzer, out var analyzerResult)) - { + if (projectAnalysisData.TryGetValue(analyzer, out var analyzerResult)) diagnostics.AddRange(analyzerResult.GetAllDiagnostics()); - await state.SaveToInMemoryStorageAsync(project, analyzerResult).ConfigureAwait(false); - } } return diagnostics.ToImmutableAndClear(); @@ -56,104 +71,68 @@ public async Task> ForceAnalyzeProjectAsync(Proje { throw ExceptionUtilities.Unreachable(); } - } - private async Task TextDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) + async Task<(Checksum checksum, ImmutableArray analyzers, ImmutableDictionary diagnosticAnalysisResults)> ComputeForceAnalyzeProjectAsync() { - var stateSets = _stateManager.GetStateSets(document.Project); + var solutionState = project.Solution.SolutionState; + var allAnalyzers = await _stateManager.GetOrCreateAnalyzersAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); - // can not be canceled - foreach (var stateSet in stateSets) - await stateSet.OnDocumentOpenedAsync(document).ConfigureAwait(false); - } - } + var fullSolutionAnalysisAnalyzers = allAnalyzers.WhereAsArray( + static (analyzer, arg) => IsCandidateForFullSolutionAnalysis( + arg.self.DiagnosticAnalyzerInfoCache, analyzer, arg.hostAnalyzerInfo.IsHostAnalyzer(analyzer), arg.project), + (self: this, project, hostAnalyzerInfo)); - /// - /// Return list of to be used for full solution analysis. - /// - private ImmutableArray GetStateSetsForFullSolutionAnalysis(ImmutableArray stateSets, Project project) - { - // If full analysis is off, remove state that is created from build. - // this will make sure diagnostics from build (converted from build to live) will never be cleared - // until next build. - _ = GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!compilerFullSolutionAnalysisEnabled) - { - // Full solution analysis is not enabled for compiler diagnostics, - // so we remove the compiler analyzer state sets that are from build. - // We do so by retaining only those state sets that are - // either not for compiler analyzer or those which are for compiler - // analyzer, but not from build. - stateSets = stateSets.WhereAsArray(s => !s.Analyzer.IsCompilerAnalyzer() || !s.FromBuild(project.Id)); - } + var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync( + project, fullSolutionAnalysisAnalyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); - if (!analyzersFullSolutionAnalysisEnabled) - { - // Full solution analysis is not enabled for analyzer diagnostics, - // so we remove the analyzer state sets that are from build. - // We do so by retaining only those state sets that are - // either for the special compiler/workspace analyzers or those which are for - // other analyzers, but not from build. - stateSets = stateSets.WhereAsArray(s => s.Analyzer.IsCompilerAnalyzer() || s.Analyzer.IsWorkspaceDiagnosticAnalyzer() || !s.FromBuild(project.Id)); + var projectAnalysisData = await ComputeDiagnosticAnalysisResultsAsync(compilationWithAnalyzers, project, fullSolutionAnalysisAnalyzers, cancellationToken).ConfigureAwait(false); + return (checksum, fullSolutionAnalysisAnalyzers, projectAnalysisData); } - // Include only analyzers we want to run for full solution analysis. - // Analyzers not included here will never be saved because result is unknown. - return stateSets.WhereAsArray(static (s, arg) => arg.self.IsCandidateForFullSolutionAnalysis(s.Analyzer, s.IsHostAnalyzer, arg.project), (self: this, project)); - } - - private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, bool isHostAnalyzer, Project project) - { - // PERF: Don't query descriptors for compiler analyzer or workspace load analyzer, always execute them. - if (analyzer == FileContentLoadAnalyzer.Instance || - analyzer == GeneratorDiagnosticsPlaceholderAnalyzer.Instance || - analyzer.IsCompilerAnalyzer()) - { - return true; - } - - if (analyzer.IsBuiltInAnalyzer()) + static bool IsCandidateForFullSolutionAnalysis( + DiagnosticAnalyzerInfoCache infoCache, DiagnosticAnalyzer analyzer, bool isHostAnalyzer, Project project) { - // always return true for builtin analyzer. we can't use - // descriptor check since many builtin analyzer always return - // hidden descriptor regardless what descriptor it actually - // return on runtime. they do this so that they can control - // severity through option page rather than rule set editor. - // this is special behavior only ide analyzer can do. we hope - // once we support editorconfig fully, third party can use this - // ability as well and we can remove this kind special treatment on builtin - // analyzer. - return true; - } - - if (analyzer is DiagnosticSuppressor) - { - // Always execute diagnostic suppressors. - return true; - } + // PERF: Don't query descriptors for compiler analyzer or workspace load analyzer, always execute them. + if (analyzer == FileContentLoadAnalyzer.Instance || + analyzer == GeneratorDiagnosticsPlaceholderAnalyzer.Instance || + analyzer.IsCompilerAnalyzer()) + { + return true; + } - if (project.CompilationOptions is null) - { - // Skip compilation options based checks for non-C#/VB projects. - return true; - } + if (analyzer.IsBuiltInAnalyzer()) + { + // always return true for builtin analyzer. we can't use + // descriptor check since many builtin analyzer always return + // hidden descriptor regardless what descriptor it actually + // return on runtime. they do this so that they can control + // severity through option page rather than rule set editor. + // this is special behavior only ide analyzer can do. we hope + // once we support editorconfig fully, third party can use this + // ability as well and we can remove this kind special treatment on builtin + // analyzer. + return true; + } - // For most of analyzers, the number of diagnostic descriptors is small, so this should be cheap. - var descriptors = DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer); - var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); + if (analyzer is DiagnosticSuppressor) + { + // Always execute diagnostic suppressors. + return true; + } - return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity(arg.CompilationOptions, arg.isHostAnalyzer ? arg.analyzerConfigOptions?.ConfigOptionsWithFallback : arg.analyzerConfigOptions?.ConfigOptionsWithoutFallback, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, (project.CompilationOptions, isHostAnalyzer, analyzerConfigOptions)); - } + if (project.CompilationOptions is null) + { + // Skip compilation options based checks for non-C#/VB projects. + return true; + } - public TestAccessor GetTestAccessor() - => new(this); + // For most of analyzers, the number of diagnostic descriptors is small, so this should be cheap. + var descriptors = infoCache.GetDiagnosticDescriptors(analyzer); + var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); - public readonly struct TestAccessor(DiagnosticIncrementalAnalyzer diagnosticIncrementalAnalyzer) - { - public Task TextDocumentOpenAsync(TextDocument document) - => diagnosticIncrementalAnalyzer.TextDocumentOpenAsync(document, CancellationToken.None); + return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity(arg.CompilationOptions, arg.isHostAnalyzer ? arg.analyzerConfigOptions?.ConfigOptionsWithFallback : arg.analyzerConfigOptions?.ConfigOptionsWithoutFallback, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, (project.CompilationOptions, isHostAnalyzer, analyzerConfigOptions)); + } } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs similarity index 96% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs rename to src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs index 3fced9e7cdeff..d43c3016d6487 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -120,8 +119,6 @@ private async Task.Empty; if (analysisResult is not null) { var map = await analysisResult.ToResultBuilderMapAsync( - additionalPragmaSuppressionDiagnostics, documentAnalysisScope, project, version, + additionalPragmaSuppressionDiagnostics, documentAnalysisScope, project, projectAnalyzers, hostAnalyzers, skippedAnalyzersInfo, cancellationToken).ConfigureAwait(false); builderMap = builderMap.AddRange(map); } @@ -237,9 +234,6 @@ private static async Task.Empty; } - // handling of cancellation and exception - var version = await DiagnosticIncrementalAnalyzer.GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var documentIds = (documentAnalysisScope != null) ? ImmutableHashSet.Create(documentAnalysisScope.TextDocument.Id) : null; return new DiagnosticAnalysisResultMap( @@ -247,7 +241,6 @@ private static async Task IReadOnlyDictionaryExtensions.GetValueOrDefault(projectAnalyzerMap, entry.analyzerId) ?? hostAnalyzerMap[entry.analyzerId], entry => DiagnosticAnalysisResult.Create( project, - version, syntaxLocalMap: Hydrate(entry.diagnosticMap.Syntax, project), semanticLocalMap: Hydrate(entry.diagnosticMap.Semantic, project), nonLocalMap: Hydrate(entry.diagnosticMap.NonLocal, project), diff --git a/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs b/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs index 5eec735c049bd..f4b0498caabc9 100644 --- a/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs +++ b/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; using StreamJsonRpc; diff --git a/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs b/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs index 6ed2dfe1ef6f1..aefec686e77a4 100644 --- a/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs +++ b/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs @@ -28,6 +28,7 @@ internal sealed class CompletionCapabilityHelper public bool SupportsMarkdownDocumentation { get; } public ISet SupportedItemKinds { get; } public ISet SupportedItemTags { get; } + public ISet SupportedInsertTextModes { get; } public CompletionCapabilityHelper(ClientCapabilities clientCapabilities) : this(supportsVSExtensions: clientCapabilities.HasVisualStudioLspCapability(), @@ -45,6 +46,7 @@ public CompletionCapabilityHelper(bool supportsVSExtensions, CompletionSetting? SupportDefaultCommitCharacters = completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(CommitCharactersPropertyName) == true; SupportedItemKinds = completionSetting?.CompletionItemKind?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); SupportedItemTags = completionSetting?.CompletionItem?.TagSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); + SupportedInsertTextModes = completionSetting?.CompletionItem?.InsertTextModeSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); // internal VS LSP if (supportsVSExtensions) diff --git a/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs b/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs index ae0bf6c7d66df..9fa30b7023723 100644 --- a/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs @@ -86,7 +86,7 @@ internal static class CompletionResultFactory ItemDefaults = new LSP.CompletionListItemDefaults { EditRange = capabilityHelper.SupportDefaultEditRange ? ProtocolConversions.TextSpanToRange(defaultSpan, documentText) : null, - Data = capabilityHelper.SupportCompletionListData ? resolveData : null + Data = capabilityHelper.SupportCompletionListData ? resolveData : null, }, // VS internal @@ -97,6 +97,12 @@ internal static class CompletionResultFactory Data = capabilityHelper.SupportVSInternalCompletionListData ? resolveData : null, }; + if (capabilityHelper.SupportedInsertTextModes.Contains(LSP.InsertTextMode.AsIs)) + { + // By default, all text edits we create include the appropriate whitespace, so tell the client to leave it as-is (if it supports the option). + completionList.ItemDefaults.InsertTextMode = LSP.InsertTextMode.AsIs; + } + PromoteCommonCommitCharactersOntoList(); if (completionList.ItemDefaults is { EditRange: null, CommitCharacters: null, Data: null }) diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index 0c122523e9b86..18fc22f98ca51 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -50,8 +50,8 @@ public override async Task> GetDiagnosticsAsync( // we're passing in. If information is already cached for that snapshot, it will be returned. Otherwise, // it will be computed on demand. Because it is always accurate as per this snapshot, all spans are correct // and do not need to be adjusted. - var diagnostics = await diagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync(Project.Solution, Project.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false); + var diagnostics = await diagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync( + Project, diagnosticIds: null, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false); // TODO(cyrusn): In the future we could consider reporting these, but with a flag on the diagnostic mentioning // that it is suppressed and should be hidden from the task list by default. @@ -74,7 +74,7 @@ public override Task> GetDiagnosticsAsync( RequestContext context, CancellationToken cancellationToken) { - return codeAnalysisService.GetLastComputedProjectDiagnosticsAsync(Project.Id, cancellationToken); + return Task.FromResult(codeAnalysisService.GetLastComputedProjectDiagnostics(Project.Id)); } } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 9becd49893ea4..7777dba417737 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -76,10 +76,7 @@ AsyncLazy> GetLazyDiagnostics() async cancellationToken => { var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, - // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. - static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + Document.Project, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); // TODO(cyrusn): Should we be filtering out suppressed diagnostics here? This is how the @@ -105,7 +102,7 @@ public override Task> GetDiagnosticsAsync( RequestContext context, CancellationToken cancellationToken) { - return codeAnalysisService.GetLastComputedDocumentDiagnosticsAsync(Document.Id, cancellationToken); + return Task.FromResult(codeAnalysisService.GetLastComputedDocumentDiagnostics(Document.Id)); } } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index b2a168f48a928..b089870386125 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -26,8 +26,7 @@ public override async Task> GetDiagnosticsAsync( // document including those reported as a compilation end diagnostic. These are not included in document pull // (uses GetDiagnosticsForSpan) due to cost. var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, _shouldIncludeAnalyzer, + Document.Project, Document.Id, diagnosticIds: null, _shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); // TODO(cyrusn): In the future we could consider reporting these, but with a flag on the diagnostic mentioning diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs index 2d1b001f6f095..bb6544ad086f3 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract partial class AbstractPullDiagnosticHandler where TDiagnosticsParams : IPartialResultParams { - internal record struct DiagnosticsRequestState(Project Project, int GlobalStateVersion, RequestContext Context, IDiagnosticSource DiagnosticSource); + internal readonly record struct DiagnosticsRequestState(Project Project, int GlobalStateVersion, RequestContext Context, IDiagnosticSource DiagnosticSource); /// /// Cache where we store the data produced by prior requests so that they can be returned if nothing of significance diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/ProjectOrDocumentId.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/ProjectOrDocumentId.cs index ac1f4f63653a6..e9924b7e75bc3 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/ProjectOrDocumentId.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/ProjectOrDocumentId.cs @@ -10,18 +10,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// Wrapper around project and document ids for convenience in caching diagnostic results and /// use in the /// -internal readonly struct ProjectOrDocumentId +internal readonly record struct ProjectOrDocumentId { /// /// Non-null if this represents a documentId. Used for equality comparisons. /// - [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for equality comparison.")] private readonly DocumentId? _documentId; /// /// Non-null if this represents a projectId. Used for equality comparisons. /// - [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for equality comparison.")] private readonly ProjectId? _projectId; public ProjectOrDocumentId(ProjectId projectId) diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs index ca79789913f81..39ec24da85143 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs @@ -16,7 +16,7 @@ internal static class PullDiagnosticCategories /// /// Edit and Continue diagnostics. Can be for Document or Workspace pull requests. /// - public static readonly string EditAndContinue = VSInternalDiagnosticKind.EditAndContiue.Value; + public static readonly string EditAndContinue = VSInternalDiagnosticKind.EditAndContinue.Value; // Workspace categories diff --git a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 4196abef13bbe..93e03de3f093a 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.LanguageServer.Protocol; using Roslyn.Text.Adornments; using Roslyn.Utilities; diff --git a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs index f1e315fc18004..08643a167efda 100644 --- a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs +++ b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; using StreamJsonRpc; diff --git a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 0f49abbd7a401..3948eb2317890 100644 --- a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -14,7 +14,6 @@ - @@ -38,10 +37,22 @@ + + + + - + + + + + + + + + @@ -84,12 +95,14 @@ + + diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs b/src/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs index 3ac8099a356ec..1956ef22d949c 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs @@ -22,6 +22,11 @@ internal readonly record struct VSInternalDiagnosticKind(string Value) : IString /// /// Edit and Continue diagnostic kind. /// - public static readonly VSInternalDiagnosticKind EditAndContiue = new("enc"); + public static readonly VSInternalDiagnosticKind EditAndContinue = new("enc"); + + /// + /// Syntax diagnostic kind. + /// + public static readonly VSInternalDiagnosticKind Syntax = new("syntax"); } } diff --git a/src/LanguageServer/Protocol/Protocol/Internal/VSInternalMapCodeParams.cs b/src/LanguageServer/Protocol/Protocol/Internal/VSInternalMapCodeParams.cs index e1460443c6612..4cf49e5466f12 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/VSInternalMapCodeParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/VSInternalMapCodeParams.cs @@ -4,6 +4,7 @@ namespace Roslyn.LanguageServer.Protocol { + using System; using System.Text.Json.Serialization; /// @@ -11,6 +12,18 @@ namespace Roslyn.LanguageServer.Protocol /// internal class VSInternalMapCodeParams { + /// + /// Internal correlation GUID, used to correlate map code messages from Copilot + /// with LSP Client actions. Used for telemetry. + /// + [JsonPropertyName("_vs_map_code_correlation_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Guid? MapCodeCorrelationId + { + get; + set; + } + /// /// Set of code blocks, associated with documents and regions, to map. /// diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index 115998c9c86ca..8acf17687cc4b 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -26,7 +26,7 @@ public CodeActionResolveTests(ITestOutputHelper testOutputHelper) : base(testOut { } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionResolveHandlerAsync(bool mutatingLspWorkspace) { var initialMarkup = @@ -77,7 +77,7 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionResolveHandlerAsync_NestedAction(bool mutatingLspWorkspace) { var initialMarkup = @@ -138,7 +138,7 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename(bool mutatingLspWorkspace) { var markUp = @" @@ -198,23 +198,35 @@ class {|caret:ABC|} AssertJsonEquals(expectedCodeAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestLinkedDocuments(bool mutatingLspWorkspace) { - var xmlWorkspace = @" + var originalMarkup = """ + class C + { + public static readonly int {|caret:_value|} = 10; + } + """; + var xmlWorkspace = $""" - class C -{ - public static readonly int {|caret:_value|} = 10; -} - + {originalMarkup} -"; + """; + + var expectedText = """ + class C + { + private static readonly int value = 10; + + public static int Value => value; + } + """; + await using var testLspServer = await CreateXmlTestLspServerAsync(xmlWorkspace, mutatingLspWorkspace); var titlePath = new string[] { string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value") }; var unresolvedCodeAction = CodeActionsTests.CreateCodeAction( @@ -230,78 +242,18 @@ public async Task TestLinkedDocuments(bool mutatingLspWorkspace) diagnostics: null); var actualResolvedAction = await RunGetCodeActionResolveAsync(testLspServer, unresolvedCodeAction); - var edits = new SumType[] - { - new TextEdit() - { - NewText = "private", - Range = new LSP.Range() - { - Start = new Position - { - Line = 2, - Character = 4 - }, - End = new Position - { - Line = 2, - Character = 10 - } - } - }, - new TextEdit - { - NewText = string.Empty, - Range = new LSP.Range - { - Start = new Position - { - Line = 2, - Character = 31 - }, - End = new Position - { - Line = 2, - Character = 32 - } - } - }, - new TextEdit - { - NewText = @" - public static int Value => value;", - Range = new LSP.Range - { - Start = new Position - { - Line = 2, - Character = 43 - }, - End = new Position - { - Line = 2, - Character = 43 - } - } - } - }; - var expectedCodeAction = CodeActionsTests.CreateCodeAction( - title: string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), - kind: CodeActionKind.Refactor, - children: [], - data: CreateCodeActionResolveData( - string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), - testLspServer.GetLocations("caret").Single(), titlePath), - priority: VSInternalPriorityLevel.Normal, - groupName: "Roslyn2", - applicableRange: new LSP.Range { Start = new Position { Line = 2, Character = 33 }, End = new Position { Line = 39, Character = 2 } }, - diagnostics: null, - edit: GenerateWorkspaceEdit(testLspServer.GetLocations("caret"), edits)); - AssertJsonEquals(expectedCodeAction, actualResolvedAction); + AssertEx.NotNull(actualResolvedAction.Edit); + var textDocumentEdit = (LSP.TextDocumentEdit[])actualResolvedAction.Edit.DocumentChanges.Value; + Assert.Single(textDocumentEdit); + var originalText = await testLspServer.GetDocumentTextAsync(textDocumentEdit[0].TextDocument.Uri); + var edits = textDocumentEdit[0].Edits.Select(e => (LSP.TextEdit)e.Value).ToArray(); + var updatedText = ApplyTextEdits(edits, originalText); + Assert.Equal(expectedText, updatedText); + } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestMoveTypeToDifferentFile(bool mutatingLspWorkspace) { var markUp = @" @@ -423,7 +375,7 @@ class BCD AssertJsonEquals(expectedCodeAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestMoveTypeToDifferentFileInDirectory(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index e62cd3c664ca5..e04e6459b4a65 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.CodeActions; public class CodeActionsTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) { - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHandlerAsync(bool mutatingLspWorkspace) { var markup = @@ -57,7 +57,7 @@ void M() AssertJsonEquals(expected, useImplicitType); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHandlerAsync_NestedAction(bool mutatingLspWorkspace) { var markup = @@ -90,13 +90,13 @@ void M() var results = await RunGetCodeActionsAsync(testLspServer, CreateCodeActionParams(caretLocation)); var topLevelAction = Assert.Single(results, action => action.Title == titlePath[0]); - var introduceConstant = topLevelAction.Children.FirstOrDefault( + var introduceConstant = topLevelAction.Children!.FirstOrDefault( r => JsonSerializer.Deserialize((JsonElement)r.Data!, ProtocolConversions.LspJsonSerializerOptions)!.UniqueIdentifier == titlePath[1]); AssertJsonEquals(expected, introduceConstant); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHasCorrectDiagnostics(bool mutatingLspWorkspace) { var markup = @@ -134,11 +134,11 @@ void M() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var addImport = results.FirstOrDefault(r => r.Title.Contains($"using System.Threading.Tasks")); - Assert.Equal(1, addImport.Diagnostics!.Length); + Assert.Equal(1, addImport!.Diagnostics!.Length); Assert.Equal(AddImportDiagnosticIds.CS0103, addImport.Diagnostics.Single().Code!.Value); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestNoSuppressionFixerInStandardLSP(bool mutatingLspWorkspace) { var markup = """ @@ -175,7 +175,7 @@ class ABC Assert.Equal("Make method synchronous", results[0].Title); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -204,7 +204,7 @@ private void XYZ() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var inline = results.FirstOrDefault(r => r.Title.Contains($"Inline 'A()'")); - var data = GetCodeActionResolveData(inline); + var data = GetCodeActionResolveData(inline!); Assert.NotNull(data); // Asserts that there are NestedActions on Inline @@ -218,10 +218,10 @@ private void XYZ() Assert.Equal("Inline and keep 'A()'", nestedActionData!.CodeActionPath[1]); // Asserts that there is a Command present on an action with nested actions - Assert.NotNull(inline.Command); + Assert.NotNull(inline?.Command); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedFixAllCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -266,7 +266,7 @@ class ABC Assert.Equal("Fix All: in Source", data.NestedCodeActions!.Value[1].Title); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedResolveTopLevelCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -296,7 +296,7 @@ private void XYZ() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); // Assert that nested code actions aren't enumerated. var inline = results.FirstOrDefault(r => r.Title.Contains($"Inline 'A()'")); - var resolvedAction = await RunGetCodeActionResolveAsync(testLspServer, inline); + var resolvedAction = await RunGetCodeActionResolveAsync(testLspServer, inline!); Assert.Null(resolvedAction.Edit); } @@ -306,7 +306,7 @@ private static async Task RunGetCodeActionsAsync( { var result = await testLspServer.ExecuteRequestAsync( LSP.Methods.TextDocumentCodeActionName, codeActionParams, CancellationToken.None); - return [.. result.Cast()]; + return [.. result!.Cast()]; } private static async Task RunGetCodeActionResolveAsync( diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs index 80bd47e9d2207..741476e85769d 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs @@ -24,7 +24,7 @@ public RunCodeActionsTests(ITestOutputHelper testOutputHelper) : base(testOutput { } - [WpfTheory(Skip = "https://github.com/dotnet/roslyn/issues/65303"), CombinatorialData] + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/65303"), CombinatorialData] public async Task TestRunCodeActions(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs index 37ff9c71e2789..1a9156b341aeb 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs @@ -386,8 +386,8 @@ void UseM() }; var actualCodeLenses = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCodeLensName, codeLensParamsDoc1, CancellationToken.None); - var firstCodeLens = actualCodeLenses.First(); - var data = JsonSerializer.Deserialize(firstCodeLens.Data!.ToString(), ProtocolConversions.LspJsonSerializerOptions); + var firstCodeLens = actualCodeLenses!.First(); + var data = JsonSerializer.Deserialize(firstCodeLens.Data!.ToString()!, ProtocolConversions.LspJsonSerializerOptions); AssertEx.NotNull(data); // Update the document so the syntax version changes @@ -412,10 +412,7 @@ void M(A a) await using var testLspServer = await CreateTestLspServerAsync(markup, lspMutatingWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableReferencesCodeLens, LanguageNames.CSharp, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableReferencesCodeLens, LanguageNames.CSharp, false) }); var actualCodeLenses = await GetCodeLensAsync(testLspServer); AssertEx.Empty(actualCodeLenses); @@ -446,10 +443,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false) }); await VerifyTestCodeLensAsync(testLspServer, FeaturesResources.Run_Test, FeaturesResources.Debug_Test); } @@ -479,10 +473,7 @@ public void M() await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false) }); await VerifyTestCodeLensAsync(testLspServer, FeaturesResources.Run_All_Tests, FeaturesResources.Debug_All_Tests); } @@ -512,10 +503,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, true); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, true) }); await VerifyTestCodeLensMissingAsync(testLspServer); } @@ -545,10 +533,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableTestsCodeLens, LanguageNames.CSharp, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableTestsCodeLens, LanguageNames.CSharp, false) }); await VerifyTestCodeLensMissingAsync(testLspServer); } diff --git a/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs b/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs new file mode 100644 index 0000000000000..5e8094d8274c8 --- /dev/null +++ b/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Commands; +public class ExecuteWorkspaceCommandTests : AbstractLanguageServerProtocolTests +{ + protected override TestComposition Composition => base.Composition.AddParts( + typeof(TestWorkspaceCommandHandler)); + + public ExecuteWorkspaceCommandTests(ITestOutputHelper? testOutputHelper) : base(testOutputHelper) + { + } + + [Theory, CombinatorialData] + public async Task TestExecuteWorkspaceCommand(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new ExecuteCommandParams() + { + Arguments = new object[] { JsonSerializer.Serialize(new TextDocumentIdentifier { Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\someFile.cs") }) }, + Command = TestWorkspaceCommandHandler.CommandName + }; + var response = await server.ExecuteRequestAsync(Methods.WorkspaceExecuteCommandName, request, CancellationToken.None); + AssertEx.NotNull(response); + Assert.True((bool)response); + + } + + [ExportCSharpVisualBasicStatelessLspService(typeof(TestWorkspaceCommandHandler)), Shared, PartNotDiscoverable] + [Command(CommandName)] + internal class TestWorkspaceCommandHandler : AbstractExecuteWorkspaceCommandHandler + { + internal const string CommandName = nameof(TestWorkspaceCommandHandler); + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestWorkspaceCommandHandler() + { + } + + public override string Command => CommandName; + + public override bool MutatesSolutionState => false; + + public override bool RequiresLSPSolution => true; + + public override TextDocumentIdentifier GetTextDocumentIdentifier(ExecuteCommandParams request) + { + return JsonSerializer.Deserialize((JsonElement)request.Arguments!.First(), ProtocolConversions.LspJsonSerializerOptions)!; + } + + public override Task HandleRequestAsync(ExecuteCommandParams request, RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + } +} diff --git a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index bf88d490792f3..ad00091a58b60 100644 --- a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -1471,6 +1471,62 @@ public async Task EditRangeShouldNotEndAtCursorPosition(bool mutatingLspWorkspac Assert.Equal(new() { Start = new(2, 0), End = new(2, 8) }, results.ItemDefaults.EditRange.Value.First); } + [Theory, CombinatorialData] + public async Task TestHasInsertTextModeIfSupportedAsync(bool mutatingLspWorkspace) + { + var markup = +@"class A +{ + void M() + { + {|caret:|} + } +}"; + var capabilities = CreateCoreCompletionCapabilities(); + capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting + { + InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [LSP.InsertTextMode.AsIs] } + }; + + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities); + var completionParams = CreateCompletionParams( + testLspServer.GetLocations("caret").Single(), + invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(LSP.InsertTextMode.AsIs, results.ItemDefaults.InsertTextMode); + } + + [Theory, CombinatorialData] + public async Task TestDoesNotHaveInsertTextModeIfNotSupportedAsync(bool mutatingLspWorkspace) + { + var markup = +@"class A +{ + void M() + { + {|caret:|} + } +}"; + var capabilities = CreateCoreCompletionCapabilities(); + capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting + { + InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [] } + }; + + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities); + var completionParams = CreateCompletionParams( + testLspServer.GetLocations("caret").Single(), + invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Null(results.ItemDefaults.InsertTextMode); + } + internal static Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { return testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 12c2b718c5e91..04875a010bc6f 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -156,7 +156,7 @@ public void VerifyLspClientOptionNames() string.Join(Environment.NewLine, actualNames)); } - private static void VerifyValuesInServer(EditorTestWorkspace workspace, List expectedValues) + private static void VerifyValuesInServer(LspTestWorkspace workspace, List expectedValues) { var globalOptionService = workspace.GetService(); var supportedOptions = DidChangeConfigurationNotificationHandler.SupportedOptions; @@ -244,7 +244,7 @@ private static string ConvertToString(object? value) => value switch { null => "null", - _ => value.ToString() + _ => value.ToString()! }; private static string GenerateNonDefaultValue(IOption2 option) diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 094dec54538ad..d13000fa95ea2 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -279,7 +279,8 @@ private protected static async Task> RunGet if (useProgress) { Assert.Null(diagnostics); - diagnostics = progress!.Value.GetValues().Single().First; + AssertEx.NotNull(progress); + diagnostics = progress.Value.GetValues()!.Single().First; } if (diagnostics == null) diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs index 140598171eaa0..1fcbd5de786d4 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs @@ -40,7 +40,7 @@ public async Task TestWorkspaceDiagnosticsReportsAdditionalFileDiagnostic(bool u @"C:\C.cs: []", @$"C:\Test.txt: [{MockAdditionalFileDiagnosticAnalyzer.Id}]", @"C:\CSProj1.csproj: []" - ], results.Select(r => $"{r.Uri.LocalPath}: [{string.Join(", ", r.Diagnostics.Select(d => d.Code?.Value?.ToString()))}]")); + ], results.Select(r => $"{r.Uri.LocalPath}: [{string.Join(", ", r.Diagnostics!.Select(d => d.Code?.Value?.ToString()))}]")); // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -64,7 +64,7 @@ public async Task TestWorkspaceDiagnosticsWithRemovedAdditionalFile(bool useVSDi Assert.Equal(3, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); AssertEx.Empty(results[2].Diagnostics); @@ -100,12 +100,12 @@ public async Task TestWorkspaceDiagnosticsWithAdditionalFileInMultipleProjects(b var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true); Assert.Equal(6, results.Length); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); - Assert.Equal("CSProj1", ((LSP.VSDiagnostic)results[1].Diagnostics.Single()).Projects.First().ProjectName); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[4].Diagnostics.Single().Code); + Assert.Equal("CSProj1", ((LSP.VSDiagnostic)results[1].Diagnostics!.Single()).Projects!.First().ProjectName); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[4].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[4].Uri.LocalPath); - Assert.Equal("CSProj2", ((LSP.VSDiagnostic)results[4].Diagnostics.Single()).Projects.First().ProjectName); + Assert.Equal("CSProj2", ((LSP.VSDiagnostic)results[4].Diagnostics!.Single()).Projects!.First().ProjectName); // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs index 0fc045014346d..4d83a1ccaa0a9 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs @@ -35,7 +35,7 @@ public async Task TestDocumentDiagnosticsCallsDiagnosticSourceWhenVersionChanges await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make a change that modifies the versions we use to cache. @@ -65,7 +65,7 @@ public async Task TestDocumentDiagnosticsCallsDiagnosticSourceWhenGlobalVersionC await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make a global version change @@ -96,7 +96,7 @@ public async Task TestDocumentDiagnosticsDoesNotCallDiagnosticSourceWhenVersionS await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make another request without modifying anything and assert we did not re-calculate anything. diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs index c1379550ec811..a728c2f2a568e 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs @@ -45,7 +45,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo { Assert.Equal(1, results.Length); Assert.Equal(2, results[0].Diagnostics?.Length); - var orderedDiagnostics = results[0].Diagnostics.OrderBy(d => d.Code!.Value.Value).ToList(); + var orderedDiagnostics = results[0].Diagnostics!.OrderBy(d => d.Code!.Value.Value).ToList(); Assert.Equal(NonLocalDiagnosticsAnalyzer.NonLocalDescriptor.Id, orderedDiagnostics[0].Code); Assert.Equal(NonLocalDiagnosticsAnalyzer.CompilationEndDescriptor.Id, orderedDiagnostics[1].Code); Assert.Equal(document.GetURI(), results[0].Uri); diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index a840823f2adc5..24cf9bca13ee0 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -11,12 +11,14 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.TaskList; +using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; @@ -35,8 +37,7 @@ public sealed class PullDiagnosticTests(ITestOutputHelper testOutputHelper) : Ab [Theory, CombinatorialData] public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -54,13 +55,9 @@ public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff(bool useVSDi [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -68,23 +65,19 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A : B {"; + var markup = @"class A : B {"; var additionalAnalyzers = new DiagnosticAnalyzer[] { new CSharpSyntaxAnalyzer(), new CSharpSemanticAnalyzer() }; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, additionalAnalyzers: additionalAnalyzers); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -95,8 +88,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool var semanticResults = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSemantic); - Assert.Equal("CS1513", syntaxResults.Single().Diagnostics.Single().Code); - Assert.Equal("CS0246", semanticResults.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", syntaxResults.Single().Diagnostics!.Single().Code); + Assert.Equal("CS0246", semanticResults.Single().Diagnostics!.Single().Code); var syntaxResults2 = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); @@ -112,8 +105,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); - Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics.Single().Code); - Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics.Single().Code); + Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics!.Single().Code); + Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics!.Single().Code); var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); @@ -157,38 +150,33 @@ public override void Initialize(AnalysisContext context) public async Task TestDocumentDiagnosticsHasVSExpandedMessage(bool mutatingLspWorkspace) { var markup = -@"internal class Program -{ - static void Main(string[] args) - { - } -}"; + """ + internal class Program + { + static void Main(string[] args) + { + } + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("IDE0060", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("IDE0060", results.Single().Diagnostics!.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics!.Single(); Assert.Equal(vsDiagnostic.ExpandedMessage, AnalyzersResources.Avoid_unused_parameters_in_your_code_If_the_parameter_cannot_be_removed_then_change_its_name_so_it_starts_with_an_underscore_and_is_optionally_followed_by_an_integer_such_as__comma__1_comma__2_etc_These_are_treated_as_special_discard_symbol_names); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2050705")] public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -196,8 +184,8 @@ public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatin var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics!.Single(); Assert.Null(vsDiagnostic.ExpandedMessage); } @@ -205,15 +193,13 @@ public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatin public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool mutatingLspWorkspace) { var markup = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -221,38 +207,31 @@ class A { var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.Task); - Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); - Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); + Assert.Equal("TODO", results.Single().Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results.Single().Diagnostics!.Single().Message); } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var workspace = testLspServer.TestWorkspace; - // Calling GetTextBuffer will effectively open the file. - workspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); // Get the diagnostics for the solution containing the doc. @@ -262,7 +241,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics).ConfigureAwait(false); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); // Now remove the doc. workspace.OnDocumentRemoved(workspace.Documents.Single().Id); @@ -278,20 +257,16 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic [Theory, CombinatorialData] public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); var resultId = results.Single().ResultId; results = await RunGetDocumentPullDiagnosticsAsync( @@ -305,20 +280,16 @@ public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnos [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1481208")] public async Task TestDocumentDiagnosticsWhenGlobalStateChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); var resultId = results.Single().ResultId; @@ -336,21 +307,18 @@ public async Task TestDocumentDiagnosticsWhenGlobalStateChanges(bool useVSDiagno [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var text = await document.GetTextAsync(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); - await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "}"); + await InsertTextAsync(testLspServer, document, text.Length, "}"); results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId); AssertEx.Empty(results[0].Diagnostics); @@ -359,42 +327,37 @@ public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiag [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var text = await document.GetTextAsync(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); - buffer.Insert(0, " "); await InsertTextAsync(testLspServer, document, position: 0, text: " "); results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: results[0].ResultId); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics!.Single().Range.Start); } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup = -@"#line 1 ""test.txt"" -class A {"; + """ + #line 1 "test.txt" + class A { + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -402,46 +365,46 @@ class A {"; var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.Equal(1, results.Single().Diagnostics.Single().Range.Start.Line); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.Equal(1, results.Single().Diagnostics!.Single().Range.Start.Line); } [Theory, CombinatorialData] public async Task TestStreamingDocumentDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, useProgress: true); - Assert.Equal("CS1513", results!.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results!.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsForOpenFilesUsesActiveContext(bool useVSDiagnostics, bool mutatingLspWorkspace) { var documentText = -@"#if ONE -class A { -#endif -class B {"; + """ + #if ONE + class A { + #endif + class B { + """; var workspaceXml = -@$" - - {documentText} - - - {documentText} - -"; + $""" + + + {documentText} + + + {documentText} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); @@ -451,18 +414,21 @@ class B {"; // Open either of the documents via LSP, we're tracking the URI and text. await OpenDocumentAsync(testLspServer, csproj1Document); - // This opens all documents in the workspace and ensures buffers are created. - testLspServer.TestWorkspace.GetTestDocument(csproj1Document.Id)!.GetTextBuffer(); + // If we don't have a mutating workspace, we need to manually open all linked documents in the workspace (otherwise updating the context will not succeed). + if (!mutatingLspWorkspace) + { + await testLspServer.OpenDocumentInWorkspaceAsync(csproj2Document.Id, openAllLinkedDocuments: true); + } // Set CSProj2 as the active context and get diagnostics. testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); if (useVSDiagnostics) { // Only VSDiagnostics will have the project. - var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); - Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); + var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics!.Single(); + Assert.Equal("CSProj2", vsDiagnostic.Projects!.Single().ProjectName); } // Set CSProj1 as the active context and get diagnostics. @@ -473,24 +439,25 @@ class B {"; if (useVSDiagnostics) { - AssertEx.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + AssertEx.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects!.Single().ProjectName)); } } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mutatingLspWorkspace) { - var documentText = -@"class A { err }"; + var documentText = @"class A { err }"; var workspaceXml = -@$" - - {documentText} - - - {documentText} - -"; + $""" + + + {documentText} + + + {documentText} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); @@ -501,9 +468,9 @@ public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mut await OpenDocumentAsync(testLspServer, csproj1Document); var csproj1Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj1Document), useVSDiagnostics: true); - var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics.Single(); + var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics!.Single(); var csproj2Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj2Document), useVSDiagnostics: true); - var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics.Single(); + var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics!.Single(); Assert.Equal(csproj1Diagnostic.Identifier, csproj2Diagnostic.Identifier); static VSTextDocumentIdentifier GetVsTextDocumentIdentifier(Document document) @@ -525,26 +492,32 @@ static VSTextDocumentIdentifier GetVsTextDocumentIdentifier(Document document) public async Task TestDocumentDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class {|caret:|} { } -}"; + """ + namespace M + { + public class {|caret:|} { } + } + """; var workspaceXml = -@$" - - {markup1} - CSProj2 - - - {markup2} - -"; + $""" + + + {markup1} + CSProj2 + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); @@ -556,7 +529,7 @@ public class {|caret:|} { } // Verify we a diagnostic in A.cs since B does not exist. var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS0246", results.Single().Diagnostics!.Single().Code); // Insert B into B.cs and verify that the error in A.cs is now gone. var locationToReplace = testLspServer.GetLocations("caret").Single().Range; @@ -572,25 +545,31 @@ public class {|caret:|} { } public async Task TestDocumentDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class {|caret:|} { } -}"; + """ + namespace M + { + public class {|caret:|} { } + } + """; var workspaceXml = -@$" - - {markup1} - - - {markup2} - -"; + $""" + + + {markup1} + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); @@ -602,7 +581,7 @@ public class {|caret:|} { } // Verify we get a diagnostic in A since the class B does not exist. var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS0246", results.Single().Diagnostics!.Single().Code); // Add B to CSProj2 and verify that we get an unchanged result (still has diagnostic) for A.cs // since CSProj1 does not reference CSProj2 @@ -618,15 +597,11 @@ public class {|caret:|} { } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.RazorLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -635,22 +610,18 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, testLspServer, document.GetURI(), useVSDiagnostics); // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); } [Theory, CombinatorialData] public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = -@"class A {"; + var markup = @"class A {"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.LiveShareLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -659,8 +630,96 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti testLspServer, document.GetURI(), useVSDiagnostics); // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); + } + + [Theory, CombinatorialData] + internal async Task TestDocumentDiagnosticsUpdatesSourceGeneratorDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace, SourceGeneratorExecutionPreference executionPreference) + { + var markup = """ + public {|insert:|}class C + { + public int F => _field; + } + """; + + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + + // Set execution mode preference. + var configService = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: executionPreference); + + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + + // Add a generator to the solution that reports a diagnostic if the text matches original markup + var generator = new CallbackGenerator(onInit: (_) => { }, onExecute: context => + { + var tree = context.Compilation.SyntaxTrees.Single(); + var text = tree.GetText(CancellationToken.None).ToString(); + if (text.Contains("public partial class C")) + { + context.AddSource("blah", """ + public partial class C + { + private readonly int _field = 1; + } + """); + } + }); + + testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( + document.Project.Id, + new TestGeneratorReference(generator)); + await testLspServer.WaitForSourceGeneratorsAsync(); + + await OpenDocumentAsync(testLspServer, document); + + // First diagnostic request should report a diagnostic since the generator does not produce any source (text does not match). + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + var diagnostic = AssertEx.Single(results.Single().Diagnostics); + Assert.Equal("CS0103", diagnostic.Code); + + // Update the text to include the text trigger the source generator relies on. + var textLocation = testLspServer.GetLocations()["insert"].Single(); + + var textEdit = new LSP.TextEdit { Range = textLocation.Range, NewText = "partial " }; + if (mutatingLspWorkspace) + { + // In the mutating workspace, we just need to update the LSP text (which will flow into the workspace). + await testLspServer.ReplaceTextAsync(textLocation.Uri, (textEdit.Range, textEdit.NewText)); + await WaitForWorkspaceOperationsAsync(testLspServer.TestWorkspace); + } + else + { + // In the non-mutating workspace, we need to ensure that both the workspace and LSP view of the world are updated. + var workspaceText = await document.GetTextAsync(CancellationToken.None); + var textChange = ProtocolConversions.TextEditToTextChange(textEdit, workspaceText); + await testLspServer.TestWorkspace.ChangeDocumentAsync(document.Id, workspaceText.WithChanges(textChange)); + await testLspServer.ReplaceTextAsync(textLocation.Uri, (textEdit.Range, textEdit.NewText)); + } + + await testLspServer.WaitForSourceGeneratorsAsync(); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + + if (executionPreference == SourceGeneratorExecutionPreference.Automatic) + { + // In automatic mode, the diagnostic should be immediately removed as the source generator ran and produced the required field. + Assert.Empty(results.Single().Diagnostics!); + } + else + { + // In balanced mode, the diagnostic should remain until there is a manual source generator run that updates the sg text. + diagnostic = AssertEx.Single(results.Single().Diagnostics); + Assert.Equal("CS0103", diagnostic.Code); + + testLspServer.TestWorkspace.EnqueueUpdateSourceGeneratorVersion(document.Project.Id, forceRegeneration: false); + await testLspServer.WaitForSourceGeneratorsAsync(); + + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + Assert.Empty(results.Single().Diagnostics!); + } } [Theory, CombinatorialData] @@ -670,12 +729,12 @@ public async Task TestDocumentDiagnosticsIncludesSourceGeneratorDiagnostics(bool await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); + var generator = new DiagnosticProducingGenerator(context => + { + return Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10)); + }); testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( document.Project.Id, @@ -694,12 +753,13 @@ public async Task TestDocumentDiagnosticsIncludesSourceGeneratorDiagnostics(bool public async Task TestDocumentDiagnosticsWithFadingOptionOn(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup = -@" -{|first:using System.Linq; -using System.Threading;|} -class A -{ -}"; + """ + {|first:using System.Linq; + using System.Threading;|} + class A + { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var firstLocation = testLspServer.GetLocations("first").Single().Range; testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, true); @@ -733,12 +793,13 @@ class A public async Task TestDocumentDiagnosticsWithFadingOptionOff(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup = -@" -{|first:using System.Linq; -using System.Threading;|} -class A -{ -}"; + """ + {|first:using System.Linq; + using System.Threading;|} + class A + { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var firstLocation = testLspServer.GetLocations("first").Single().Range; testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, false); @@ -757,14 +818,16 @@ class A public async Task TestDocumentDiagnosticsWithNotConfigurableFading(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup = -@"class A -{ - void M() - { - _ = {|line:{|open:(|}1 + 2 +|} - 3 + 4{|close:)|}; - } -}"; + """ + class A + { + void M() + { + _ = {|line:{|open:(|}1 + 2 +|} + 3 + 4{|close:)|}; + } + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var openLocation = testLspServer.GetLocations("open").Single().Range; var closeLocation = testLspServer.GetLocations("close").Single().Range; @@ -794,14 +857,14 @@ void M() else { // There should be one unnecessary diagnostic. - Assert.True(results.Single().Diagnostics.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(lineLocation, results.Single().Diagnostics.Single().Range); + Assert.True(results.Single().Diagnostics!.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(lineLocation, results.Single().Diagnostics!.Single().Range); // There should be an additional location for the open paren. - Assert.Equal(openLocation, results.Single().Diagnostics.Single().RelatedInformation![0].Location.Range); + Assert.Equal(openLocation, results.Single().Diagnostics!.Single().RelatedInformation![0].Location.Range); // There should be an additional location for the close paren. - Assert.Equal(closeLocation, results.Single().Diagnostics.Single().RelatedInformation![1].Location.Range); + Assert.Equal(closeLocation, results.Single().Diagnostics!.Single().RelatedInformation![1].Location.Range); } } @@ -818,22 +881,23 @@ public async Task TestDocumentDiagnosticsForUnnecessarySuppressions(bool useVSDi var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics.Single().Code); + Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1824321")] public async Task TestDocumentDiagnosticsForSourceSuppressions(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup = @" -class C -{ - void M() - { -#pragma warning disable CS0168 // Variable is declared but never used - int x; -#pragma warning restore CS0168 // Variable is declared but never used - } -}"; + var markup = """ + class C + { + void M() + { + #pragma warning disable CS0168 // Variable is declared but never used + int x; + #pragma warning restore CS0168 // Variable is declared but never used + } + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -850,15 +914,14 @@ void M() public async Task TestInfoDiagnosticsAreReportedAsInformationInVS(bool mutatingLspWorkspace) { var markup = -@"class A -{ - public A SomeA = new A(); -}"; + """ + class A + { + public A SomeA = new A(); + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -866,32 +929,8 @@ public async Task TestInfoDiagnosticsAreReportedAsInformationInVS(bool mutatingL var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics.Single().Severity); - } - - [Theory, CombinatorialData] - public async Task TestInfoDiagnosticsAreReportedAsHintInVSCode(bool mutatingLspWorkspace) - { - var markup = -@"class A -{ - public A SomeA = new A(); -}"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); - - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - - await OpenDocumentAsync(testLspServer, document); - - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: false); - - Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Hint, results.Single().Diagnostics.Single().Severity); + Assert.Equal("IDE0090", results.Single().Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics!.Single().Severity); } #endregion @@ -901,8 +940,7 @@ public async Task TestInfoDiagnosticsAreReportedAsHintInVSCode(bool mutatingLspW [Theory, CombinatorialData] public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); @@ -915,8 +953,7 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOff(bool useVSD [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -924,7 +961,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiag var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); } @@ -932,8 +969,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiag [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); @@ -944,16 +980,15 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFS var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); // this should be considered a build-error, since it was produced by the last code-analysis run. - Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags!); + Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics!.Single().Tags!); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); // Now fix the compiler error, but don't re-execute code analysis. // Verify that we still get the workspace diagnostics from the prior snapshot on which code analysis was executed. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -973,8 +1008,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFS [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -986,16 +1020,15 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisFSAOn var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); // this should *not* be considered a build-error, since it was produced by the live workspace results. - Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags!); + Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics!.Single().Tags!); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); // Now fix the compiler error, but don't rerun code analysis. // Verify that we get up-to-date workspace diagnostics, i.e. no compiler errors, from the current snapshot because FSA is enabled. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -1033,17 +1066,18 @@ public async Task SourceGeneratorFailures_FSA(bool useVSDiagnostics, bool mutati Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.True(results[1].Diagnostics.Single().Message.Contains("Source generator failed")); + Assert.True(results[1].Diagnostics!.Single().Message.Contains("Source generator failed")); } [Theory, CombinatorialData] public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool mutatingLspWorkspace) { var markup1 = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); @@ -1056,19 +1090,20 @@ class A { public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool mutatingLspWorkspace) { var markup1 = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); + Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics!.Single()).DiagnosticRank); } [Theory] @@ -1083,10 +1118,11 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn_Priorities( { var rank = (VSDiagnosticRank)intRank; var markup1 = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); @@ -1097,19 +1133,20 @@ class A { var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); + Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics!.Single()).DiagnosticRank); } [Theory, CombinatorialData] public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool mutatingLspWorkspace) { var markup1 = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); @@ -1122,10 +1159,11 @@ class A { public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = -@" -// todo: goo -class A { -}"; + """ + // todo: goo + class A { + } + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); @@ -1133,18 +1171,19 @@ class A { Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); } [Theory, CombinatorialData] public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = -@" -// todo: goo -class A { -"; + """ + // todo: goo + class A { + + """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); @@ -1276,30 +1315,33 @@ public async Task EditAndContinue(bool useVSDiagnostics, bool mutatingLspWorkspa AssertEx.Equal([], workspaceResults3.Select(Inspect)); static DiagnosticData CreateDiagnostic(string id, Document? document = null, Project? project = null) - => new( - id, - category: "EditAndContinue", - message: "test message", - severity: DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true, - warningLevel: 0, - projectId: project?.Id, - customTags: [], - properties: ImmutableDictionary.Empty, - location: new DiagnosticDataLocation(new FileLinePositionSpan("file", span: default), document?.Id), - additionalLocations: [], - language: (project ?? document!.Project).Language); + { + return new( + id, + category: "EditAndContinue", + message: "test message", + severity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + warningLevel: 0, + projectId: project?.Id, + customTags: [], + properties: ImmutableDictionary.Empty, + location: new DiagnosticDataLocation(new FileLinePositionSpan("file", span: default), document?.Id), + additionalLocations: [], + language: (project ?? document!.Project).Language); + } static string Inspect(TestDiagnosticResult result) - => $"{result.TextDocument.Uri} -> [{string.Join(",", result.Diagnostics?.Select(d => d.Code?.Value) ?? [])}]"; + { + return $"{result.TextDocument.Uri} -> [{string.Join(",", result.Diagnostics?.Select(d => d.Code?.Value) ?? [])}]"; + } } [Theory, CombinatorialData] public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOffWithFileInProjectOpen(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); @@ -1329,7 +1371,7 @@ public async Task TestWorkspaceDiagnosticsIncludesSourceGeneratorDiagnosticsClos var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); } @@ -1356,19 +1398,20 @@ public async Task TestWorkspaceDiagnosticsDoesNotIncludeSourceGeneratorDiagnosti [Theory, CombinatorialData] public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrectLanguage(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var csharpMarkup = -@"class A {"; + var csharpMarkup = @"class A {"; var typeScriptMarkup = "???"; var workspaceXml = -@$" - - {csharpMarkup} - - - {typeScriptMarkup} - -"; + $""" + + + {csharpMarkup} + + + {typeScriptMarkup} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); @@ -1380,8 +1423,7 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrec [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestLspServerAsync( markups: [], mutatingLspWorkspace, @@ -1397,15 +1439,14 @@ public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiag Assert.Equal(3, results.Length); // Since we sorted above by URI the first result is the project. AssertEx.Empty(results[0].Diagnostics); - Assert.Equal("CS1513", results[1].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[1].Diagnostics!.Single().Code); AssertEx.Empty(results[2].Diagnostics); } [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1413,7 +1454,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); @@ -1431,8 +1472,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti [Theory, CombinatorialData] public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1440,7 +1480,7 @@ public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagno var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); @@ -1453,8 +1493,7 @@ public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagno [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1462,12 +1501,11 @@ public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDia var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -1480,8 +1518,7 @@ public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDia [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1489,27 +1526,21 @@ public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSD var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(0, " "); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, " ", position: 0); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); var text = await document.GetTextAsync(); - // Hacky, but we need to close the document manually since editing the text-buffer will open it in the - // test-workspace. - testLspServer.TestWorkspace.OnDocumentClosed( - document.Id, TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()))); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal("CS1513", results2[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results2[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics!.Single().Range.Start); AssertEx.Empty(results2[1].Diagnostics); Assert.NotEqual(results[1].ResultId, results2[1].ResultId); @@ -1520,8 +1551,7 @@ public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSD [Theory, CombinatorialData] public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1529,8 +1559,8 @@ public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true); @@ -1541,8 +1571,10 @@ public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool public async Task TestWorkspaceDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"#line 1 ""test.txt"" -class A {"; + """ + #line 1 "test.txt" + class A { + """; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1550,8 +1582,8 @@ class A {"; var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(@"C:\test1.cs"), results[0].TextDocument!.Uri); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(1, results[0].Diagnostics.Single().Range.Start.Line); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(1, results[0].Diagnostics!.Single().Range.Start.Line); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); } @@ -1560,26 +1592,32 @@ class A {"; public async Task TestWorkspaceDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class {|caret:|} { } -}"; + """ + namespace M + { + public class {|caret:|} { } + } + """; var workspaceXml = -@$" - - {markup1} - CSProj2 - - - {markup2} - -"; + $""" + + + {markup1} + CSProj2 + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -1589,8 +1627,8 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); // Insert B into B.cs via the workspace. var caretLocation = testLspServer.GetLocations("caret").First().Range; @@ -1619,41 +1657,49 @@ public class {|caret:|} { } public async Task TestWorkspaceDiagnosticsWithChangeInRecursiveReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - public class A : B - { - } -}"; + """ + namespace M + { + public class A : B + { + } + } + """; var markup2 = -@"namespace M -{ - public class B : C - { - } -}"; + """ + namespace M + { + public class B : C + { + } + } + """; var markup3 = -@"namespace M -{ - public class {|caret:|} - { - } -}"; + """ + namespace M + { + public class {|caret:|} + { + } + } + """; var workspaceXml = -@$" - - CSProj2 - {markup1} - - - CSProj3 - {markup2} - - - {markup3} - -"; + $""" + + + CSProj2 + {markup1} + + + CSProj3 + {markup2} + + + {markup3} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj3Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj3").Single().Documents.First(); @@ -1663,13 +1709,13 @@ public class {|caret:|} AssertEx.NotNull(results); Assert.Equal(6, results.Length); // Type C does not exist. - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); // Type C does not exist. - Assert.Equal("CS0246", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[2].Diagnostics!.Single().Code); AssertEx.Empty(results[3].Diagnostics); // Syntax error missing identifier. - Assert.Equal("CS1001", results[4].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[4].Diagnostics!.Single().Code); AssertEx.Empty(results[5].Diagnostics); // Insert C into C.cs via the workspace. @@ -1687,7 +1733,7 @@ public class {|caret:|} Assert.Equal(3, results.Length); // A.cs should report CS0012 indicating that C is not directly referenced. - Assert.Equal("CS0012", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0012", results[0].Diagnostics!.Single().Code); Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); // B.cs should no longer have a diagnostic since C exists. @@ -1703,25 +1749,31 @@ public class {|caret:|} public async Task TestWorkspaceDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class {|caret:|} { } -}"; + """ + namespace M + { + public class {|caret:|} { } + } + """; var workspaceXml = -@$" - - {markup1} - - - {markup2} - -"; + $""" + + + {markup1} + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -1731,9 +1783,9 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); AssertEx.Empty(results[3].Diagnostics); // Insert B into B.cs via the workspace. @@ -1753,40 +1805,43 @@ public class {|caret:|} { } AssertEx.Empty(results[0].Diagnostics); Assert.NotEqual(previousResultIds[2].resultId, results[0].ResultId); } -#if true -#else -#endif [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedAndChanged(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class B - { - public static void Do() - { - int unusedVariable; - } - } -}"; + """ + namespace M + { + public class B + { + public static void Do() + { + int unusedVariable; + } + } + } + """; var workspaceXml = -@$" - - {markup1} - CSProj2 - - - {markup2} - -"; + $""" + + + {markup1} + CSProj2 + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -1797,8 +1852,8 @@ public static void Do() AssertEx.NotNull(results); Assert.Equal(4, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal("CS0168", results[2].Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Warning, results[2].Diagnostics.Single().Severity); + Assert.Equal("CS0168", results[2].Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Warning, results[2].Diagnostics!.Single().Severity); // Change and reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -1816,34 +1871,40 @@ public static void Do() // We should get a single report back for B.cs now that the diagnostic has been promoted to an error. // The diagnostics in A.cs did not change and so are not reported again. Assert.Equal(1, results.Length); - Assert.Equal("CS0168", results[0].Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Error, results[0].Diagnostics.Single().Severity); + Assert.Equal("CS0168", results[0].Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Error, results[0].Diagnostics!.Single().Severity); } [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedUnchanged(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var markup2 = -@"namespace M -{ - public class {|caret:|} { } -}"; + """ + namespace M + { + public class {|caret:|} { } + } + """; var workspaceXml = -@$" - - {markup1} - CSProj2 - - - {markup2} - -"; + $""" + + + {markup1} + CSProj2 + + + {markup2} + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -1853,8 +1914,8 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); // Reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -1877,25 +1938,29 @@ public class {|caret:|} { } public async Task TestWorkspaceDiagnosticsOrderOfReferencedProjectsReloadedDoesNotMatter(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = -@"namespace M -{ - class A : B { } -}"; + """ + namespace M + { + class A : B { } + } + """; var workspaceXml = -@$" - - {markup1} - CSProj2 - CSProj3 - - - - - - - -"; + $""" + + + {markup1} + CSProj2 + CSProj3 + + + + + + + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); @@ -1905,7 +1970,7 @@ class A : B { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(6, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); // Reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -1928,17 +1993,18 @@ class A : B { } [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsDoesNotThrowIfProjectWithoutFilePathExists(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var csharpMarkup = -@"class A {"; + var csharpMarkup = @"class A {"; var workspaceXml = -@$" - - {csharpMarkup} - - - - -"; + $""" + + + {csharpMarkup} + + + + + + """; await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); @@ -1953,8 +2019,7 @@ public async Task TestWorkspaceDiagnosticsDoesNotThrowIfProjectWithoutFilePathEx [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsWaitsForLspTextChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -1982,8 +2047,7 @@ public async Task TestWorkspaceDiagnosticsWaitsForLspTextChanges(bool useVSDiagn [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsWaitsForLspSolutionChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -2011,8 +2075,7 @@ public async Task TestWorkspaceDiagnosticsWaitsForLspSolutionChanges(bool useVSD [Theory(Skip = "https://github.com/dotnet/roslyn/issues/76503"), CombinatorialData] public async Task TestWorkspaceDiagnosticsWaitsForLspTextChangesWithMultipleSources(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; var markup2 = ""; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); @@ -2070,15 +2133,14 @@ public async Task TestWorkspaceDiagnosticsWaitsForLspTextChangesWithMultipleSour [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOnToOff(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(2, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); var options = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); @@ -2108,8 +2170,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOnToOff(boo [Theory, CombinatorialData] public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOffToOn(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var markup1 = -@"class A {"; + var markup1 = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); @@ -2124,9 +2185,17 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOffToOn(boo results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); Assert.Equal(2, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); } + internal static async Task InsertInClosedDocumentAsync(TestLspServer testLspServer, DocumentId documentId, string textToInsert, int? position = null) + { + var text = await testLspServer.GetCurrentSolution().GetDocument(documentId)!.GetTextAsync(CancellationToken.None); + position ??= text.Length; + text = text.WithChanges(new TextChange(new TextSpan(position.Value, 0), textToInsert)); + await testLspServer.TestWorkspace.ChangeDocumentAsync(documentId, text); + } + #endregion } diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs index ce607c6a9fc25..93cf0d63cc83f 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs @@ -29,7 +29,7 @@ public async Task TestWorkspaceDiagnosticsReportsProjectDiagnostic(bool useVSDia Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(testLspServer.GetCurrentSolution().Projects.First().FilePath!), results[1].Uri); // Asking again should give us back an unchanged diagnostic. @@ -46,7 +46,7 @@ public async Task TestWorkspaceDiagnosticsWithRemovedProject(bool useVSDiagnosti Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(testLspServer.GetCurrentSolution().Projects.First().FilePath!), results[1].Uri); var initialSolution = testLspServer.GetCurrentSolution(); diff --git a/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs b/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs index 5968d8f97ca07..5963b91d09b83 100644 --- a/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs @@ -130,8 +130,9 @@ void M() }; var actualInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + AssertEx.NotNull(actualInlayHints); var firstInlayHint = actualInlayHints.First(); - var data = JsonSerializer.Deserialize(firstInlayHint.Data!.ToString(), ProtocolConversions.LspJsonSerializerOptions); + var data = JsonSerializer.Deserialize(firstInlayHint.Data!.ToString()!, ProtocolConversions.LspJsonSerializerOptions); AssertEx.NotNull(data); var firstResultId = data.ResultId; @@ -143,6 +144,7 @@ void M() await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); var lastInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + AssertEx.NotNull(lastInlayHints); Assert.True(lastInlayHints.Any()); // Assert that the first result id is no longer in the cache. diff --git a/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs b/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs index 0aaf597dec428..2947fd4a7dcf1 100644 --- a/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs @@ -112,7 +112,7 @@ static void Main(string[] args) var results = await testLspServer.ExecuteRequestAsync(VSInternalMethods.WorkspaceMapCodeName, mapCodeParams, CancellationToken.None); AssertEx.NotNull(results); - TextEdit[] edits; + TextEdit[]? edits; if (supportDocumentChanges) { Assert.Null(results.Changes); @@ -131,6 +131,8 @@ static void Main(string[] args) Assert.True(results.Changes!.TryGetValue(ProtocolConversions.GetDocumentFilePathFromUri(documentUri), out edits)); } + AssertEx.NotNull(edits); + var documentText = await document.GetTextAsync(); var actualText = ApplyTextEdits(edits, documentText); Assert.Equal(expected, actualText); diff --git a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs index 36e37923ab780..733d6c9306bd8 100644 --- a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs @@ -43,6 +43,7 @@ void M() var location = testLspServer.GetLocations("definition").Single(); var definition = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName, CreateTextDocumentPositionParams(location), CancellationToken.None); + AssertEx.NotNull(definition); // Open the metadata file and verify it gets added to the metadata workspace. await testLspServer.OpenDocumentAsync(definition.Single().Uri, text: string.Empty).ConfigureAwait(false); @@ -89,6 +90,7 @@ public static void WriteLine(string value) {} var location = testLspServer.GetLocations("definition").Single(); var definition = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName, CreateTextDocumentPositionParams(location), CancellationToken.None); + AssertEx.NotNull(definition); // Open the metadata file and verify it gets added to the metadata workspace. // We don't have the real metadata source, so just populate it with our fake metadata source. diff --git a/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj b/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj index b244b45d8789b..77dcec96afa1b 100644 --- a/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj +++ b/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj @@ -3,7 +3,7 @@ - net472 + $(NetVSCode);net472 Library Microsoft.CodeAnalysis.LanguageServer.UnitTests UnitTest @@ -13,10 +13,6 @@ Always - - - - @@ -27,13 +23,6 @@ - - - - - - - @@ -47,11 +36,9 @@ + - - - diff --git a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index be2a76a84c976..5299ad3bec399 100644 --- a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -286,6 +286,7 @@ void M() Assert.Equal(LanguageNames.CSharp, miscDoc.Project.Language); // Verify GTD request succeeded. + AssertEx.NotNull(result); Assert.Equal(0, result.Single().Range.Start.Line); Assert.Equal(6, result.Single().Range.Start.Character); Assert.Equal(0, result.Single().Range.End.Line); diff --git a/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs b/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs index f85284982a6ef..22f7e85fe4175 100644 --- a/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs @@ -6,8 +6,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; diff --git a/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs b/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs index 44befe94053bb..a255360e48280 100644 --- a/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs @@ -81,11 +81,9 @@ public async Task SwitchingContextsChangesDefaultContext(bool mutatingLspWorkspa await using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, mutatingLspWorkspace); - // Ensure the documents are open so we can change contexts - foreach (var document in testLspServer.TestWorkspace.Documents) - { - _ = document.GetOpenTextContainer(); - } + // Ensure all the linked documents are open so we can change contexts + var document = testLspServer.TestWorkspace.Documents.First(); + await testLspServer.OpenDocumentInWorkspaceAsync(document.Id, openAllLinkedDocuments: true); var documentUri = testLspServer.GetLocations("caret").Single().Uri; diff --git a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs index 8fda99724efe7..d57d43540f495 100644 --- a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -70,8 +69,8 @@ class SomeClass{{i}} } """; - var testDocument = new EditorTestHostDocument(text: source, displayName: @$"C:\SomeFile{i}.cs", exportProvider: testLspServer.TestWorkspace.ExportProvider, filePath: @$"C:\SomeFile{i}.cs"); - testLspServer.TestWorkspace.AddTestProject(new EditorTestHostProject(testLspServer.TestWorkspace, documents: [testDocument])); + var testDocument = new TestHostDocument(text: source, displayName: @$"C:\SomeFile{i}.cs", exportProvider: testLspServer.TestWorkspace.ExportProvider, filePath: @$"C:\SomeFile{i}.cs"); + testLspServer.TestWorkspace.AddTestProject(new TestHostProject(testLspServer.TestWorkspace, documents: [testDocument])); } await WaitForWorkspaceOperationsAsync(testLspServer.TestWorkspace); diff --git a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs index 979a37c00517c..d4965c90a9321 100644 --- a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs @@ -95,7 +95,7 @@ class Y useProgress: useProgress); Assert.Equal(1, results.Length); - Assert.Equal(project.Documents.Last().FilePath, results[0].FilePaths.Single()); + Assert.Equal(project.Documents.Last().FilePath, results[0].FilePaths!.Single()); } [Theory, CombinatorialData] @@ -129,8 +129,8 @@ class Z project.Documents.First().GetURI(), useProgress: useProgress); - Assert.Equal(2, results.SelectMany(r => r.FilePaths).Count()); - AssertEx.SetEqual([.. project.Documents.Skip(1).Select(d => d.FilePath)], results.SelectMany(r => r.FilePaths)); + Assert.Equal(2, results.SelectMany(r => r.FilePaths!).Count()); + AssertEx.SetEqual([.. project.Documents.Skip(1).Select(d => d.FilePath)], results.SelectMany(r => r.FilePaths!)); } [Theory, CombinatorialData] diff --git a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs index 68c71fadb2528..870cbd83c119b 100644 --- a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs @@ -22,7 +22,7 @@ public RenameTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRenameAsync(bool mutatingLspWorkspace) { var markup = @@ -45,7 +45,7 @@ void M2() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_InvalidIdentifierAsync(bool mutatingLspWorkspace) { var markup = @@ -67,7 +67,7 @@ void M2() Assert.Null(results); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithLinkedFilesAsync(bool mutatingLspWorkspace) { var markup = @@ -101,7 +101,7 @@ void M2() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithLinkedFilesAndPreprocessorAsync(bool mutatingLspWorkspace) { var markup = @@ -147,7 +147,7 @@ void M4() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithMappedFileAsync(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs b/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs index f9eaf873a3a5f..aad9886aeadb5 100644 --- a/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; @@ -48,9 +49,7 @@ public async Task TestDocumentResultsForOpenFiles(bool mutatingLspWorkspace) }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. var testDocument = testLspServer.TestWorkspace.Documents.Single(); - testDocument.GetTextBuffer(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -81,7 +80,6 @@ class {|Identifier:A{{v}}|} """)); await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. var testDocument = testLspServer.TestWorkspace.Documents.Single(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -114,9 +112,6 @@ public async Task TestDocumentResultsForRemovedDocument(bool mutatingLspWorkspac await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var workspace = testLspServer.TestWorkspace; - // Calling GetTextBuffer will effectively open the file. - workspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); // Get the diagnostics for the solution containing the doc. @@ -153,9 +148,6 @@ public async Task TestNoChangeIfDocumentResultsCalledTwice(bool mutatingLspWorks }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -189,9 +181,6 @@ public async Task TestDocumentResultChangedAfterEntityAdded(bool mutatingLspWork "; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -206,7 +195,7 @@ public async Task TestDocumentResultChangedAfterEntityAdded(bool mutatingLspWork Ranges = GetRanges(testLspServer.TestWorkspace.Documents.Single().AnnotatedSpans), }); - await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "// comment"); + await InsertTextAsync(testLspServer, document, sourceText.Length, "// comment"); var (_, lspSolution) = await testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None).ConfigureAwait(false); document = lspSolution!.Projects.Single().Documents.Single(); @@ -237,9 +226,6 @@ public async Task TestDocumentResultIdSameAfterIrrelevantEdit(bool mutatingLspWo }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -277,9 +263,6 @@ class {|Identifier:A|} }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -304,9 +287,6 @@ public async Task TestStreamingDocumentDiagnostics(bool mutatingLspWorkspace) }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -497,8 +477,7 @@ public async Task TestWorkspaceResultUpdatedAfterEdit(bool mutatingLspWorkspace) }); AssertEx.Empty(results[1].Ranges); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "// comment"); + await PullDiagnosticTests.InsertInClosedDocumentAsync(testLspServer, document.Id, "// comment"); var results2 = await RunGetWorkspaceSpellCheckSpansAsync(testLspServer, previousResults: CreateParamsFromPreviousReports(results)); diff --git a/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs b/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs index 3a90b5d341ab6..213721f2f4ea1 100644 --- a/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs @@ -21,7 +21,7 @@ public class WorkspaceSymbolsTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) { private static void AssertSetEquals(LSP.SymbolInformation[] expected, LSP.SymbolInformation[]? results) - => Assert.True(expected.ToHashSet().SetEquals(results)); + => Assert.True(expected.ToHashSet().SetEquals(results!)); private Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace) => CreateTestLspServerAsync(markup, mutatingLspWorkspace, composition: Composition.AddParts(typeof(TestWorkspaceNavigateToSearchHostService))); diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index 5866d332036e1..8226be4f8c8b4 100644 --- a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -298,7 +298,7 @@ public async Task TestUsesRegisteredHostWorkspace(bool mutatingLspWorkspace) // Verify 1 workspace registered to start with. Assert.True(IsWorkspaceRegistered(testLspServer.TestWorkspace, testLspServer)); - using var testWorkspaceTwo = EditorTestWorkspace.Create( + using var testWorkspaceTwo = LspTestWorkspace.Create( XElement.Parse(secondWorkspaceXml), workspaceKind: "OtherWorkspaceKind", composition: testLspServer.TestWorkspace.Composition); @@ -512,14 +512,15 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync(bool mutatingLspWor } [Theory, CombinatorialData] - public async Task TestDoesNotForkWhenDocumentTextBufferOpenedAsync(bool mutatingLspWorkspace) + public async Task TestDoesNotForkWhenDocumentOpenedInWorkspaceAsync(bool mutatingLspWorkspace) { var markup = "Text"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var documentUri = testLspServer.GetCurrentSolution().Projects.First().Documents.First().GetURI(); - // Calling get text buffer opens the document in the workspace. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Open document in the workspace. + await testLspServer.OpenDocumentInWorkspaceAsync(document.Id, openAllLinkedDocuments: false); await testLspServer.OpenDocumentAsync(documentUri, "Text"); diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs index 1d6ae9f07598d..eced98f15883e 100644 --- a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs @@ -171,7 +171,7 @@ internal async Task TestReturnsGeneratedSourceWhenDocumentChanges(bool mutatingL // In automatic mode this should trigger generators to re-run. // In balanced mode generators should not re-run. await testLspServer.TestWorkspace.ChangeDocumentAsync(testLspServer.TestWorkspace.Documents.Single(d => !d.IsSourceGenerated).Id, SourceText.From("new text")); - await WaitForSourceGeneratorsAsync(testLspServer.TestWorkspace); + await testLspServer.WaitForSourceGeneratorsAsync(); // Ask for the source generated text again. var secondRequest = await testLspServer.ExecuteRequestAsync(SourceGeneratedDocumentGetTextHandler.MethodName, @@ -216,7 +216,7 @@ internal async Task TestReturnsGeneratedSourceWhenManuallyRefreshed(bool mutatin // Updating the execution version should trigger source generators to run in both automatic and balanced mode. testLspServer.TestWorkspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); - await WaitForSourceGeneratorsAsync(testLspServer.TestWorkspace); + await testLspServer.WaitForSourceGeneratorsAsync(); var secondRequest = await testLspServer.ExecuteRequestAsync(SourceGeneratedDocumentGetTextHandler.MethodName, new SourceGeneratorGetTextParams(new LSP.TextDocumentIdentifier { Uri = sourceGeneratorDocumentUri }, ResultId: text.ResultId), CancellationToken.None); @@ -281,12 +281,6 @@ public async Task TestReturnsNullForRemovedOpenedGeneratedFile(bool mutatingLspW Assert.Null(secondRequest.Text); } - private static async Task WaitForSourceGeneratorsAsync(EditorTestWorkspace workspace) - { - var operations = workspace.ExportProvider.GetExportedValue(); - await operations.WaitAllAsync(workspace, [FeatureAttribute.Workspace, FeatureAttribute.SourceGenerators]); - } - private async Task CreateTestLspServerWithGeneratorAsync(bool mutatingLspWorkspace, string generatedDocumentText) { var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace); diff --git a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj index 92a958498c6cb..33d9538cb027a 100644 --- a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj +++ b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/Test/PdbUtilities/Reader/MockSymUnmanagedReader.cs b/src/Test/PdbUtilities/Reader/MockSymUnmanagedReader.cs index b158a7f9a477d..dfc3608111351 100644 --- a/src/Test/PdbUtilities/Reader/MockSymUnmanagedReader.cs +++ b/src/Test/PdbUtilities/Reader/MockSymUnmanagedReader.cs @@ -11,7 +11,7 @@ using System.Runtime.InteropServices.ComTypes; using DSR::Microsoft.DiaSymReader; using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis; using Xunit; namespace Roslyn.Test.Utilities diff --git a/src/Tools/BuildActionTelemetryTable/BuildActionTelemetryTable.csproj b/src/Tools/BuildActionTelemetryTable/BuildActionTelemetryTable.csproj index 783809b4edae9..76e418c1e9cd8 100644 --- a/src/Tools/BuildActionTelemetryTable/BuildActionTelemetryTable.csproj +++ b/src/Tools/BuildActionTelemetryTable/BuildActionTelemetryTable.csproj @@ -21,14 +21,11 @@ - - - - - + + diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index ed7802956b81f..57e8189c34faa 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -76,7 +76,7 @@ public static class Program { "Microsoft.CodeAnalysis.CodeStyle.CSharpFormattingCodeFixProvider", "Formatting" }, { "Microsoft.CodeAnalysis.CodeStyle.VisualBasicFormattingCodeFixProvider", "Formatting" }, { "Microsoft.CodeAnalysis.ConvertToInterpolatedString.ConvertRegularStringToInterpolatedStringRefactoringProvider", "Convert To Interpolated String: Convert Regular String To Interpolated String (Refactoring)" }, - { "Microsoft.CodeAnalysis.CSharp.AddAccessibilityModifiers.CSharpAddAccessibilityModifiersCodeFixProvider", "Add Accessibility Modifiers" }, + { "Microsoft.CodeAnalysis.CSharp.AddOrRemoveAccessibilityModifiers.CSharpAddOrRemoveAccessibilityModifiersCodeFixProvider", "Add Accessibility Modifiers" }, { "Microsoft.CodeAnalysis.CSharp.AddAnonymousTypeMemberName.CSharpAddAnonymousTypeMemberNameCodeFixProvider", "Add Anonymous Type Member Name" }, { "Microsoft.CodeAnalysis.CSharp.AddDebuggerDisplay.CSharpAddDebuggerDisplayCodeRefactoringProvider", "Add Debugger Display (Refactoring)" }, { "Microsoft.CodeAnalysis.CSharp.AddFileBanner.CSharpAddFileBannerCodeRefactoringProvider", "Add File Banner (Refactoring)" }, @@ -312,7 +312,7 @@ public static class Program { "Microsoft.CodeAnalysis.UseExplicitTupleName.UseExplicitTupleNameCodeFixProvider", "Use Explicit Tuple Name" }, { "Microsoft.CodeAnalysis.UseSystemHashCode.UseSystemHashCodeCodeFixProvider", "Use System Hash Code" }, { "Microsoft.CodeAnalysis.UseThrowExpression.UseThrowExpressionCodeFixProvider", "Use Throw Expression" }, - { "Microsoft.CodeAnalysis.VisualBasic.AddAccessibilityModifiers.VisualBasicAddAccessibilityModifiersCodeFixProvider", "Add Accessibility Modifiers" }, + { "Microsoft.CodeAnalysis.VisualBasic.AddOrRemoveAccessibilityModifiers.VisualBasicAddOrRemoveAccessibilityModifiersCodeFixProvider", "Add Accessibility Modifiers" }, { "Microsoft.CodeAnalysis.VisualBasic.AddAnonymousTypeMemberName.VisualBasicAddAnonymousTypeMemberNameCodeFixProvider", "Add Anonymous Type Member Name" }, { "Microsoft.CodeAnalysis.VisualBasic.AddDebuggerDisplay.VisualBasicAddDebuggerDisplayCodeRefactoringProvider", "Add Debugger Display (Refactoring)" }, { "Microsoft.CodeAnalysis.VisualBasic.AddFileBanner.VisualBasicAddFileBannerCodeRefactoringProvider", "Add File Banner (Refactoring)" }, diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 3a3c437a1ae7d..7f19bb818826a 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -31,45 +31,45 @@ static int Main(string[] args) { System.Diagnostics.Trace.Listeners.Clear(); - var assembliesPath = new Option("--assembliesPath") + var assembliesPath = new CliOption("--assembliesPath") { Description = BuildValidatorResources.Path_to_assemblies_to_rebuild_can_be_specified_one_or_more_times, Required = true, Arity = ArgumentArity.OneOrMore, }; - var exclude = new Option("--exclude") + var exclude = new CliOption("--exclude") { Description = BuildValidatorResources.Assemblies_to_be_excluded_substring_match, Arity = ArgumentArity.ZeroOrMore, }; - var source = new Option("--sourcePath") + var source = new CliOption("--sourcePath") { Description = BuildValidatorResources.Path_to_sources_to_use_in_rebuild, Required = true, }; - var referencesPath = new Option("--referencesPath") + var referencesPath = new CliOption("--referencesPath") { Description = BuildValidatorResources.Path_to_referenced_assemblies_can_be_specified_zero_or_more_times, Arity = ArgumentArity.ZeroOrMore, }; - var verbose = new Option("--verbose") + var verbose = new CliOption("--verbose") { Description = BuildValidatorResources.Output_verbose_log_information }; - var quiet = new Option("--quiet") + var quiet = new CliOption("--quiet") { Description = BuildValidatorResources.Do_not_output_log_information_to_console }; - var debug = new Option("--debug") + var debug = new CliOption("--debug") { Description = BuildValidatorResources.Output_debug_info_when_rebuild_is_not_equal_to_the_original }; - var debugPath = new Option("--debugPath") + var debugPath = new CliOption("--debugPath") { Description = BuildValidatorResources.Path_to_output_debug_info }; - var rootCommand = new RootCommand + var rootCommand = new CliRootCommand { assembliesPath, exclude, diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index 6164b50074dd8..92be22f2f3e09 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Tools/ExternalAccess/Razor/RazorPredefinedCodeFixProviderNames.cs b/src/Tools/ExternalAccess/Razor/RazorPredefinedCodeFixProviderNames.cs index 34bd12e2e12f1..991e600c2e763 100644 --- a/src/Tools/ExternalAccess/Razor/RazorPredefinedCodeFixProviderNames.cs +++ b/src/Tools/ExternalAccess/Razor/RazorPredefinedCodeFixProviderNames.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { internal static class RazorPredefinedCodeFixProviderNames { - public static string AddAccessibilityModifiers => PredefinedCodeFixProviderNames.AddAccessibilityModifiers; + public static string AddAccessibilityModifiers => PredefinedCodeFixProviderNames.AddOrRemoveAccessibilityModifiers; public static string AddAnonymousTypeMemberName => PredefinedCodeFixProviderNames.AddAnonymousTypeMemberName; public static string AddAsync => PredefinedCodeFixProviderNames.AddAsync; public static string AddBraces => PredefinedCodeFixProviderNames.AddBraces; diff --git a/src/Tools/ExternalAccess/Xaml/Microsoft.CodeAnalysis.ExternalAccess.Xaml.csproj b/src/Tools/ExternalAccess/Xaml/Microsoft.CodeAnalysis.ExternalAccess.Xaml.csproj index 4310078bf9b32..d17ef579337c3 100644 --- a/src/Tools/ExternalAccess/Xaml/Microsoft.CodeAnalysis.ExternalAccess.Xaml.csproj +++ b/src/Tools/ExternalAccess/Xaml/Microsoft.CodeAnalysis.ExternalAccess.Xaml.csproj @@ -31,14 +31,7 @@ - - - - - - - diff --git a/src/Tools/Replay/Replay.csproj b/src/Tools/Replay/Replay.csproj index 0f52b65a3e8f4..fd3d130c7aca8 100644 --- a/src/Tools/Replay/Replay.csproj +++ b/src/Tools/Replay/Replay.csproj @@ -11,12 +11,8 @@ - - - - @@ -36,4 +32,6 @@ + + diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs index 6f394264544f9..be93f54c55fba 100644 --- a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -149,31 +149,14 @@ internal void WriteApis(string outputFilePath, byte[] peImage) var newContent = "# Generated, do not update manually\r\n" + string.Join("\r\n", apis); - string currentContent; try { - currentContent = File.ReadAllText(outputFilePath, Encoding.UTF8); + File.WriteAllText(outputFilePath, newContent); + Log.LogMessage($"Baseline updated: '{outputFilePath}'"); } - catch (Exception) - { - currentContent = ""; - } - - if (currentContent != newContent) - { - try - { - File.WriteAllText(outputFilePath, newContent); - Log.LogMessage($"Baseline updated: '{outputFilePath}'"); - } - catch (Exception e) - { - Log.LogError($"Error updating baseline '{outputFilePath}': {e.Message}"); - } - } - else + catch (Exception e) { - Log.LogMessage($"Baseline not updated '{outputFilePath}'"); + Log.LogError($"Error updating baseline '{outputFilePath}': {e.Message}"); } } diff --git a/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj b/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj index 9e28511dab63f..f0184c57d0cd0 100644 --- a/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj +++ b/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj @@ -17,10 +17,9 @@ - - + diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt index 7f8d403ef4ae5..ae3b53b6e80d9 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt @@ -542,6 +542,7 @@ Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(System.String) Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGeneratorsForAllLanguages Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetHashCode +Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.ToString Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.add_AnalyzerLoadFailed(System.EventHandler{Microsoft.CodeAnalysis.Diagnostics.AnalyzerLoadFailureEventArgs}) Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.get_AssemblyLoader Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.get_Display diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs index 5b549fcc253d8..9df6aa70e214d 100644 --- a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs +++ b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading; @@ -9,17 +10,17 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.UnitTests; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServices.DocumentOutline; using Microsoft.VisualStudio.Text; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; using StreamJsonRpc; using Xunit.Abstractions; using static Roslyn.Test.Utilities.AbstractLanguageServerProtocolTests; @@ -94,7 +95,7 @@ protected async Task CreateMocksAsync(string code) } } - private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace) + private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace) { var solution = workspace.CurrentSolution; @@ -118,10 +119,25 @@ private async Task CreateTestLspServerAsync(EditorTestWorkspace w solution = solution.WithAnalyzerReferences([new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap())]); await workspace.ChangeSolutionAsync(solution); - var server = await TestLspServer.CreateAsync(workspace, new InitializationOptions(), _logger); + var server = await EditorTestLspServer.CreateAsync(workspace, new InitializationOptions(), _logger); return server; } + internal class EditorTestLspServer : AbstractTestLspServer + { + private EditorTestLspServer(EditorTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions options, AbstractLspLogger logger) : base(testWorkspace, locations, options, logger) + { + } + + public static async Task CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new EditorTestLspServer(testWorkspace, locations, initializationOptions, logger); + await server.InitializeAsync(); + return server; + } + } + [DataContract] private class NewtonsoftInitializeParams { diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index cab7be9c43233..3b17836fefa1e 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -16,6 +16,7 @@ + diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/TestEvaluationData.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/TestEvaluationData.cs index 3eea12d66da49..055f3bfdb87e4 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/TestEvaluationData.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/TestEvaluationData.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; -using Roslyn.Utilities; namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim; diff --git a/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs b/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs index 45bd1af61476a..5be87f357eafc 100644 --- a/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs +++ b/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs @@ -31,18 +31,18 @@ internal static class CommonCodeCleanUpFixerDiagnosticIds public static readonly FixIdDefinition? RemoveQualificationDiagnosticId; [Export] - [FixId(IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId)] - [Name(IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId)] + [FixId(IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId)] + [Name(IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId)] [Order(After = IDEDiagnosticIds.AddBracesDiagnosticId)] [ConfigurationKey("unused")] - [HelpLink($"https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/{IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId}")] + [HelpLink($"https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/{IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId}")] [LocalizedName(typeof(AnalyzersResources), nameof(AnalyzersResources.Add_accessibility_modifiers))] - public static readonly FixIdDefinition? AddAccessibilityModifiersDiagnosticId; + public static readonly FixIdDefinition? AddOrRemoveAccessibilityModifiersDiagnosticId; [Export] [FixId(IDEDiagnosticIds.OrderModifiersDiagnosticId)] [Name(IDEDiagnosticIds.OrderModifiersDiagnosticId)] - [Order(After = IDEDiagnosticIds.AddAccessibilityModifiersDiagnosticId)] + [Order(After = IDEDiagnosticIds.AddOrRemoveAccessibilityModifiersDiagnosticId)] [ConfigurationKey("unused")] [HelpLink($"https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/{IDEDiagnosticIds.OrderModifiersDiagnosticId}")] [LocalizedName(typeof(AnalyzersResources), nameof(AnalyzersResources.Order_modifiers))] diff --git a/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.cs b/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.cs index b9ac22121c1fd..04e21d0fe143c 100644 --- a/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.cs +++ b/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.cs @@ -13,12 +13,12 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; -using Roslyn.Utilities; using Task = System.Threading.Tasks.Task; namespace Microsoft.CodeAnalysis.ColorSchemes; diff --git a/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2017.xml b/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2017.xml index ba5c889c9428f..2cf5c5cfa8692 100644 --- a/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2017.xml +++ b/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2017.xml @@ -790,7 +790,8 @@ - + + diff --git a/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2019.xml b/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2019.xml index 99a3dbfdf8685..7dc8bafcedef3 100644 --- a/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2019.xml +++ b/src/VisualStudio/Core/Def/ColorSchemes/VisualStudio2019.xml @@ -817,7 +817,8 @@ - + + diff --git a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs index 7a52ecb07cf53..1998be1c60a8c 100644 --- a/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs +++ b/src/VisualStudio/Core/Def/DebuggerIntelliSense/DebuggerIntellisenseFilter.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; @@ -15,7 +16,6 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense; diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index 09e002ab96aa5..699bfc38552a4 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Designer.Interfaces; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs index 0edbbe0210450..6a7cbe8655fe6 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs @@ -47,7 +47,7 @@ public async Task GetOrCreateProviderAsy // this will allow us to build once and deploy on different versions of VS SxS. var vsDteVersion = Version.Parse(dte.Version.Split(' ')[0]); // DTE.Version is in the format of D[D[.D[D]]][ (?+)], so we need to split out the version part and check for uninitialized Major/Minor below - var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, PublicKeyToken=b03f5f7f11d50a3a"); + var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); var typeIExtensionContent = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.IExtensionContent"); var type = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.SVsExtensionManager"); var extensionManager = _serviceProvider.GetService(type); diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs index 439e12fccf8fa..02e1bffa43eb0 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServices.Utilities; diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolDataViewModelSorter.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolDataViewModelSorter.cs index 351bcc84e61b8..db3bc907a9933 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolDataViewModelSorter.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolDataViewModelSorter.cs @@ -8,7 +8,7 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Markup; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.DocumentOutline; diff --git a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index a4a0895b1616d..0a7c6416824b2 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -25,6 +25,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/SimpleMessageEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/SimpleMessageEntry.cs index d0e60da16ee5b..3265dc00db67a 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Entries/SimpleMessageEntry.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/SimpleMessageEntry.cs @@ -4,9 +4,9 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Navigation; using Microsoft.VisualStudio.Shell.TableManager; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.FindUsages; diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.Query.cs b/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.Query.cs index 3d7ae6167f9e0..5ecfbc0281b4a 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.Query.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.Query.cs @@ -5,8 +5,8 @@ #nullable disable using System; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.OLE.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation; diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.cs b/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.cs index 937c7342b8289..47fca7fc4bda2 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractOleCommandTarget.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.ComponentModelHost; @@ -12,7 +13,6 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation; diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs index c76b3b9699c59..70eb12b511530 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractVsTextViewFilter.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.BraceMatching; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Editor; diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs index 41b73f8a55c5c..5d14623779347 100644 --- a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Shell; diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs b/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs index ea160fee7fdee..ef414babf9f31 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/MenuItemContainerTemplateSelector.cs @@ -4,7 +4,7 @@ using System.Windows; using System.Windows.Controls; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph; diff --git a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs index bd38aac5f3580..9b4fe70571d2a 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs @@ -12,6 +12,7 @@ using System.Diagnostics; using System.Linq; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Interactive; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.InteractiveWindow.Commands; diff --git a/src/VisualStudio/Core/Def/Interop/ComAggregate.cs b/src/VisualStudio/Core/Def/Interop/ComAggregate.cs index 463116bded3f7..86c23d4e7d16e 100644 --- a/src/VisualStudio/Core/Def/Interop/ComAggregate.cs +++ b/src/VisualStudio/Core/Def/Interop/ComAggregate.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop; diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index e94d9c9579b6b..cd088e1f5d81c 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.PlatformUI.OleComponentSupport; diff --git a/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs b/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs index 4aacd546fca84..85206d8e1ab58 100644 --- a/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs +++ b/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs @@ -4,10 +4,10 @@ using System; using System.Diagnostics; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.VisualStudio.LogHub; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient; diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs index 629a09e27f115..01e7b4d0faf02 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs @@ -16,9 +16,9 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs index 9820e02d3087e..8cd5493da8635 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs @@ -5,9 +5,9 @@ #nullable disable using System; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; -using Roslyn.Utilities; using IVsEnumBSTR = Microsoft.VisualStudio.TextManager.Interop.IVsEnumBSTR; using IVsTextBuffer = Microsoft.VisualStudio.TextManager.Interop.IVsTextBuffer; using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan; diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs index e6babc4236130..64ff1b7dde1fb 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs @@ -35,16 +35,17 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke Assumes.Present(_componentModel_doNotAccessDirectly); } - protected override Task OnAfterPackageLoadedAsync(CancellationToken cancellationToken) + protected override async Task OnAfterPackageLoadedAsync(CancellationToken cancellationToken) { + await base.OnAfterPackageLoadedAsync(cancellationToken).ConfigureAwait(false); + // TODO: remove, workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1985204 var globalOptions = ComponentModel.GetService(); if (globalOptions.GetOption(SemanticSearchFeatureFlag.Enabled)) { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); UIContext.FromUIContextGuid(new Guid(SemanticSearchFeatureFlag.UIContextId)).IsActive = true; } - - return base.OnAfterPackageLoadedAsync(cancellationToken); } protected async Task LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 007c55fa569e3..6bb7c7aca2d1f 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -51,9 +51,6 @@ - - - diff --git a/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs b/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs index 8597fe3e621cd..ce1819728e7cc 100644 --- a/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs +++ b/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Wpf; @@ -17,7 +18,6 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.NavigationBar; diff --git a/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs b/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs index a15b51d241d7d..66621e6fd58fb 100644 --- a/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs +++ b/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs @@ -4,10 +4,10 @@ using System; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Options; using Microsoft.Internal.VisualStudio.Shell.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Options; diff --git a/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs b/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs index 1e6dc3b3e4d9e..8387e52ae155a 100644 --- a/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs @@ -4,10 +4,10 @@ using System; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.Win32; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Options; diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersister.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersister.cs index 4d5deb8bbcd9c..532b178e16347 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersister.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersisterProvider.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersisterProvider.cs index b7a78d2196971..4827ef4fcb0ae 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersisterProvider.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionPersisterProvider.cs @@ -8,6 +8,7 @@ using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index a602ba3b033f2..9e2c2a3b16ae2 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -26,6 +26,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.SymbolSearch; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.SymbolSearch; diff --git a/src/VisualStudio/Core/Def/PdbSourceDocument/PdbSourceDocumentOutputWindowLogger.cs b/src/VisualStudio/Core/Def/PdbSourceDocument/PdbSourceDocumentOutputWindowLogger.cs index 7a880dd52a724..9992c08ca693c 100644 --- a/src/VisualStudio/Core/Def/PdbSourceDocument/PdbSourceDocumentOutputWindowLogger.cs +++ b/src/VisualStudio/Core/Def/PdbSourceDocument/PdbSourceDocumentOutputWindowLogger.cs @@ -13,9 +13,9 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PdbSourceDocument; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.PdbSourceDocument; diff --git a/src/VisualStudio/Core/Def/PreviewPane/PreviewPane.xaml.cs b/src/VisualStudio/Core/Def/PreviewPane/PreviewPane.xaml.cs index 764c706bcb21e..d0fbc450c5737 100644 --- a/src/VisualStudio/Core/Def/PreviewPane/PreviewPane.xaml.cs +++ b/src/VisualStudio/Core/Def/PreviewPane/PreviewPane.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Navigation; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics.Log; using Microsoft.CodeAnalysis.Editor.Implementation.Preview; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs b/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs index 034074da3a89b..d9bcc816f68cf 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.GraphModel; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/FileChangeTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/FileChangeTracker.cs index 34db1dfbb8256..ad0eaf96c4078 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/FileChangeTracker.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/FileChangeTracker.cs @@ -13,6 +13,7 @@ using IVsAsyncFileChangeEx2 = Microsoft.VisualStudio.Shell.IVsAsyncFileChangeEx2; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/FileChangeWatcher.cs b/src/VisualStudio/Core/Def/ProjectSystem/FileChangeWatcher.cs index c4c5124e4bf76..d25c1736e9826 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/FileChangeWatcher.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/FileChangeWatcher.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; using IVsAsyncFileChangeEx2 = Microsoft.VisualStudio.Shell.IVsAsyncFileChangeEx2; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs b/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs index 4a7e5a4639726..34a7c77ccc94a 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; @@ -14,7 +15,6 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs index 9a78bcbae6dbf..df4ea8ce29de6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 620d493be4cef..92da315daac29 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api; using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; @@ -33,6 +34,7 @@ internal sealed class VisualStudioProjectFactory : IVsTypeScriptVisualStudioProj private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl; private readonly ImmutableArray> _dynamicFileInfoProviders; private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _vsixAnalyzerProviderFactory; + private readonly ImmutableArray _analyzerAssemblyRedirectors; private readonly IVsService _solution2; [ImportingConstructor] @@ -42,12 +44,14 @@ public VisualStudioProjectFactory( VisualStudioWorkspaceImpl visualStudioWorkspaceImpl, [ImportMany] IEnumerable> fileInfoProviders, IVisualStudioDiagnosticAnalyzerProviderFactory vsixAnalyzerProviderFactory, + [ImportMany] IEnumerable analyzerAssemblyRedirectors, IVsService solution2) { _threadingContext = threadingContext; _visualStudioWorkspaceImpl = visualStudioWorkspaceImpl; _dynamicFileInfoProviders = fileInfoProviders.AsImmutableOrEmpty(); _vsixAnalyzerProviderFactory = vsixAnalyzerProviderFactory; + _analyzerAssemblyRedirectors = analyzerAssemblyRedirectors.AsImmutableOrEmpty(); _solution2 = solution2; } @@ -93,7 +97,7 @@ public async Task CreateAndAddToWorkspaceAsync( _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionPath = solutionFilePath; _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionTelemetryId = GetSolutionSessionId(); - var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, vsixAnalyzerProvider); + var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, vsixAnalyzerProvider, _analyzerAssemblyRedirectors); var project = await _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync(projectSystemName, language, creationInfo, hostInfo); _visualStudioWorkspaceImpl.AddProjectToInternalMaps(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); diff --git a/src/VisualStudio/Core/Def/RoslynActivityLogger.cs b/src/VisualStudio/Core/Def/RoslynActivityLogger.cs index c8aa1ca25a30f..307b9cdc4c1d5 100644 --- a/src/VisualStudio/Core/Def/RoslynActivityLogger.cs +++ b/src/VisualStudio/Core/Def/RoslynActivityLogger.cs @@ -6,8 +6,8 @@ using System.Diagnostics; using System.Threading; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Internal.Log; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices; diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index d0980fe34b761..9baed617356bc 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ColorSchemes; using Microsoft.CodeAnalysis.Common; using Microsoft.CodeAnalysis.EditAndContinue; diff --git a/src/VisualStudio/Core/Def/SolutionEventMonitor.cs b/src/VisualStudio/Core/Def/SolutionEventMonitor.cs index 18d960b371a7e..5011b4542c63d 100644 --- a/src/VisualStudio/Core/Def/SolutionEventMonitor.cs +++ b/src/VisualStudio/Core/Def/SolutionEventMonitor.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Notification; using Microsoft.VisualStudio.Shell; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices; diff --git a/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerCommandHandler.cs b/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerCommandHandler.cs index 610808ca0e25a..ea22f4b2be2a4 100644 --- a/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerCommandHandler.cs +++ b/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerCommandHandler.cs @@ -5,6 +5,7 @@ using System; using System.ComponentModel.Design; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; diff --git a/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs b/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs index df77fde5013ba..73455ee6880d2 100644 --- a/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs +++ b/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index 083a2fc3c2efa..e29643e157e4e 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -486,7 +486,7 @@ private async Task>> Ge RoslynDebug.AssertNotNull(latestDocumentDiagnosticsMap); var uniqueDiagnosticIds = group.SelectMany(kvp => kvp.Value.Select(d => d.Id)).ToImmutableHashSet(); - var latestProjectDiagnostics = (await _diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id, documentId: null, + var latestProjectDiagnostics = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)).Where(IsDocumentDiagnostic); @@ -576,7 +576,7 @@ private async Task>> Get RoslynDebug.AssertNotNull(latestDiagnosticsToFix); var uniqueDiagnosticIds = diagnostics.Select(d => d.Id).ToImmutableHashSet(); - var latestDiagnosticsFromDiagnosticService = (await _diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id, documentId: null, + var latestDiagnosticsFromDiagnosticService = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)); diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index e370f5eee08c0..eb8d2f0c68786 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.RpcContracts.DiagnosticManagement; using Microsoft.VisualStudio.RpcContracts.Utilities; diff --git a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs index cb1ef8c812cbd..7921adfd5ddae 100644 --- a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs @@ -5,11 +5,11 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Telemetry; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Telemetry; diff --git a/src/VisualStudio/Core/Def/Telemetry/FileLogger.cs b/src/VisualStudio/Core/Def/Telemetry/FileLogger.cs index 916429085c48e..92187c85c6a8b 100644 --- a/src/VisualStudio/Core/Def/Telemetry/FileLogger.cs +++ b/src/VisualStudio/Core/Def/Telemetry/FileLogger.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Telemetry; diff --git a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs index 3b3a85b6b5fe4..2fa4f7ad2c105 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VisualStudioWorkspaceTelemetryService.cs @@ -6,16 +6,15 @@ using System.Composition; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Telemetry; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Telemetry; diff --git a/src/VisualStudio/Core/Def/UnusedReferences/UnusedReferenceExtensions.cs b/src/VisualStudio/Core/Def/UnusedReferences/UnusedReferenceExtensions.cs index 5c6251c350a89..66165dfc70e97 100644 --- a/src/VisualStudio/Core/Def/UnusedReferences/UnusedReferenceExtensions.cs +++ b/src/VisualStudio/Core/Def/UnusedReferences/UnusedReferenceExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.UnusedReferences; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.ProjectSystem.Api; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs b/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs index 5f98a5145219a..23c46561cc095 100644 --- a/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs +++ b/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs @@ -6,13 +6,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Utilities; diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index d4757ab3baa21..0b511973a49b5 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs @@ -139,8 +139,6 @@ internal ContainedLanguage( this.DataBuffer.Changed += OnDataBufferChanged; } - public IGlobalOptionService GlobalOptions => _diagnosticAnalyzerService.GlobalOptions; - private void OnDisconnect() { this.DataBuffer.Changed -= OnDataBufferChanged; diff --git a/src/VisualStudio/Core/Def/Venus/VenusCommandFilter.cs b/src/VisualStudio/Core/Def/Venus/VenusCommandFilter.cs index 4749f29b355ae..3a14dc0bf351d 100644 --- a/src/VisualStudio/Core/Def/Venus/VenusCommandFilter.cs +++ b/src/VisualStudio/Core/Def/Venus/VenusCommandFilter.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; @@ -14,7 +15,6 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus; diff --git a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs index 0af0d25e5fda4..2f9c4cde491f2 100644 --- a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Imaging.Interop; diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index e27de36eb5a39..c1a74289d86d3 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Impl/Options/Style/EnumCodeStyleOptionViewModel.cs b/src/VisualStudio/Core/Impl/Options/Style/EnumCodeStyleOptionViewModel.cs index b8156f7ce1f45..2943777c92018 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/EnumCodeStyleOptionViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/EnumCodeStyleOptionViewModel.cs @@ -8,9 +8,9 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options { diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs index 7db53244947e0..0cf1db496727d 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SourceGeneration; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Shell; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerReferenceManager.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerReferenceManager.cs index 453f8f4ed10d1..d1e4d14816e2f 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerReferenceManager.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerReferenceManager.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs index 09c14500e946e..2197f03fa4d48 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SourceGeneration; +using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Shell; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index 96bc24025d51c..c667b11bce123 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Microsoft.Internal.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Language.Intellisense; using Roslyn.Utilities; diff --git a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj index ffd478efc1217..3573c500702b9 100644 --- a/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj +++ b/src/VisualStudio/Core/Test.Next/Roslyn.VisualStudio.Next.UnitTests.csproj @@ -71,8 +71,4 @@ - - - - \ No newline at end of file diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 06f61fddcb5f2..b9908670866a8 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; using Xunit; diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 2626d6f12bbf8..f061e876b2ab5 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -95,7 +95,7 @@ public async Task TestRemoteHostTextSynchronize() // sync await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeTextChangesAsync([(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText).AsImmutable(), newState.Text)], cancellationToken), + (service, cancellationToken) => service.SynchronizeTextChangesAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText).AsImmutable(), newState.Text, cancellationToken), CancellationToken.None); // check that text already exist in remote side diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index b5c05e9dfb7c7..425004234ef26 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -62,8 +62,7 @@ void Method() Assert.Equal(isHostAnalyzer ? DiagnosticSeverity.Info : DiagnosticSeverity.Hidden, diagnostics[0].Severity); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVisualBasicAnalyzerOptions(bool isHostAnalyzer) { var code = @"Class Test @@ -83,7 +82,7 @@ End Sub ImmutableArray diagnostics; if (isHostAnalyzer) { - Assert.True(analyzerResult.IsEmpty); + Assert.True(analyzerResult.GetAllDiagnostics().IsEmpty); } else { diff --git a/src/VisualStudio/Core/Test.Next/UnifiedSettings/TestModel/Utilities.cs b/src/VisualStudio/Core/Test.Next/UnifiedSettings/TestModel/Utilities.cs index 01a816265e1c2..1287a12ab69bc 100644 --- a/src/VisualStudio/Core/Test.Next/UnifiedSettings/TestModel/Utilities.cs +++ b/src/VisualStudio/Core/Test.Next/UnifiedSettings/TestModel/Utilities.cs @@ -4,9 +4,9 @@ using System; using System.Globalization; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.LanguageServices.CSharp; -using Roslyn.Utilities; using CSharpPackage = Microsoft.VisualStudio.LanguageServices.CSharp.VSPackage; using VisualBasicPackage = Microsoft.VisualStudio.LanguageServices.VisualBasic.VSPackage; diff --git a/src/VisualStudio/Core/Test/ClassView/MockVsServiceProvider.vb b/src/VisualStudio/Core/Test/ClassView/MockVsServiceProvider.vb index 10b5a08695ac7..0921df8013f73 100644 --- a/src/VisualStudio/Core/Test/ClassView/MockVsServiceProvider.vb +++ b/src/VisualStudio/Core/Test/ClassView/MockVsServiceProvider.vb @@ -2,9 +2,9 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports Microsoft.CodeAnalysis Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop -Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ClassView Friend Class MockServiceProvider diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 71428bfca2712..9476f840de109 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -52,7 +52,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim threadingContext = workspace.ExportProvider.GetExport(Of IThreadingContext).Value - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() vsWorkspace.SetWorkspace(workspace) Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)() @@ -71,7 +71,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Async Function TestExternalDiagnostics_SupportedDiagnosticId_Concurrent() As Task Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition) Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList) - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() vsWorkspace.SetWorkspace(workspace) Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)() @@ -98,7 +98,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim threadingContext = workspace.ExportProvider.GetExport(Of IThreadingContext).Value - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() vsWorkspace.SetWorkspace(workspace) Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)() @@ -119,7 +119,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() Dim diagnostic = GetDiagnosticData(project.Id) - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() @@ -154,7 +154,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() @@ -195,7 +195,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() Dim diagnostic = GetDiagnosticData(project.Id) - Dim service = New TestDiagnosticAnalyzerService(globalOptions) + Dim service = New TestDiagnosticAnalyzerService() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() @@ -233,8 +233,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim registration = service.CreateIncrementalAnalyzer(workspace) + Dim service = workspace.GetService(Of IDiagnosticAnalyzerService)() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() @@ -300,11 +299,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Private ReadOnly _analyzerInfoCache As DiagnosticAnalyzerInfoCache - Public ReadOnly Property GlobalOptions As IGlobalOptionService Implements IDiagnosticAnalyzerService.GlobalOptions - - Public Sub New(globalOptions As IGlobalOptionService, Optional data As ImmutableArray(Of DiagnosticData) = Nothing) + Public Sub New() _analyzerInfoCache = New DiagnosticAnalyzerInfoCache() - Me.GlobalOptions = globalOptions End Sub Public ReadOnly Property AnalyzerInfoCache As DiagnosticAnalyzerInfoCache Implements IDiagnosticAnalyzerService.AnalyzerInfoCache @@ -316,23 +312,19 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub RequestDiagnosticRefresh() Implements IDiagnosticAnalyzerService.RequestDiagnosticRefresh End Sub - Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), includeCompilerDiagnostics As Boolean, priority As ICodeActionRequestPriorityProvider, diagnosticKinds As DiagnosticKind, isExplicit As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync + Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), priority As ICodeActionRequestPriorityProvider, diagnosticKinds As DiagnosticKind, isExplicit As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData) End Function - Public Function GetCachedDiagnosticsAsync(workspace As Workspace, projectId As ProjectId, documentId As DocumentId, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetCachedDiagnosticsAsync - Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() - End Function - - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync + Public Function GetDiagnosticsForIdsAsync(project As Project, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetProjectDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync + Public Function GetProjectDiagnosticsForIdsAsync(project As Project, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function ForceAnalyzeProjectAsync(project As Project, cancellationToken As CancellationToken) As Task Implements IDiagnosticAnalyzerService.ForceAnalyzeProjectAsync + Public Function ForceAnalyzeProjectAsync(project As Project, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.ForceAnalyzeProjectAsync Throw New NotImplementedException() End Function End Class diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index 6d9f10df1256e..c0d0b85ee28c5 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -2,13 +2,13 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Collections.Immutable +Imports System.ComponentModel.Composition Imports System.IO Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.[Shared].TestHooks +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework @@ -285,5 +285,43 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Assert.False(project.HasSdkCodeStyleAnalyzers) End Using End Function + + + Public Async Function RedirectedAnalyzers_CSharp() As Task + Using environment = New TestEnvironment(GetType(Redirector)) + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "Project", LanguageNames.CSharp, CancellationToken.None) + + ' Add analyzers + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "analyzers", "Microsoft.CodeAnalysis.NetAnalyzers.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "analyzers", "Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) + + ' Ensure the SDK ones are redirected + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "analyzers", "Microsoft.CodeAnalysis.NetAnalyzers.redirected.dll"), + Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "analyzers", "Microsoft.CodeAnalysis.CSharp.NetAnalyzers.redirected.dll"), + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + End Using + End Function + + + Private Class Redirector + Implements IAnalyzerAssemblyRedirector + + + Public Sub New() + End Sub + + Public Function RedirectPath(fullPath As String) As String Implements IAnalyzerAssemblyRedirector.RedirectPath + If fullPath.Contains("Microsoft.NET.Sdk") Then + Return Path.ChangeExtension(fullPath, ".redirected.dll") + End If + + Return Nothing + End Function + End Class End Class End Namespace diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 85111a14aaf6e..38be0fb67008c 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -229,17 +229,14 @@ class { } ' confirm there are errors Assert.True(model.GetDiagnostics().Any()) - Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) + Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() ' confirm diagnostic support is off for the document Assert.False(document.SupportsDiagnostics()) - ' register the workspace to the service - diagnosticService.CreateIncrementalAnalyzer(workspace) - ' confirm that IDE doesn't report the diagnostics Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + document.Project, documentId:=document.Id, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using diff --git a/src/VisualStudio/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj b/src/VisualStudio/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj index d5e722268a4da..e041981e98b54 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj +++ b/src/VisualStudio/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj @@ -15,10 +15,6 @@ - - - - - Library Roslyn.VisualStudio.Setup @@ -50,7 +49,7 @@ true BindingRedirect - + Microsoft.CodeAnalysis.ExternalAccess.Copilot BuiltProjectOutputGroup true diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest index 438a34bfc9481..545f5f5674c87 100644 --- a/src/VisualStudio/Setup/source.extension.vsixmanifest +++ b/src/VisualStudio/Setup/source.extension.vsixmanifest @@ -47,7 +47,6 @@ - @@ -70,8 +69,6 @@ - - diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs index 373e438aed590..427b2fb7060f5 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs @@ -5,10 +5,10 @@ using System; using System.Runtime.InteropServices; using System.Windows.Controls; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.Shell; using Roslyn.Hosting.Diagnostics.PerfMargin; -using Roslyn.Utilities; namespace Roslyn.VisualStudio.DiagnosticsWindow { diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs index bad837fd8f3bf..db6d1fcff6d58 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Commands/CreateEventCommandHandler.cs @@ -7,14 +7,15 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Xaml; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Commands; -using Roslyn.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Commands; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion; using Newtonsoft.Json.Linq; +using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs index 4fc4845f0ec71..ab75b1ca74f47 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -12,6 +12,7 @@ using Roslyn.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.AutoInsert; using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs index f5b603d1d93d1..d185a95657da5 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnTypeRename/OnTypeRenameHandler.cs @@ -13,6 +13,7 @@ using Roslyn.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.TypeRename; using Roslyn.Utilities; +using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler { diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index cf2aa2ed0d0e9..d5c6575451d44 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -277,7 +277,7 @@ private protected override SyntaxNode OperatorDeclaration(string operatorName, b var modifierList = AsModifierList(accessibility, modifiers, SyntaxKind.OperatorDeclaration); var attributes = default(SyntaxList); - if (operatorName is WellKnownMemberNames.ImplicitConversionName or WellKnownMemberNames.ExplicitConversionName) + if (operatorName is WellKnownMemberNames.ImplicitConversionName or WellKnownMemberNames.ExplicitConversionName or WellKnownMemberNames.CheckedExplicitConversionName) { var isImplicit = operatorName is WellKnownMemberNames.ImplicitConversionName; return SyntaxFactory.ConversionOperatorDeclaration( @@ -285,7 +285,7 @@ private protected override SyntaxNode OperatorDeclaration(string operatorName, b isImplicit ? ImplicitKeyword : ExplicitKeyword, explicitInterfaceSpecifier: null, OperatorKeyword, - checkedKeyword: default, + checkedKeyword: CSharp.SyntaxFacts.IsCheckedOperator(operatorName) ? CheckedKeyword : default, returnTypeNode, parameterList, body, expressionBody: null, semicolon); } else @@ -440,13 +440,15 @@ private static SyntaxNode AccessorDeclaration( public override SyntaxNode WithAccessorDeclarations(SyntaxNode declaration, IEnumerable accessorDeclarations) => declaration switch { - PropertyDeclarationSyntax property => property.WithAccessorList(CreateAccessorList(property.AccessorList, accessorDeclarations)) - .WithExpressionBody(null) - .WithSemicolonToken(default), + PropertyDeclarationSyntax property => + property.WithAccessorList(CreateAccessorList(property.AccessorList, accessorDeclarations)) + .WithExpressionBody(null) + .WithSemicolonToken(property.Initializer is null ? default : property.SemicolonToken), - IndexerDeclarationSyntax indexer => indexer.WithAccessorList(CreateAccessorList(indexer.AccessorList, accessorDeclarations)) - .WithExpressionBody(null) - .WithSemicolonToken(default), + IndexerDeclarationSyntax indexer => + indexer.WithAccessorList(CreateAccessorList(indexer.AccessorList, accessorDeclarations)) + .WithExpressionBody(null) + .WithSemicolonToken(default), _ => declaration, }; diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index 717c86bb6493b..9883526bd0041 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -1001,6 +1001,24 @@ public void TestConversionOperatorDeclaration() "public static implicit operator global::System.Decimal(global::System.Byte value)\r\n{\r\n}"); } + [Fact, WorkItem(77101, "https://github.com/dotnet/roslyn/issues/77101")] + public void TestExplicitCheckedOperatorFromSymbol() + { + var compilation = CSharpCompilation.Create("Test", [SyntaxFactory.ParseSyntaxTree(""" + public class C + { + public static explicit operator checked int(C c) => 0; + } + """)]); + + var c = compilation.GetTypeByMetadataName("C"); + var op = c.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Conversion).Single(); + + VerifySyntax( + Generator.OperatorDeclaration(op), + "public static explicit operator checked global::System.Int32(global::C c)\r\n{\r\n}"); + } + [Fact] public void TestConstructorDeclaration() { diff --git a/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs b/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs index 3064aa9009685..6c97f2080b6e3 100644 --- a/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs +++ b/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -28,9 +27,7 @@ internal abstract partial class SyntaxEditorBasedCodeRefactoringProvider : CodeR return FixAllProvider.Create( async (fixAllContext, document, fixAllSpans) => - { - return await this.FixAllAsync(document, fixAllSpans, fixAllContext.CodeActionEquivalenceKey, fixAllContext.CancellationToken).ConfigureAwait(false); - }, + await this.FixAllAsync(document, fixAllSpans, fixAllContext.CodeActionEquivalenceKey, fixAllContext.CancellationToken).ConfigureAwait(false), SupportedFixAllScopes); } diff --git a/src/Workspaces/Core/Portable/Diagnostics/CompilationWithAnalyzersPair.cs b/src/Workspaces/Core/Portable/Diagnostics/CompilationWithAnalyzersPair.cs index e9b7f7f73746e..5e308ed7521be 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/CompilationWithAnalyzersPair.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/CompilationWithAnalyzersPair.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index 9cd424ee9c284..8a7b8371ab1f0 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -20,59 +21,40 @@ namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics; /// internal readonly struct DiagnosticAnalysisResult { - public readonly bool FromBuild; public readonly ProjectId ProjectId; - public readonly VersionStamp Version; /// /// The set of documents that has any kind of diagnostics on it. /// public readonly ImmutableHashSet? DocumentIds; - public readonly bool IsEmpty; /// /// Syntax diagnostics from this file. /// - private readonly ImmutableDictionary>? _syntaxLocals; + private readonly ImmutableDictionary> _syntaxLocals; /// /// Semantic diagnostics from this file. /// - private readonly ImmutableDictionary>? _semanticLocals; + private readonly ImmutableDictionary> _semanticLocals; /// /// Diagnostics that were produced for these documents, but came from the analysis of other files. /// - private readonly ImmutableDictionary>? _nonLocals; + private readonly ImmutableDictionary> _nonLocals; /// /// Diagnostics that don't have locations. /// private readonly ImmutableArray _others; - private DiagnosticAnalysisResult(ProjectId projectId, VersionStamp version, ImmutableHashSet? documentIds, bool isEmpty, bool fromBuild) - { - ProjectId = projectId; - Version = version; - DocumentIds = documentIds; - IsEmpty = isEmpty; - FromBuild = fromBuild; - - _syntaxLocals = null; - _semanticLocals = null; - _nonLocals = null; - _others = default; - } - private DiagnosticAnalysisResult( ProjectId projectId, - VersionStamp version, ImmutableDictionary> syntaxLocals, ImmutableDictionary> semanticLocals, ImmutableDictionary> nonLocals, ImmutableArray others, - ImmutableHashSet? documentIds, - bool fromBuild) + ImmutableHashSet? documentIds) { Debug.Assert(!others.IsDefault); Debug.Assert(!syntaxLocals.Values.Any(item => item.IsDefault)); @@ -80,8 +62,6 @@ private DiagnosticAnalysisResult( Debug.Assert(!nonLocals.Values.Any(item => item.IsDefault)); ProjectId = projectId; - Version = version; - FromBuild = fromBuild; _syntaxLocals = syntaxLocals; _semanticLocals = semanticLocals; @@ -89,74 +69,21 @@ private DiagnosticAnalysisResult( _others = others; DocumentIds = documentIds ?? GetDocumentIds(syntaxLocals, semanticLocals, nonLocals); - IsEmpty = DocumentIds.IsEmpty && _others.IsEmpty; } - public static DiagnosticAnalysisResult CreateEmpty(ProjectId projectId, VersionStamp version) + public static DiagnosticAnalysisResult CreateEmpty(ProjectId projectId) { return new DiagnosticAnalysisResult( projectId, - version, documentIds: [], syntaxLocals: ImmutableDictionary>.Empty, semanticLocals: ImmutableDictionary>.Empty, nonLocals: ImmutableDictionary>.Empty, - others: [], - fromBuild: false); - } - - public static DiagnosticAnalysisResult CreateInitialResult(ProjectId projectId) - { - return new DiagnosticAnalysisResult( - projectId, - version: VersionStamp.Default, - documentIds: null, - isEmpty: true, - fromBuild: false); - } - - public static DiagnosticAnalysisResult CreateFromBuild(Project project, ImmutableArray diagnostics, IEnumerable initialDocuments) - { - // we can't distinguish locals and non locals from build diagnostics nor determine right snapshot version for the build. - // so we put everything in as semantic local with default version. this lets us to replace those to live diagnostics when needed easily. - var version = VersionStamp.Default; - - var documentIds = ImmutableHashSet.CreateBuilder(); - documentIds.AddRange(initialDocuments); - - var diagnosticsWithDocumentId = PooledDictionary>.GetInstance(); - var diagnosticsWithoutDocumentId = ArrayBuilder.GetInstance(); - - foreach (var data in diagnostics) - { - var documentId = data.DocumentId; - if (documentId != null) - { - documentIds.Add(documentId); - diagnosticsWithDocumentId.MultiAdd(documentId, data); - } - else - { - diagnosticsWithoutDocumentId.Add(data); - } - } - - var result = new DiagnosticAnalysisResult( - project.Id, - version, - documentIds: documentIds.ToImmutable(), - syntaxLocals: ImmutableDictionary>.Empty, - semanticLocals: diagnosticsWithDocumentId.ToImmutableMultiDictionaryAndFree(), - nonLocals: ImmutableDictionary>.Empty, - others: diagnosticsWithoutDocumentId.ToImmutableAndFree(), - fromBuild: true); - - return result; + others: []); } public static DiagnosticAnalysisResult Create( Project project, - VersionStamp version, ImmutableDictionary> syntaxLocalMap, ImmutableDictionary> semanticLocalMap, ImmutableDictionary> nonLocalMap, @@ -169,20 +96,17 @@ public static DiagnosticAnalysisResult Create( return new DiagnosticAnalysisResult( project.Id, - version, syntaxLocalMap, semanticLocalMap, nonLocalMap, others, - documentIds, - fromBuild: false); + documentIds); } public static DiagnosticAnalysisResult CreateFromBuilder(DiagnosticAnalysisResultBuilder builder) { return Create( builder.Project, - builder.Version, builder.SyntaxLocals, builder.SemanticLocals, builder.NonLocals, @@ -190,15 +114,6 @@ public static DiagnosticAnalysisResult CreateFromBuilder(DiagnosticAnalysisResul builder.DocumentIds); } - // aggregated form means it has aggregated information but no actual data. - public bool IsAggregatedForm => _syntaxLocals == null; - - // default analysis result - public bool IsDefault => DocumentIds == null; - - // make sure we don't return null - public ImmutableHashSet DocumentIdsOrEmpty => DocumentIds ?? []; - private ImmutableDictionary>? GetMap(AnalysisKind kind) => kind switch { @@ -210,41 +125,24 @@ public static DiagnosticAnalysisResult CreateFromBuilder(DiagnosticAnalysisResul public ImmutableArray GetAllDiagnostics() { - // PERF: don't allocation anything if not needed - if (IsAggregatedForm || IsEmpty) - { - return []; - } + using var result = TemporaryArray.Empty; - Contract.ThrowIfNull(_syntaxLocals); - Contract.ThrowIfNull(_semanticLocals); - Contract.ThrowIfNull(_nonLocals); - Contract.ThrowIfTrue(_others.IsDefault); + foreach (var (_, data) in _syntaxLocals) + result.AddRange(data); - using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var (_, data) in _semanticLocals) + result.AddRange(data); - foreach (var data in _syntaxLocals.Values) - builder.AddRange(data); + foreach (var (_, data) in _nonLocals) + result.AddRange(data); - foreach (var data in _semanticLocals.Values) - builder.AddRange(data); + result.AddRange(_others); - foreach (var data in _nonLocals.Values) - builder.AddRange(data); - - foreach (var data in _others) - builder.AddRange(data); - - return builder.ToImmutableAndClear(); + return result.ToImmutableAndClear(); } public ImmutableArray GetDocumentDiagnostics(DocumentId documentId, AnalysisKind kind) { - if (IsAggregatedForm || IsEmpty) - { - return []; - } - var map = GetMap(kind); Contract.ThrowIfNull(map); @@ -258,35 +156,24 @@ public ImmutableArray GetDocumentDiagnostics(DocumentId document } public ImmutableArray GetOtherDiagnostics() - => (IsAggregatedForm || IsEmpty) ? [] : _others; - - public DiagnosticAnalysisResult ToAggregatedForm() - => new(ProjectId, Version, DocumentIds, IsEmpty, FromBuild); - - public DiagnosticAnalysisResult UpdateAggregatedResult(VersionStamp version, DocumentId documentId, bool fromBuild) - => new(ProjectId, version, DocumentIdsOrEmpty.Add(documentId), isEmpty: false, fromBuild: fromBuild); - - public DiagnosticAnalysisResult Reset() - => new(ProjectId, VersionStamp.Default, DocumentIds, IsEmpty, FromBuild); + => _others; public DiagnosticAnalysisResult DropExceptSyntax() { // quick bail out if (_syntaxLocals == null || _syntaxLocals.Count == 0) { - return CreateEmpty(ProjectId, Version); + return CreateEmpty(ProjectId); } // keep only syntax errors return new DiagnosticAnalysisResult( ProjectId, - Version, _syntaxLocals, semanticLocals: ImmutableDictionary>.Empty, nonLocals: ImmutableDictionary>.Empty, others: [], - documentIds: null, - fromBuild: false); + documentIds: null); } private static ImmutableHashSet GetDocumentIds( diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs index c2ec484358ccb..f4b4f6ec2b9bf 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs @@ -16,10 +16,9 @@ namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics; /// We have this builder to avoid creating collections unnecessarily. /// Expectation is that, most of time, most of analyzers doesn't have any diagnostics. so no need to actually create any objects. /// -internal struct DiagnosticAnalysisResultBuilder(Project project, VersionStamp version) +internal struct DiagnosticAnalysisResultBuilder(Project project) { public readonly Project Project = project; - public readonly VersionStamp Version = version; private HashSet? _lazyDocumentsWithDiagnostics = null; diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index ae879b375a924..9457027a23a87 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -103,7 +103,6 @@ public static async Task additionalPragmaSuppressionDiagnostics, DocumentAnalysisScope? documentAnalysisScope, Project project, - VersionStamp version, ImmutableArray projectAnalyzers, ImmutableArray hostAnalyzers, SkippedHostAnalyzersInfo skippedAnalyzersInfo, @@ -133,7 +132,7 @@ public static async Task> CreateDia /// Create identity and s map for given that /// has only project analyzers /// - public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(Project project) + public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(ProjectState project) => CreateProjectDiagnosticAnalyzersPerReference(project.AnalyzerReferences, project.Language); public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(IReadOnlyList projectAnalyzerReferences, string language) @@ -296,7 +296,7 @@ private static ImmutableDictionary> M return current; } - public SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(Project project, DiagnosticAnalyzerInfoCache infoCache) + public SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(ProjectState project, DiagnosticAnalyzerInfoCache infoCache) { var box = _skippedHostAnalyzers.GetOrCreateValue(project.AnalyzerReferences); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index 79fc5f5b76625..cb4122d233d18 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree; diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 7ddf1ce681c17..4196dfa0a75af 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -146,6 +146,7 @@ + @@ -164,5 +165,7 @@ + + diff --git a/src/Workspaces/Core/Portable/Notification/AbstractGlobalOperationNotificationService.cs b/src/Workspaces/Core/Portable/Notification/AbstractGlobalOperationNotificationService.cs index 1256b2fa12588..c8a88b0ac843d 100644 --- a/src/Workspaces/Core/Portable/Notification/AbstractGlobalOperationNotificationService.cs +++ b/src/Workspaces/Core/Portable/Notification/AbstractGlobalOperationNotificationService.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Notification; diff --git a/src/Workspaces/Core/Portable/SemanticModelReuse/AbstractSemanticModelReuseLanguageService.cs b/src/Workspaces/Core/Portable/SemanticModelReuse/AbstractSemanticModelReuseLanguageService.cs index 0d4712b4ce423..58b31221b2e31 100644 --- a/src/Workspaces/Core/Portable/SemanticModelReuse/AbstractSemanticModelReuseLanguageService.cs +++ b/src/Workspaces/Core/Portable/SemanticModelReuse/AbstractSemanticModelReuseLanguageService.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.SemanticModelReuse; @@ -106,26 +107,58 @@ protected SyntaxNode GetPreviousBodyNode(SyntaxNode previousRoot, SyntaxNode cur } else { - using var pooledCurrentMembers = this.SyntaxFacts.GetMethodLevelMembers(currentRoot); - var currentMembers = pooledCurrentMembers.Object; + // Walk up the ancestor nodes of currentBodyNode, finding child indexes up to the root. + using var _ = ArrayBuilder.GetInstance(out var indexPath); + GetNodeChildIndexPathToRootReversed(currentBodyNode, indexPath); - var index = currentMembers.IndexOf(currentBodyNode); - if (index < 0) + // Then use those indexes to walk back down the previous tree to find the equivalent node. + var previousNode = previousRoot; + for (var i = indexPath.Count - 1; i >= 0; i--) { - Debug.Fail($"Unhandled member type in {nameof(GetPreviousBodyNode)}"); - return null; + var childIndex = indexPath[i]; + var children = previousNode.ChildNodesAndTokens(); + + if (children.Count <= childIndex) + { + Debug.Fail("Member count shouldn't have changed as there were no top level edits."); + return null; + } + + var childAsNode = children[childIndex].AsNode(); + if (childAsNode is null) + { + Debug.Fail("Child at indicated index should be a node as there were no top level edits."); + return null; + } + + previousNode = childAsNode; } - using var pooledPreviousMembers = this.SyntaxFacts.GetMethodLevelMembers(previousRoot); - var previousMembers = pooledPreviousMembers.Object; + return previousNode; + } + } + + private static void GetNodeChildIndexPathToRootReversed(SyntaxNode node, ArrayBuilder path) + { + var current = node; + var parent = current.Parent; - if (currentMembers.Count != previousMembers.Count) + while (parent != null) + { + var childIndex = 0; + foreach (var child in parent.ChildNodesAndTokens()) { - Debug.Fail("Member count shouldn't have changed as there were no top level edits."); - return null; + if (child.AsNode() == current) + { + path.Add(childIndex); + break; + } + + childIndex++; } - return previousMembers[index]; + current = parent; + parent = current.Parent; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 7bb2618b19b0a..9c1adb44c8940 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.Serialization; +using static Microsoft.CodeAnalysis.Serialization.SerializerService.TestAccessor; using static TemporaryStorageService; internal partial class SerializerService @@ -29,18 +30,18 @@ internal partial class SerializerService /// pretend that a is a during tests. /// private static readonly object s_analyzerImageReferenceMapGate = new(); - private static IBidirectionalMap s_analyzerImageReferenceMap = BidirectionalMap.Empty; + private static IBidirectionalMap s_analyzerReferenceMap = BidirectionalMap.Empty; private static bool TryGetAnalyzerImageReferenceGuid(AnalyzerImageReference imageReference, out Guid guid) { lock (s_analyzerImageReferenceMapGate) - return s_analyzerImageReferenceMap.TryGetValue(imageReference, out guid); + return s_analyzerReferenceMap.TryGetValue(imageReference, out guid); } - private static bool TryGetAnalyzerImageReferenceFromGuid(Guid guid, [NotNullWhen(true)] out AnalyzerImageReference? imageReference) + private static bool TryGetAnalyzerImageReferenceFromGuid(Guid guid, [NotNullWhen(true)] out AnalyzerReference? analyzerReference) { lock (s_analyzerImageReferenceMapGate) - return s_analyzerImageReferenceMap.TryGetKey(guid, out imageReference); + return s_analyzerReferenceMap.TryGetKey(guid, out analyzerReference); } private static Checksum CreateChecksum(MetadataReference reference) @@ -78,6 +79,12 @@ protected virtual Checksum CreateChecksum(AnalyzerReference reference) writer.WriteGuid(guid); break; + case IAnalyzerReferenceWithGuid analyzerReferenceWithGuid: + lock (s_analyzerImageReferenceMapGate) + s_analyzerReferenceMap = s_analyzerReferenceMap.Add(reference, analyzerReferenceWithGuid.Guid); + writer.WriteGuid(analyzerReferenceWithGuid.Guid); + break; + default: throw ExceptionUtilities.UnexpectedValue(reference); } @@ -482,12 +489,6 @@ private static unsafe void WriteTo(MetadataReader reader, ObjectWriter writer) writer.WriteSpan(new ReadOnlySpan(reader.MetadataPointer, reader.MetadataLength)); } - private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference reference, ObjectWriter writer) - { - writer.WriteString(nameof(UnresolvedAnalyzerReference)); - writer.WriteString(reference.FullPath); - } - private static Metadata? TryGetMetadata(PortableExecutableReference reference) { try @@ -541,9 +542,23 @@ public static void AddAnalyzerImageReference(AnalyzerImageReference analyzerImag { lock (s_analyzerImageReferenceMapGate) { - if (!s_analyzerImageReferenceMap.ContainsKey(analyzerImageReference)) - s_analyzerImageReferenceMap = s_analyzerImageReferenceMap.Add(analyzerImageReference, Guid.NewGuid()); + if (!s_analyzerReferenceMap.ContainsKey(analyzerImageReference)) + s_analyzerReferenceMap = s_analyzerReferenceMap.Add(analyzerImageReference, Guid.NewGuid()); } } + + public static void AddAnalyzerImageReferences(IReadOnlyList analyzerReferences) + { + foreach (var analyzer in analyzerReferences) + { + if (analyzer is AnalyzerImageReference analyzerImageReference) + AddAnalyzerImageReference(analyzerImageReference); + } + } + + public interface IAnalyzerReferenceWithGuid + { + Guid Guid { get; } + } } } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs index 5db50f3c9a6a9..61a50ca6b3f81 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs @@ -4,8 +4,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.TestHooks; diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs index 9ea33b38eff0f..d39132afee723 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.SQLite.Interop; using Microsoft.CodeAnalysis.SQLite.v2.Interop; using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SQLite.v2; diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs index a796a791df4ef..1021f3f6e872e 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SQLite.v2; diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index adf3210139dbd..259db7cc54e15 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; diff --git a/src/Workspaces/Core/Portable/Workspace/IsolatedAnalyzerFileReference.cs b/src/Workspaces/Core/Portable/Workspace/IsolatedAnalyzerFileReference.cs index 6db69d9a65935..614c890088fa6 100644 --- a/src/Workspaces/Core/Portable/Workspace/IsolatedAnalyzerFileReference.cs +++ b/src/Workspaces/Core/Portable/Workspace/IsolatedAnalyzerFileReference.cs @@ -7,9 +7,9 @@ using System; using System.Collections.Immutable; using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis.Diagnostics; using System.Runtime.Loader; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Threading; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/FileWatchedPortableExecutableReferenceFactory.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/FileWatchedPortableExecutableReferenceFactory.cs index 66880110e594b..f68e3fa6861e7 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/FileWatchedPortableExecutableReferenceFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/FileWatchedPortableExecutableReferenceFactory.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ProjectSystem; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IAnalyzerAssemblyRedirector.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IAnalyzerAssemblyRedirector.cs new file mode 100644 index 0000000000000..c1881e5ccc073 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IAnalyzerAssemblyRedirector.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting; + +/// +/// Any MEF component implementing this interface will be used to redirect analyzer assemblies. +/// +/// +/// The redirected path is passed to the compiler where it is processed in the standard way, +/// e.g., the redirected assembly is shadow copied before it's loaded +/// (this could be improved in the future since shadow copying redirected assemblies is usually unnecessary). +/// +internal interface IAnalyzerAssemblyRedirector +{ + /// + /// Original full path of the analyzer assembly. + /// + /// + /// The redirected full path of the analyzer assembly + /// or if this instance cannot redirect the given assembly. + /// + /// + /// + /// If two redirectors return different paths for the same assembly, no redirection will be performed. + /// + /// + /// No thread switching inside this method is allowed. + /// + /// + string? RedirectPath(string fullPath); +} diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index aa75a5cd9f367..fdf1663a2a0b5 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; @@ -22,6 +23,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Workspaces.ProjectSystem.ProjectSystemProjectFactory; @@ -1126,9 +1128,43 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) return GetMappedRazorSourceGenerator(fullPath); } + if (TryRedirectAnalyzerAssembly(fullPath) is { } redirectedPath) + { + return OneOrMany.Create(redirectedPath); + } + return OneOrMany.Create(fullPath); } + private string? TryRedirectAnalyzerAssembly(string fullPath) + { + string? redirectedPath = null; + + foreach (var redirector in _hostInfo.AnalyzerAssemblyRedirectors) + { + try + { + if (redirector.RedirectPath(fullPath) is { } currentlyRedirectedPath) + { + if (redirectedPath == null) + { + redirectedPath = currentlyRedirectedPath; + } + else if (redirectedPath != currentlyRedirectedPath) + { + throw new InvalidOperationException($"Multiple redirectors disagree on the path to redirect '{fullPath}' to ('{redirectedPath}' vs '{currentlyRedirectedPath}')."); + } + } + } + catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.General)) + { + // Ignore if the external redirector throws. + } + } + + return redirectedPath; + } + private static readonly string s_csharpCodeStyleAnalyzerSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk", "codestyle", "cs"); private static readonly string s_visualBasicCodeStyleAnalyzerSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk", "codestyle", "vb"); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs index 6b47e6cfdfd01..e31420944a198 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs @@ -603,11 +603,12 @@ private static bool CanConvertMetadataReferenceToProjectReference(Solution solut Contract.ThrowIfNull(projectWithMetadataReference); Contract.ThrowIfNull(referencedProject); - // We don't want to convert a metadata reference to a project reference if the project being referenced isn't something - // we can create a Compilation for. For example, if we have a C# project, and it's referencing a F# project via a metadata reference - // everything would be fine if we left it a metadata reference. Converting it to a project reference means we couldn't create a Compilation - // anymore in the IDE, since the C# compilation would need to reference an F# compilation. F# projects referencing other F# projects though - // do expect this to work, and so we'll always allow references through of the same language. + // We don't want to convert a metadata reference to a project reference if the project being referenced isn't + // something we can create a Compilation for. For example, if we have a C# project, and it's referencing a F# + // project via a metadata reference everything would be fine if we left it a metadata reference. Converting it + // to a project reference means we couldn't create a Compilation anymore in the IDE, since the C# compilation + // would need to reference an F# compilation. F# projects referencing other F# projects though do expect this to + // work, and so we'll always allow references through of the same language. if (projectWithMetadataReference.Language != referencedProject.Language) { if (projectWithMetadataReference.LanguageServices.GetService() != null && @@ -618,6 +619,12 @@ private static bool CanConvertMetadataReferenceToProjectReference(Solution solut } } + // Getting a metadata reference from a 'module' is not supported from the compilation layer. Nor is emitting a + // 'metadata-only' stream for it (a 'skeleton' reference). So converting a NetModule reference to a project + // reference won't actually help us out. Best to keep this as a plain metadata reference. + if (referencedProject.CompilationOptions?.OutputKind == OutputKind.NetModule) + return false; + // If this is going to cause a circular reference, also disallow it if (solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(referencedProjectId).Contains(projectIdWithMetadataReference)) { diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs index fc3f0a58ef18a..5bb5f96513091 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs @@ -6,9 +6,11 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting; namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; internal record ProjectSystemHostInfo( ImmutableArray> DynamicFileInfoProviders, - IHostDiagnosticAnalyzerProvider HostDiagnosticAnalyzerProvider); + IHostDiagnosticAnalyzerProvider HostDiagnosticAnalyzerProvider, + ImmutableArray AnalyzerAssemblyRedirectors); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index c76d53fcca168..8c2c27c46d89f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -568,6 +568,8 @@ public Task GetSemanticVersionAsync(CancellationToken cancellation /// changes as the project is removed, then added resulting in a version change. /// /// + /// This checksum is also affected by the for this project. + /// As such, it is not usable across different sessions of a particular host. /// internal Task GetDependentChecksumAsync(CancellationToken cancellationToken) => Solution.CompilationState.GetDependentChecksumAsync(this.Id, cancellationToken); @@ -830,9 +832,6 @@ internal StructuredAnalyzerConfigOptions GetFallbackAnalyzerOptions() private string GetDebuggerDisplay() => this.Name; - internal SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(DiagnosticAnalyzerInfoCache infoCache) - => Solution.SolutionState.Analyzers.GetSkippedAnalyzersInfo(this, infoCache); - internal async ValueTask GetDocumentAsync(ImmutableArray contentHash, CancellationToken cancellationToken) { var documentId = await State.GetDocumentIdAsync(contentHash, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs index f63b4425e06ab..e2db3822075b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -1095,40 +1096,50 @@ public Task GetDependentChecksumAsync( Interlocked.CompareExchange( ref _lazyDependentChecksum, AsyncLazy.Create(static (arg, c) => - arg.self.ComputeDependentChecksumAsync(arg.SolutionState, c), - arg: (self: this, compilationState.SolutionState)), + arg.self.ComputeDependentChecksumAsync(arg.compilationState, c), + arg: (self: this, compilationState)), null); } return _lazyDependentChecksum.GetValueAsync(cancellationToken); } - private async Task ComputeDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task ComputeDependentChecksumAsync( + SolutionCompilationState solution, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var tempChecksumArray); + // Mix in the SG information for this project. That way if it changes, we will have a different + // checksum (since semantics could have changed because of this). + if (solution.SourceGeneratorExecutionVersionMap.Map.TryGetValue(this.ProjectState.Id, out var executionVersion)) + tempChecksumArray.Add(executionVersion.Checksum); + // Get the checksum for the project itself. var projectChecksum = await this.ProjectState.GetChecksumAsync(cancellationToken).ConfigureAwait(false); tempChecksumArray.Add(projectChecksum); - // Calculate a checksum this project and for each dependent project that could affect semantics for - // this project. Ensure that the checksum calculation orders the projects consistently so that we get - // the same checksum across sessions of VS. Note: we use the project filepath+name as a unique way - // to reference a project. This matches the logic in our persistence-service implemention as to how - // information is associated with a project. - var transitiveDependencies = solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(this.ProjectState.Id); + // Calculate a checksum this project and for each dependent project that could affect semantics for this + // project. We order the projects so that we are resilient to the underlying in-memory graph structure + // changing this arbitrarily. We do not want that to cause us to change our semantic version.. Note: we + // use the project filepath+name as a unique way to reference a project. This matches the logic in our + // persistence-service implementation as to how information is associated with a project. + var transitiveDependencies = solution.SolutionState.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(this.ProjectState.Id); var orderedProjectIds = transitiveDependencies.OrderBy(id => { - var depProject = solution.GetRequiredProjectState(id); + var depProject = solution.SolutionState.GetRequiredProjectState(id); return (depProject.FilePath, depProject.Name); }); foreach (var projectId in orderedProjectIds) { - var referencedProject = solution.GetRequiredProjectState(projectId); + // Mix in the SG information for the dependent project. That way if it changes, we will have a + // different checksum (since semantics could have changed because of this). + if (solution.SourceGeneratorExecutionVersionMap.Map.TryGetValue(projectId, out executionVersion)) + tempChecksumArray.Add(executionVersion.Checksum); // Note that these checksums should only actually be calculated once, if the project is unchanged // the same checksum will be returned. + var referencedProject = solution.SolutionState.GetRequiredProjectState(projectId); var referencedProjectChecksum = await referencedProject.GetChecksumAsync(cancellationToken).ConfigureAwait(false); tempChecksumArray.Add(referencedProjectChecksum); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs index 32d50e7f8e9fd..dfb473ea9a080 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 83eb6fae6fa13..42a42b6becaad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; @@ -1216,6 +1217,17 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( { try { + // Getting a metadata reference from a 'module' is not supported from the compilation layer. Nor is + // emitting a 'metadata-only' stream for it (a 'skeleton' reference). So we just bail here. Note: this is + // not a common user scenario (they have to be explicitly creating 'modules' which are a feature practically + // never used anymore). So it's not worthwhile trying to merge the referenced module into the current + // compilation in any fashion. + // + // Note: ProjectSystemProjectFactory.CanConvertMetadataReferenceToProjectReference attempts to prevent this + // scenario from arising. However, this code just acts as a final failsafe to ensure we don't crash. + if (tracker.ProjectState.CompilationOptions?.OutputKind == OutputKind.NetModule) + return null; + // If same language then we can wrap the other project's compilation into a compilation reference if (tracker.ProjectState.LanguageServices == fromProject.LanguageServices) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs index db17349aa041e..a8eabc3e626c2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs @@ -46,6 +46,8 @@ public static SourceGeneratorExecutionVersion ReadFrom(ObjectReader reader) public override string ToString() => $"{MajorVersion}.{MinorVersion}"; + + public Checksum Checksum => new(MajorVersion, MinorVersion); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs index f8ded8d24ae96..6997357e85bf7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 2e71d615b880c..3c7117483f2af 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs index a54a64ccfc8bf..5811a90a8cfb8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs @@ -19,6 +19,7 @@ public abstract partial class Workspace private readonly EventMap _eventMap = new(); private const string WorkspaceChangeEventName = "WorkspaceChanged"; + private const string WorkspaceChangedImmediateEventName = "WorkspaceChangedImmediate"; private const string WorkspaceFailedEventName = "WorkspaceFailed"; private const string DocumentOpenedEventName = "DocumentOpened"; private const string DocumentClosedEventName = "DocumentClosed"; @@ -42,6 +43,24 @@ public event EventHandler WorkspaceChanged } } + /// + /// An event raised *immediately* whenever the current solution is changed. Handlers + /// should be written to be very fast. Called on the same thread changing the workspace, + /// which may vary depending on the workspace. + /// + internal event EventHandler WorkspaceChangedImmediate + { + add + { + _eventMap.AddEventHandler(WorkspaceChangedImmediateEventName, value); + } + + remove + { + _eventMap.RemoveEventHandler(WorkspaceChangedImmediateEventName, value); + } + } + protected Task RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind kind, Solution oldSolution, Solution newSolution, ProjectId projectId = null, DocumentId documentId = null) { if (newSolution == null) @@ -59,22 +78,34 @@ protected Task RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind kind, Solutio projectId = documentId.ProjectId; } - var ev = GetEventHandlers(WorkspaceChangeEventName); + var args = new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId); + + var ev = GetEventHandlers(WorkspaceChangedImmediateEventName); + RaiseEventForHandlers(ev, args, FunctionId.Workspace_EventsImmediate); + + ev = GetEventHandlers(WorkspaceChangeEventName); if (ev.HasHandlers) { return this.ScheduleTask(() => { - using (Logger.LogBlock(FunctionId.Workspace_Events, (s, p, d, k) => $"{s.Id} - {p} - {d} {kind.ToString()}", newSolution, projectId, documentId, kind, CancellationToken.None)) - { - var args = new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId); - ev.RaiseEvent(static (handler, arg) => handler(arg.self, arg.args), (self: this, args)); - } + RaiseEventForHandlers(ev, args, FunctionId.Workspace_Events); }, WorkspaceChangeEventName); } else { return Task.CompletedTask; } + + static void RaiseEventForHandlers( + EventMap.EventHandlerSet> handlers, + WorkspaceChangeEventArgs args, + FunctionId functionId) + { + using (Logger.LogBlock(functionId, (s, p, d, k) => $"{s.Id} - {p} - {d} {args.Kind.ToString()}", args.NewSolution, args.ProjectId, args.DocumentId, args.Kind, CancellationToken.None)) + { + handlers.RaiseEvent(static (handler, args) => handler(args.NewSolution.Workspace, args), args); + } + } } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs index 2de8d674b6024..e4bdc6b38c9a0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 73f7d29d9fe0c..fdb847ccc5e62 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2820,7 +2820,10 @@ public async Task TestCrossLanguageProjectsAsync() using var workspace = CreateWorkspace(); var solution = workspace.CurrentSolution - .AddProject(pm1, "goo", "goo.dll", LanguageNames.CSharp) + .AddProject(ProjectInfo.Create(pm1, VersionStamp.Create(), "goo", "goo.dll", LanguageNames.CSharp, compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))) .AddMetadataReference(pm1, s_mscorlib) .AddProject(pm2, "bar", "bar.dll", LanguageNames.VisualBasic) .AddMetadataReference(pm2, s_mscorlib) @@ -3968,6 +3971,13 @@ public class C : A { .AddProjectReference(pid3, new ProjectReference(pid1)) .AddProjectReference(pid3, new ProjectReference(pid2)); + var options = solution.Workspace.Services + .GetLanguageService(LanguageNames.VisualBasic) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary); + solution = solution.WithProjectCompilationOptions(pid1, options) + .WithProjectCompilationOptions(pid2, options); + var project3 = solution.GetProject(pid3); var comp3 = project3.GetCompilationAsync().Result; var classC = comp3.GetTypeByMetadataName("C"); @@ -4020,7 +4030,11 @@ public async Task TestProjectWithNoBrokenReferencesHasNoIncompleteReferences() "CSharpProject", "CSharpProject", LanguageNames.CSharp, - metadataReferences: [MscorlibRef])); + metadataReferences: [MscorlibRef], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))); var project2 = workspace.AddProject( ProjectInfo.Create( ProjectId.CreateNewId(), @@ -4221,7 +4235,11 @@ public async Task TestFrozenPartialProjectAlwaysIsIncomplete() "CSharpProject", "CSharpProject", LanguageNames.CSharp, - metadataReferences: [MscorlibRef])); + metadataReferences: [MscorlibRef], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))); var project2 = workspace.AddProject( ProjectInfo.Create( @@ -5326,7 +5344,12 @@ private static void GetMultipleProjects( "CSharpProject", "CSharpProject", LanguageNames.CSharp, - metadataReferences: [MscorlibRef]).WithHasAllInformation(hasAllInformation: false)); + metadataReferences: [MscorlibRef], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)) + .WithHasAllInformation(hasAllInformation: false)); vbNormalProject = workspace.AddProject( ProjectInfo.Create( @@ -5335,7 +5358,11 @@ private static void GetMultipleProjects( "VisualBasicProject", "VisualBasicProject", LanguageNames.VisualBasic, - metadataReferences: [MscorlibRef])); + metadataReferences: [MscorlibRef], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.VisualBasic) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))); dependsOnBrokenProject = workspace.AddProject( ProjectInfo.Create( @@ -5345,7 +5372,11 @@ private static void GetMultipleProjects( "VisualBasicProject", LanguageNames.VisualBasic, metadataReferences: [MscorlibRef], - projectReferences: [new ProjectReference(csBrokenProject.Id), new ProjectReference(vbNormalProject.Id)])); + projectReferences: [new ProjectReference(csBrokenProject.Id), new ProjectReference(vbNormalProject.Id)], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.VisualBasic) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))); dependsOnVbNormalProject = workspace.AddProject( ProjectInfo.Create( @@ -5355,7 +5386,11 @@ private static void GetMultipleProjects( "CSharpProject", LanguageNames.CSharp, metadataReferences: [MscorlibRef], - projectReferences: [new ProjectReference(vbNormalProject.Id)])); + projectReferences: [new ProjectReference(vbNormalProject.Id)], + compilationOptions: workspace.Services + .GetLanguageService(LanguageNames.CSharp) + .GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))); transitivelyDependsOnBrokenProjects = workspace.AddProject( ProjectInfo.Create( diff --git a/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs b/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs index ff97ddfac212b..417597ee5f63c 100644 --- a/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs @@ -5,8 +5,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; diff --git a/src/Workspaces/CoreTest/UtilityTest/CancellationSeriesTests.cs b/src/Workspaces/CoreTest/UtilityTest/CancellationSeriesTests.cs index b34fddf770451..2634f592348ee 100644 --- a/src/Workspaces/CoreTest/UtilityTest/CancellationSeriesTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/CancellationSeriesTests.cs @@ -11,6 +11,7 @@ using System; using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis.Threading; using Xunit; namespace Roslyn.Utilities diff --git a/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs b/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs new file mode 100644 index 0000000000000..681622f567569 --- /dev/null +++ b/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.UnitTests; + +public sealed class TestOutputLoggerProvider(ITestOutputHelper testOutputHelper) : ILoggerProvider +{ + public ILogger CreateLogger(string categoryName) + { + return new TestOutputLogger(testOutputHelper, categoryName); + } + + private sealed class TestOutputLogger(ITestOutputHelper testOutputHelper, string categoryName) : ILogger + { + public IDisposable BeginScope(TState state) where TState : notnull + { + return new NoOpDisposable(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + testOutputHelper.WriteLine($"[{DateTime.UtcNow:hh:mm:ss.fff}] [{logLevel}] [{categoryName}] {formatter(state, exception)}"); + + if (exception is not null) + testOutputHelper.WriteLine(exception.ToString()); + } + + private sealed class NoOpDisposable : IDisposable + { + public void Dispose() + { + } + } + } + + public void Dispose() + { + } +} diff --git a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj index 633d78b7eb8f1..cfc6c47a176a1 100644 --- a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj @@ -35,9 +35,6 @@ - - - diff --git a/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs b/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs index c8a762042ae32..5371d55acc443 100644 --- a/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs +++ b/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { - internal class TestAnalyzerReferenceByLanguage : AnalyzerReference + internal class TestAnalyzerReferenceByLanguage : + AnalyzerReference, + SerializerService.TestAccessor.IAnalyzerReferenceWithGuid { private readonly IReadOnlyDictionary> _analyzersMap; @@ -28,6 +31,7 @@ public TestAnalyzerReferenceByLanguage(IReadOnlyDictionary nameof(TestAnalyzerReferenceByLanguage); public override object Id => Display; + public Guid Guid { get; } = Guid.NewGuid(); public Checksum Checksum; diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs similarity index 95% rename from src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs rename to src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs index ec01c8c69affc..529f126e62ef1 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs @@ -7,7 +7,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.Editor.Test; +namespace Microsoft.CodeAnalysis.Test.Utilities; [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] [method: ImportingConstructor] diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index bac4fc5437153..40533be8db84b 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -774,15 +774,9 @@ public override bool TryApplyChanges(Solution newSolution) // Ensure that any in-memory analyzer references in this test workspace are known by the serializer service // so that we can validate OOP scenarios involving analyzers. - foreach (var analyzer in this.CurrentSolution.AnalyzerReferences) - { - if (analyzer is AnalyzerImageReference analyzerImageReference) - { #pragma warning disable CA1416 // Validate platform compatibility - SerializerService.TestAccessor.AddAnalyzerImageReference(analyzerImageReference); + SerializerService.TestAccessor.AddAnalyzerImageReferences(this.CurrentSolution.AnalyzerReferences); #pragma warning restore CA1416 // Validate platform compatibility - } - } return result; } diff --git a/src/Workspaces/MSBuild/BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj b/src/Workspaces/MSBuild/BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj index 8101f56b8be6c..9b1558f7d9866 100644 --- a/src/Workspaces/MSBuild/BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj +++ b/src/Workspaces/MSBuild/BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj @@ -67,15 +67,12 @@ - - - @@ -112,5 +109,6 @@ - + + diff --git a/src/Workspaces/MSBuild/BuildHost/Program.cs b/src/Workspaces/MSBuild/BuildHost/Program.cs index b3a286b67419d..6f12720481727 100644 --- a/src/Workspaces/MSBuild/BuildHost/Program.cs +++ b/src/Workspaces/MSBuild/BuildHost/Program.cs @@ -16,11 +16,11 @@ internal static class Program { internal static async Task Main(string[] args) { - var pipeOption = new Option("--pipe") { Required = true }; - var propertyOption = new Option("--property") { Arity = ArgumentArity.ZeroOrMore }; - var binaryLogOption = new Option("--binlog") { Required = false }; - var localeOption = new Option("--locale") { Required = true }; - var command = new RootCommand { pipeOption, binaryLogOption, propertyOption, localeOption }; + var pipeOption = new CliOption("--pipe") { Required = true }; + var propertyOption = new CliOption("--property") { Arity = ArgumentArity.ZeroOrMore }; + var binaryLogOption = new CliOption("--binlog") { Required = false }; + var localeOption = new CliOption("--locale") { Required = true }; + var command = new CliRootCommand { pipeOption, binaryLogOption, propertyOption, localeOption }; var parsedArguments = command.Parse(args); var pipeName = parsedArguments.GetValue(pipeOption)!; var properties = parsedArguments.GetValue(propertyOption)!; @@ -52,7 +52,7 @@ internal static async Task Main(string[] args) var pipeServer = NamedPipeUtil.CreateServer(pipeName, PipeDirection.InOut); await pipeServer.WaitForConnectionAsync().ConfigureAwait(false); - var server = new RpcServer(sendingStream: pipeServer, receivingStream: pipeServer); + var server = new RpcServer(pipeServer); var targetObject = server.AddTarget(new BuildHost(logger, propertiesBuilder.ToImmutable(), binaryLogPath, server)); Contract.ThrowIfFalse(targetObject == 0, "The first object registered should have target 0, which is assumed by the client."); diff --git a/src/Workspaces/MSBuild/BuildHost/Rpc/RpcServer.cs b/src/Workspaces/MSBuild/BuildHost/Rpc/RpcServer.cs index a6a08b8ed3050..e161bc6fa0c2a 100644 --- a/src/Workspaces/MSBuild/BuildHost/Rpc/RpcServer.cs +++ b/src/Workspaces/MSBuild/BuildHost/Rpc/RpcServer.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.IO.Pipes; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -25,19 +26,19 @@ namespace Microsoft.CodeAnalysis.MSBuild; /// internal sealed class RpcServer { - private readonly TextWriter _sendingStream; + private readonly TextWriter _streamWriter; private readonly SemaphoreSlim _sendingStreamSemaphore = new SemaphoreSlim(initialCount: 1); - private readonly TextReader _receivingStream; + private readonly TextReader _streamReader; private readonly ConcurrentDictionary _rpcTargets = []; private volatile int _nextRpcTargetIndex = -1; // We'll start at -1 so the first value becomes zero private readonly CancellationTokenSource _shutdownTokenSource = new CancellationTokenSource(); - public RpcServer(Stream sendingStream, Stream receivingStream) + public RpcServer(PipeStream stream) { - _sendingStream = new StreamWriter(sendingStream, JsonSettings.StreamEncoding); - _receivingStream = new StreamReader(receivingStream, JsonSettings.StreamEncoding); + _streamWriter = new StreamWriter(stream, JsonSettings.StreamEncoding); + _streamReader = new StreamReader(stream, JsonSettings.StreamEncoding); } public int AddTarget(object rpcTarget) @@ -61,7 +62,7 @@ public async Task RunAsync() var runningTasks = new ConcurrentSet(); string? line; - while ((line = await _receivingStream.TryReadLineOrReturnNullIfCancelledAsync(_shutdownTokenSource.Token).ConfigureAwait(false)) != null) + while ((line = await _streamReader.TryReadLineOrReturnNullIfCancelledAsync(_shutdownTokenSource.Token).ConfigureAwait(false)) != null) { Request? request; @@ -173,8 +174,8 @@ private async Task ProcessRequestAsync(Request request) #endif using (await _sendingStreamSemaphore.DisposableWaitAsync().ConfigureAwait(false)) { - await _sendingStream.WriteLineAsync(responseJson).ConfigureAwait(false); - await _sendingStream.FlushAsync().ConfigureAwait(false); + await _streamWriter.WriteLineAsync(responseJson).ConfigureAwait(false); + await _streamWriter.FlushAsync().ConfigureAwait(false); } } diff --git a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs index 1592705b0962a..057a90fafd4bb 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs @@ -389,7 +389,7 @@ public BuildHostProcess(Process process, string pipeName, ILoggerFactory? logger throw new Exception("Ownership of BuildHost pipe is incorrect."); } - _rpcClient = new RpcClient(sendingStream: pipeClient, receivingStream: pipeClient); + _rpcClient = new RpcClient(pipeClient); _rpcClient.Start(); _rpcClient.Disconnected += Process_Exited; BuildHost = new RemoteBuildHost(_rpcClient); diff --git a/src/Workspaces/MSBuild/Core/MSBuild/DiagnosticReporterLoggerProvider.cs b/src/Workspaces/MSBuild/Core/MSBuild/DiagnosticReporterLoggerProvider.cs index 5f3ccc2bb87e6..fea58aff5d40b 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/DiagnosticReporterLoggerProvider.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/DiagnosticReporterLoggerProvider.cs @@ -11,7 +11,7 @@ internal class DiagnosticReporterLoggerProvider : ILoggerProvider { private readonly DiagnosticReporter _reporter; - private DiagnosticReporterLoggerProvider(DiagnosticReporter reporter) + public DiagnosticReporterLoggerProvider(DiagnosticReporter reporter) { _reporter = reporter; } @@ -25,14 +25,6 @@ public void Dispose() { } - public static ILoggerFactory CreateLoggerFactoryForDiagnosticReporter(DiagnosticReporter reporter) - { - // Note: it's important we set MinLevel here, or otherwise we'll still get called in Log() for things below LogLevel.Warning. - return new LoggerFactory( - [new DiagnosticReporterLoggerProvider(reporter)], - new LoggerFilterOptions() { MinLevel = LogLevel.Warning }); - } - private sealed class Logger(DiagnosticReporter reporter, string categoryName) : ILogger { public IDisposable? BeginScope(TState state) where TState : notnull @@ -47,6 +39,10 @@ public bool IsEnabled(LogLevel logLevel) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + // Despite returning IsEnabled somebody might still call us anyways, so filter it out + if (logLevel < LogLevel.Warning) + return; + var kind = logLevel == LogLevel.Warning ? WorkspaceDiagnosticKind.Warning : WorkspaceDiagnosticKind.Failure; var message = formatter(state, exception); if (!string.IsNullOrEmpty(categoryName)) diff --git a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs index 4487e1f70532f..50971a42caf27 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs @@ -61,7 +61,7 @@ public MSBuildProjectLoader(Workspace workspace, ImmutableDictionary properties, Ho return new MSBuildWorkspace(hostServices, properties.ToImmutableDictionary()); } + internal void AddLoggerProvider(Microsoft.Extensions.Logging.ILoggerProvider loggerProvider) => _loggerFactory.AddProvider(loggerProvider); + /// /// The MSBuild properties used when interpreting project files. /// These are the same properties that are passed to msbuild via the /property:<n>=<v> command line argument. diff --git a/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index 3b9134d8249c5..0b4d11ad2192c 100644 --- a/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/MSBuild/Core/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -28,7 +28,6 @@ -