diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml
index 7039575ec2659..5738ee47aae0c 100644
--- a/.github/policies/resourceManagement.yml
+++ b/.github/policies/resourceManagement.yml
@@ -277,34 +277,6 @@ configuration:
then:
- closeIssue
- - description: Add "untriaged" label on new issues
- triggerOnOwnActions: false
- if:
- - payloadType: Issues
- - not:
- hasLabel:
- label: untriaged
- - and:
- - isAction:
- action: Opened
- then:
- - addLabel:
- label: untriaged
-
- - description: Remove "untriaged" label from issues when closed or added to a milestone
- triggerOnOwnActions: false
- if:
- - payloadType: Issues
- - or:
- - isAction:
- action: Closed
- - isPartOfAnyMilestone
- - hasLabel:
- label: untriaged
- then:
- - removeLabel:
- label: untriaged
-
- description: Add breaking change doc instructions to issue
if:
- payloadType: Issues
diff --git a/NuGet.config b/NuGet.config
index 0472eeb256e12..703703fb400a9 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -9,8 +9,6 @@
-
-
diff --git a/Roslyn.sln b/Roslyn.sln
index 3e4776aca3d40..09b0cc37519be 100644
--- a/Roslyn.sln
+++ b/Roslyn.sln
@@ -727,6 +727,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeA
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.BuildClient.Package", "src\NuGet\Microsoft.CodeAnalysis.BuildClient.Package\Microsoft.CodeAnalysis.BuildClient.Package.csproj", "{5E4F7448-B00B-4F5B-859F-6ED0354253D5}"
EndProject
+Project("{9a19103f-16f7-4668-be54-9a1e7a4f7556}") = "Microsoft.CodeAnalysis.SemanticSearch.Extensions", "src\Tools\SemanticSearch\Extensions\Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj", "{66C8265C-2C79-F259-9807-3E97CA586F1E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1797,6 +1799,10 @@ Global
{5E4F7448-B00B-4F5B-859F-6ED0354253D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E4F7448-B00B-4F5B-859F-6ED0354253D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E4F7448-B00B-4F5B-859F-6ED0354253D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66C8265C-2C79-F259-9807-3E97CA586F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66C8265C-2C79-F259-9807-3E97CA586F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66C8265C-2C79-F259-9807-3E97CA586F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66C8265C-2C79-F259-9807-3E97CA586F1E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2137,6 +2143,7 @@ Global
{91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{5399BBCC-417F-C710-46DE-EB0C0074C34D} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{5E4F7448-B00B-4F5B-859F-6ED0354253D5} = {C52D8057-43AF-40E6-A01B-6CDBB7301985}
+ {66C8265C-2C79-F259-9807-3E97CA586F1E} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29}
diff --git a/azure-pipelines-pr-validation.yml b/azure-pipelines-pr-validation.yml
index 0afa702c1f38e..7e1eb38f8c8fd 100644
--- a/azure-pipelines-pr-validation.yml
+++ b/azure-pipelines-pr-validation.yml
@@ -153,12 +153,14 @@ extends:
displayName: Disable Real-time Monitoring
- task: Powershell@2
+ name: SetOriginalBuildNumber
displayName: Setting OriginalBuildNumber variable
condition: succeeded()
inputs:
targetType: inline
script: |
$originalBuildNumber = "$(Build.BuildNumber)".Split(' - ')[0]
+ Write-Host "##vso[task.setvariable variable=OutputOriginalBuildNumber;isoutput=true;isreadonly=true]$originalBuildNumber"
Write-Host "##vso[task.setvariable variable=OriginalBuildNumber;isreadonly=true]$originalBuildNumber"
- powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName;isreadonly=true]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))"
@@ -326,12 +328,20 @@ extends:
- job: insert
variables:
FancyBuildNumber: $[stageDependencies.build.PRValidationBuild.outputs['FancyBuild.BuildNumber']]
+ OriginalBuildNumber: $[stageDependencies.build.PRValidationBuild.outputs['SetOriginalBuildNumber.OutputOriginalBuildNumber']]
displayName: Insert to VS
pool:
name: VSEngSS-MicroBuild2022-1ES
steps:
- download: current
artifact: VSSetup
+
+ # RIT looks at the build number and cannot handle the fancy build version.
+ # While in normal scenarios this is already set to the original number, we reset it again here in case this is a re-run.
+ - powershell: Write-Host "##vso[build.updatebuildnumber]$(OriginalBuildNumber)"
+ displayName: Reset BuildNumber
+ condition: succeeded()
+
- powershell: |
$branchName = "$(Build.SourceBranch)".Substring("refs/heads/".Length)
Write-Host "##vso[task.setvariable variable=ComponentBranchName]$branchName"
diff --git a/docs/features/incremental-generators.md b/docs/features/incremental-generators.md
index 48dc1a9a3eac1..d25cdb969a2fb 100644
--- a/docs/features/incremental-generators.md
+++ b/docs/features/incremental-generators.md
@@ -1022,11 +1022,11 @@ The generator would run select1 on the first and second files, producing
select for the third file, as the input has not changed. It can just use the
previously cached value.
-AdditionalText | Select1 | Select2
------------------------------|----------------|-----------
-**Text{ Path: "diff.txt" }** | **"diff.txt"** |
-**Text{ Path: "def.txt" }** | **"def.txt"** |
-Text{ Path: "ghi.txt" } | "ghi.txt" |
+AdditionalText | Select1 | Select2
+-----------------------------|----------------------|-----------
+**Text{ Path: "diff.txt" }** | **"diff.txt"** (new) |
+**Text{ Path: "def.txt" }** | **"def.txt"** (new) |
+Text{ Path: "ghi.txt" } | "ghi.txt" (reuse) |
Next the driver would look to run Select2. It would operate on `"diff.txt"`
producing `"prefix_diff.txt"`, but when it comes to `"def.txt"` it can observe
@@ -1038,9 +1038,9 @@ it can just use the cached value from before. Similarly the cached state of
AdditionalText | Select1 | Select2
-----------------------------|----------------|----------------------
-**Text{ Path: "diff.txt" }** | **"diff.txt"** | **"prefix_diff.txt"**
-**Text{ Path: "def.txt" }** | **"def.txt"** | "prefix_def.txt"
-Text{ Path: "ghi.txt" } | "ghi.txt" | "prefix_ghi.txt"
+**Text{ Path: "diff.txt" }** | **"diff.txt"** | **"prefix_diff.txt"** (new)
+**Text{ Path: "def.txt" }** | **"def.txt"** | "prefix_def.txt" (reuse)
+Text{ Path: "ghi.txt" } | "ghi.txt" | "prefix_ghi.txt" (reuse)
In this way, only changes that are consequential flow through the pipeline, and
duplicate work is avoided. If a generator only relies on `AdditionalTexts` then
diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props
index 8b0c5a473de71..e8dece7fb2225 100644
--- a/eng/Directory.Packages.props
+++ b/eng/Directory.Packages.props
@@ -2,7 +2,6 @@
3.11.0-beta1.24081.1
- 8.0.0-preview.23468.1
1.1.3-beta1.24319.1
0.1.796-beta
<_BasicReferenceAssembliesVersion>1.8.3
@@ -53,13 +52,13 @@
-
+
-
+
@@ -78,6 +77,7 @@
+
@@ -117,7 +117,7 @@
-
+
@@ -252,7 +252,7 @@
-
+
diff --git a/eng/Signing.props b/eng/Signing.props
index f1d94e766f317..e7a63462562b6 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -61,4 +61,13 @@
+
+
+
+
+
+
diff --git a/eng/Version.Details.props b/eng/Version.Details.props
index aa1fc141196d4..5b5244a709636 100644
--- a/eng/Version.Details.props
+++ b/eng/Version.Details.props
@@ -1 +1,97 @@
-
+
+
+
+
+
+ 3.11.0
+ 4.10.0-1.24061.4
+
+ 2.0.0-rc.1.25406.102
+
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 8.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 8.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+ 9.0.0
+
+ 10.0.0-beta.25367.5
+ 10.0.0-beta.25367.5
+ 10.0.0-beta.25367.5
+
+ 2.0.0
+
+ 3.11.0
+ 3.3.0
+ 10.0.0-preview.25375.1
+
+
+
+
+ $(MicrosoftCodeAnalysisPackageVersion)
+ $(MicrosoftNetCompilersToolsetPackageVersion)
+
+ $(SystemCommandLinePackageVersion)
+
+ $(MicrosoftBclAsyncInterfacesPackageVersion)
+ $(MicrosoftExtensionsConfigurationPackageVersion)
+ $(MicrosoftExtensionsDependencyInjectionPackageVersion)
+ $(MicrosoftExtensionsFileSystemGlobbingPackageVersion)
+ $(MicrosoftExtensionsLoggingPackageVersion)
+ $(MicrosoftExtensionsLoggingAbstractionsPackageVersion)
+ $(MicrosoftExtensionsLoggingConsolePackageVersion)
+ $(MicrosoftExtensionsOptionsPackageVersion)
+ $(MicrosoftExtensionsOptionsConfigurationExtensionPackageVersion)
+ $(MicrosoftExtensionsPrimitivesPackageVersion)
+ $(SystemCollectionsImmutablePackageVersion)
+ $(SystemComponentModelCompositionPackageVersion)
+ $(SystemCompositionPackageVersion)
+ $(SystemConfigurationConfigurationManagerPackageVersion)
+ $(SystemDiagnosticsDiagnosticSourcePackageVersion)
+ $(SystemDiagnosticsEventLogPackageVersion)
+ $(SystemIOHashingPackageVersion)
+ $(SystemIOPipelinesPackageVersion)
+ $(SystemReflectionMetadataPackageVersion)
+ $(SystemResourcesExtensionsPackageVersion)
+ $(SystemSecurityCryptographyProtectedDataPackageVersion)
+ $(SystemSecurityPermissionsPackageVersion)
+ $(SystemTextEncodingsWebPackageVersion)
+ $(SystemTextJsonPackageVersion)
+ $(SystemThreadingTasksDataflowPackageVersion)
+ $(SystemWindowsExtensionsPackageVersion)
+
+ $(MicrosoftDotNetArcadeSdkPackageVersion)
+ $(MicrosoftDotNetHelixSdkPackageVersion)
+ $(MicrosoftDotNetXliffTasksPackageVersion)
+
+ $(MicrosoftDiaSymReaderPackageVersion)
+
+ $(MicrosoftCodeAnalysisAnalyzersPackageVersion)
+ $(MicrosoftCodeAnalysisAnalyzerUtilitiesPackageVersion)
+ $(MicrosoftCodeAnalysisNetAnalyzersPackageVersion)
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 68107c2ef0d67..bef618c5ab9ca 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,15 +1,15 @@
-
+
https://github.com/dotnet/roslyn
ae1fff344d46976624e68ae17164e0607ab68b10
-
+
https://github.com/dotnet/dotnet
- c0e325f90fb79db0da6be5128dc292f2aabb264f
+ 30bc8f92be07c2c8c3a6addb946877260e042f63
@@ -24,6 +24,10 @@
https://github.com/dotnet/runtime
9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
+
+ https://github.com/dotnet/runtime
+ 5535e31a712343a63f5d7d796cd874e563e5ac14
+
https://github.com/dotnet/runtime
9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
@@ -76,6 +80,7 @@
https://github.com/dotnet/runtime
9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
+
https://github.com/dotnet/runtime
9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3
@@ -130,9 +135,9 @@
https://github.com/dotnet/arcade
d777c20040bdc2e52b372fa98dcb84141ed692d3
-
+
https://github.com/dotnet/roslyn-analyzers
- 2c9a20b6706b8a9ad650b41bff30980cf5af67ed
+ dd67b33c8b4164fa2c2c1dd13b616aea3948f7da
diff --git a/eng/Versions.props b/eng/Versions.props
index 540580b3c22d7..7cd36718f8df0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -1,6 +1,7 @@
+
@@ -44,53 +45,17 @@
4.6.0
4.5.0
-
- 2.0.0-beta7.25373.104
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
-
- 9.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 5.0.0-1.25277.114
- 8.0.0-preview.23468.1
- 2.0.0
- 9.0.0
- 9.0.0
- 8.0.0
- 9.0.0
- 9.0.0
- 9.0.0
- 8.0.0
- 9.0.0
- 9.0.0
17.14.1043-preview2
3.1.7
- 4.61.3
1.0.865
6.34.0
- 13.0.3
- 6.34.0
4.61.3
+ 13.0.3
6.34.0
-
+
@@ -24,7 +24,10 @@
+
+
+
diff --git a/src/Features/ExternalAccess/Copilot/SemanticSearch/CopilotSemanticSearchUtilities.cs b/src/Features/ExternalAccess/Copilot/SemanticSearch/CopilotSemanticSearchUtilities.cs
new file mode 100644
index 0000000000000..e9de02e2f8ad9
--- /dev/null
+++ b/src/Features/ExternalAccess/Copilot/SemanticSearch/CopilotSemanticSearchUtilities.cs
@@ -0,0 +1,66 @@
+// 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.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.FindSymbols;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Shared.Utilities;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch;
+
+internal static class CopilotSemanticSearchUtilities
+{
+ private static readonly FindReferencesSearchOptions s_options = new()
+ {
+ AssociatePropertyReferencesWithSpecificAccessor = false,
+ Cascade = false,
+ DisplayAllDefinitions = false,
+ Explicit = true,
+ UnidirectionalHierarchyCascade = false
+ };
+
+ public static SyntaxTree CreateSyntaxTree(SolutionServices services, string language, string? filePath, ParseOptions options, SourceText? text, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, SyntaxNode root)
+ => services.GetRequiredLanguageService(language).CreateSyntaxTree(filePath, options, text, encoding, checksumAlgorithm, root);
+
+ public static SyntaxTree ParseSyntaxTree(SolutionServices services, string language, string? filePath, ParseOptions options, SourceText text, CancellationToken cancellationToken)
+ => services.GetRequiredLanguageService(language).ParseSyntaxTree(filePath, options, text, cancellationToken);
+
+ public static PortableExecutableReference GetMetadataReference(SolutionServices services, string resolvedPath, MetadataReferenceProperties properties)
+ => services.GetRequiredService().GetReference(resolvedPath, properties);
+
+ public static ImmutableArray ToTaggedText(this IEnumerable? displayParts)
+ => TaggedTextExtensions.ToTaggedText(displayParts);
+
+ public static SyntaxNode FindNode(this Location location, bool findInsideTrivia, bool getInnermostNodeForTie, CancellationToken cancellationToken)
+ => location.SourceTree!.GetRoot(cancellationToken).FindNode(location.SourceSpan, findInsideTrivia, getInnermostNodeForTie);
+
+ public static Task FindReferencesAsync(Solution solution, ISymbol symbol, Action callback, CancellationToken cancellationToken)
+ => SymbolFinder.FindReferencesAsync(
+ symbol, solution, new Progress(callback), documents: null, s_options, cancellationToken);
+
+ private sealed class Progress(Action callback) : IStreamingFindReferencesProgress
+ {
+ public ValueTask OnStartedAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
+ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
+ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => ValueTask.CompletedTask;
+
+ public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken)
+ {
+ foreach (var (_, _, location) in references)
+ callback(location);
+
+ return ValueTask.CompletedTask;
+ }
+
+ public IStreamingProgressTracker ProgressTracker
+ => NoOpStreamingFindReferencesProgress.Instance.ProgressTracker;
+ }
+}
diff --git a/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchQueryService.cs b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchQueryService.cs
new file mode 100644
index 0000000000000..65bc4971b2a51
--- /dev/null
+++ b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchQueryService.cs
@@ -0,0 +1,56 @@
+// 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.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch;
+
+internal interface ICopilotSemanticSearchQueryService
+{
+ CompileQueryResult CompileQuery(
+ SolutionServices services,
+ string query,
+ string? targetLanguage,
+ string referenceAssembliesDir,
+ TraceSource traceSource,
+ CancellationToken cancellationToken);
+
+ Task ExecuteQueryAsync(
+ Solution solution,
+ CompiledQueryId queryId,
+ ICopilotSemanticSearchResultsObserver observer,
+ QueryExecutionOptions options,
+ TraceSource traceSource,
+ CancellationToken cancellationToken);
+
+ void DiscardQuery(CompiledQueryId queryId);
+
+ internal readonly record struct QueryExecutionOptions(
+ int? ResultCountLimit,
+ bool KeepCompiledQuery);
+
+ internal readonly record struct ExecuteQueryResult(
+ string? ErrorMessage,
+ string[]? ErrorMessageArgs = null,
+ TimeSpan ExecutionTime = default);
+
+ internal readonly record struct CompileQueryResult(
+ CompiledQueryId QueryId,
+ ImmutableArray CompilationErrors,
+ TimeSpan EmitTime = default);
+
+ internal readonly record struct QueryCompilationError(
+ string Id,
+ string Message,
+ TextSpan Span);
+
+ internal readonly record struct CompiledQueryId(
+ int Id);
+}
diff --git a/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchResultsObserver.cs b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchResultsObserver.cs
new file mode 100644
index 0000000000000..b06bfcbc5665f
--- /dev/null
+++ b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchResultsObserver.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch;
+
+internal interface ICopilotSemanticSearchResultsObserver
+{
+ ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken);
+ ValueTask OnSymbolFoundAsync(Solution solution, ISymbol symbol, CancellationToken cancellationToken);
+ ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken);
+ ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken);
+
+ internal readonly record struct UserCodeExceptionInfo(
+ string ProjectDisplayName,
+ string Message,
+ ImmutableArray TypeName,
+ ImmutableArray StackTrace,
+ TextSpan Span);
+}
diff --git a/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchSolutionService.cs b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchSolutionService.cs
new file mode 100644
index 0000000000000..4ccac508783ac
--- /dev/null
+++ b/src/Features/ExternalAccess/Copilot/SemanticSearch/ICopilotSemanticSearchSolutionService.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch;
+
+internal interface ICopilotSemanticSearchSolutionService
+{
+ DocumentId GetQueryDocumentId(Solution solution);
+ string GetQueryDocumentFilePath();
+
+ (WorkspaceChangeKind changeKind, ProjectId? projectId, DocumentId? documentId) GetWorkspaceChangeKind(Solution oldSolution, Solution newSolution);
+
+ Solution SetQueryText(Solution solution, string? query, string referenceAssembliesDir);
+}
diff --git a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs
index c3b7810df4307..b3132866960e5 100644
--- a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs
+++ b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs
@@ -152,12 +152,12 @@ public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocume
EnterBreakState(debuggingSession);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
// TODO: warning reported https://github.com/dotnet/roslyn/issues/78125
- // AssertEx.Equal([$"P.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFileB.Path)}"], InspectDiagnostics(emitDiagnostics));
+ // AssertEx.Equal([$"P.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFileB.Path)}"], InspectDiagnostics(results.Diagnostics));
EndDebuggingSession(debuggingSession);
}
@@ -182,13 +182,13 @@ public async Task ProjectNotBuilt()
Assert.Empty(diagnostics);
// changes in the project are ignored:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(
[
- $"{document1.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, document1.FilePath)}"
- ], InspectDiagnostics(emitDiagnostics));
+ $"proj: {document1.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, document1.FilePath)}"
+ ], InspectDiagnostics(results.Diagnostics));
EndDebuggingSession(debuggingSession);
}
@@ -219,8 +219,8 @@ public async Task DifferentDocumentWithSameContent()
Assert.Empty(diagnostics2);
// validate solution update status and emit - changes made during run mode are ignored:
- var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
EndDebuggingSession(debuggingSession);
@@ -253,10 +253,10 @@ public async Task ProjectThatDoesNotSupportEnC_Language(bool breakMode)
solution = solution.WithDocumentText(document1.Id, CreateText("dummy2"));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
var document2 = solution.GetDocument(document1.Id);
diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
@@ -296,7 +296,7 @@ public void F() {}
}
"""));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -335,10 +335,10 @@ class C { int Y => 2; }
var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None);
Assert.Empty(diagnostics1);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
EndDebuggingSession(debuggingSession);
}
@@ -415,7 +415,7 @@ End Class
var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None);
AssertEx.Empty(diagnostics);
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -500,7 +500,7 @@ End Class
var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None);
AssertEx.Empty(diagnostics);
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(isWarning ? ModuleUpdateStatus.None : ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -576,7 +576,7 @@ End Class
var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None);
AssertEx.Empty(diagnostics);
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -629,7 +629,7 @@ class A
return null;
};
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
Assert.Empty(results.Diagnostics);
@@ -680,7 +680,7 @@ internal async Task Project_MetadataReferences()
var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None);
AssertEx.Empty(diagnostics);
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -722,7 +722,7 @@ internal async Task Project_MetadataReferences_RemoveAdd()
// remove dependency:
solution = project.RemoveMetadataReference(libV1).Solution;
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
Assert.Empty(results.Diagnostics);
@@ -730,7 +730,7 @@ internal async Task Project_MetadataReferences_RemoveAdd()
// add newer version:
solution = project.AddMetadataReference(libV2).Solution;
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -770,7 +770,7 @@ internal async Task Project_MetadataReferences_Add()
// add dependency:
solution = project.AddMetadataReference(libV1).Solution;
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
Assert.Empty(results.Diagnostics);
@@ -805,7 +805,7 @@ internal async Task Project_MetadataReferences_MultipleVersions()
// add version 3:
solution = project.AddMetadataReference(libV3).Solution;
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
@@ -847,8 +847,8 @@ public async Task DesignTimeOnlyDocument()
Assert.Empty(diagnostics);
// validate solution update status and emit - changes made in design-time-only documents are ignored:
- var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
EndDebuggingSession(debuggingSession);
@@ -884,15 +884,15 @@ public async Task DesignTimeOnlyDocument_Dynamic()
solution = solution.WithDocumentText(document1.Id, CreateText("class E {}"));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
-
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
+
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
}
[Theory, CombinatorialData]
@@ -978,18 +978,18 @@ public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames.
Assert.Empty(await service.GetDocumentDiagnosticsAsync(designTimeOnlyDocument2, s_noActiveSpans, CancellationToken.None));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
if (delayLoad)
{
LoadLibraryToDebuggee(moduleId);
// validate solution update status and emit:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
}
EndDebuggingSession(debuggingSession);
@@ -1036,19 +1036,21 @@ public async Task ErrorReadingModuleFile(bool breakMode)
var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
Assert.Empty(docDiagnostics);
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(
[$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"],
InspectDiagnostics(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
+
// correct the error:
EmitLibrary(projectId, source2);
- var (updates2, diagnostics2) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates2.Status);
- Assert.Empty(diagnostics2);
+ var results2 = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results2.ModuleUpdates.Status);
+ Assert.Empty(results2.Diagnostics);
CommitSolutionUpdate(debuggingSession);
@@ -1117,13 +1119,14 @@ public async Task ErrorReadingPdbFile()
Assert.Empty(docDiagnostics);
// an error occurred so we need to call update to determine whether we have changes to apply or not:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(
[$"proj: {document2.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"],
InspectDiagnostics(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
AssertEx.Equal(
@@ -1165,17 +1168,18 @@ public async Task ErrorReadingSourceFile()
Assert.Empty(docDiagnostics);
// an error occurred so we need to call update to determine whether we have changes to apply or not:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(
[$"test: {document1.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"],
InspectDiagnostics(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
fileLock.Dispose();
// try apply changes again:
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.NotEmpty(results.ModuleUpdates.Updates);
Assert.Empty(results.Diagnostics);
@@ -1227,8 +1231,8 @@ public async Task Document_Add(bool breakMode)
var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None);
Assert.Empty(diagnostics2);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
debuggingSession.DiscardSolutionUpdate();
if (breakMode)
@@ -1279,9 +1283,9 @@ public async Task Document_Delete()
// delete B:
solution = solution.RemoveDocument(documentBId);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.NotEmpty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.NotEmpty(results.ModuleUpdates.Updates);
debuggingSession.DiscardSolutionUpdate();
@@ -1316,9 +1320,9 @@ public async Task Document_RenameAndUpdate()
var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(document2Id), s_noActiveSpans, CancellationToken.None);
AssertEx.Empty(diagnostics);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.NotEmpty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.NotEmpty(results.ModuleUpdates.Updates);
debuggingSession.DiscardSolutionUpdate();
@@ -1381,10 +1385,10 @@ public async Task ModuleDisallowsEditAndContinue_NoChanges(bool breakMode)
// workspace is updated to new version after build completed and the session started:
solution = solution.WithDocumentText(document0.Id, CreateText(source1));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
if (breakMode)
{
@@ -1419,9 +1423,9 @@ public async Task ModuleDisallowsEditAndContinue_SourceGenerator_NoChanges()
var document1 = solution.Projects.Single().Documents.Single();
solution = solution.WithDocumentText(document1.Id, CreateText(source2));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
EndDebuggingSession(debuggingSession);
}
@@ -1476,13 +1480,14 @@ void M()
AssertEx.Empty(docDiagnostics);
// validate solution update status and emit:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(
[$"proj: {document2.FilePath}: (5,0)-(5,32): Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}"],
InspectDiagnostics(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
@@ -1575,11 +1580,13 @@ public async Task RudeEdits(bool breakMode)
InspectDiagnostics(docDiagnostics));
// validate solution update status and emit:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
+
if (breakMode)
{
ExitBreakState(debuggingSession);
@@ -1663,10 +1670,10 @@ public async Task DeferredApplyChangeWithActiveStatementRudeEdits()
ExitBreakState(debuggingSession);
// apply the change:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.NotEmpty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.NotEmpty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
CommitSolutionUpdate(debuggingSession);
@@ -1717,15 +1724,17 @@ class C { int Y => 2; }
[$"{generatedDocument.FilePath}: (0,17)-(0,18): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"],
InspectDiagnostics(docDiagnostics));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
- [Theory, CombinatorialData]
+ [Theory(Skip = "https://github.com/dotnet/roslyn/issues/79589")]
+ [CombinatorialData]
public async Task RudeEdits_DocumentOutOfSync(bool breakMode)
{
var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }";
@@ -1766,68 +1775,53 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode)
Assert.Empty(docDiagnostics);
// the document is out-of-sync, so no rude edits reported:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
+ // project is stale:
+ Assert.True(debuggingSession.LastCommittedSolution.StaleProjects.ContainsKey(projectId));
+
// TODO: warning reported https://github.com/dotnet/roslyn/issues/78125
- // AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
+ // AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics));
// We do not reload the content of out-of-sync file for analyzer query.
// We don't check if the content on disk has been updated to match either.
- // Document state can only be reset via UpdateBaselines.
sourceFile.WriteAllText(source0, Encoding.UTF8);
docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
Assert.Empty(docDiagnostics);
// rebuild triggers reload of out-of-sync file content:
moduleId = EmitAndLoadLibraryToDebuggee(projectId, source0, sourceFilePath: sourceFile.Path);
- debuggingSession.UpdateBaselines(solution.WithDocumentText(documentId, CreateText(source0)), [projectId]);
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
- Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
- Assert.Empty(results.ModuleUpdates.Updates);
- AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
- // now we see the rude edit:
- docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
- AssertEx.Equal(
- [$"{document2.FilePath}: (0,11)-(0,22): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"],
- InspectDiagnostics(docDiagnostics));
+ // project has been unstaled:
+ Assert.False(debuggingSession.LastCommittedSolution.StaleProjects.ContainsKey(projectId));
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
- Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
- AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ Assert.Empty(results.Diagnostics);
if (breakMode)
{
ExitBreakState(debuggingSession);
- EndDebuggingSession(debuggingSession);
- }
- else
- {
- EndDebuggingSession(debuggingSession);
}
- AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
+ EndDebuggingSession(debuggingSession);
if (breakMode)
{
AssertEx.SequenceEqual(
[
- "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=1|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2",
- "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=",
- "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
+ "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=1|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2"
], _telemetryLog);
}
else
{
AssertEx.SequenceEqual(
[
- "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=1",
- "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=",
- "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
+ "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1"
], _telemetryLog);
}
}
@@ -1867,11 +1861,12 @@ public async Task RudeEdits_DocumentWithoutSequencePoints()
InspectDiagnostics(docDiagnostics));
// validate solution update status and emit:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
@@ -1909,11 +1904,13 @@ public async Task RudeEdits_DelayLoadedModule()
[$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"],
InspectDiagnostics(docDiagnostics));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
+
// load library to the debuggee:
LoadLibraryToDebuggee(moduleId);
@@ -1923,17 +1920,18 @@ public async Task RudeEdits_DelayLoadedModule()
[$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"],
InspectDiagnostics(docDiagnostics));
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
[Theory]
[CombinatorialData]
- public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool allowPartialUpdates)
+ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit)
{
var source3 = "abstract class C { void F() {} public abstract void G(); }";
var projectDir = Temp.CreateDirectory();
@@ -1956,7 +1954,7 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool
{
solution = solution.WithDocumentText(documentId, CreateText("abstract class C { void F() {} }"));
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ProjectsToRebuild);
Assert.Empty(results.ProjectsToRestart);
@@ -1985,29 +1983,21 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool
InspectDiagnostics(docDiagnostics));
// validate solution update status and emit:
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.GetAllDiagnostics()));
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
Assert.Empty(results.ModuleUpdates.Updates);
AssertEx.Equal([projectId], results.ProjectsToRebuild);
AssertEx.Equal([projectId], results.ProjectsToRestart.Keys);
- if (allowPartialUpdates)
- {
- // assuming user approved restart and rebuild:
- CommitSolutionUpdate(debuggingSession);
- }
+ // assuming user approved restart and rebuild:
+ CommitSolutionUpdate(debuggingSession);
// rebuild and restart:
_debuggerService.LoadedModules.Remove(moduleId);
File.WriteAllText(sourceFilePath, source3, Encoding.UTF8);
moduleId = EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentId));
- if (!allowPartialUpdates)
- {
- debuggingSession.UpdateBaselines(solution, results.ProjectsToRebuild);
- }
-
if (validChangeBeforeRudeEdit)
{
// baseline should be removed:
@@ -2027,7 +2017,7 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool
Assert.Empty(await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None));
// apply valid change:
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -2075,10 +2065,10 @@ public async Task SyntaxError()
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
EndDebuggingSession(debuggingSession);
@@ -2116,14 +2106,14 @@ public async Task SemanticError()
// The EnC analyzer does not check for and block on all semantic errors as they are already reported by diagnostic analyzer.
// Blocking update on semantic errors would be possible, but the status check is only an optimization to avoid emitting.
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
- Assert.Empty(updates.Updates);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
// TODO: https://github.com/dotnet/roslyn/issues/36061
// Semantic errors should not be reported in emit diagnostics.
- AssertEx.Equal([$"{document2.FilePath}: (0,30)-(0,32): Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}"], InspectDiagnostics(emitDiagnostics));
+ AssertEx.Equal([$"proj: {document2.FilePath}: (0,30)-(0,32): Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}"], InspectDiagnostics(results.Diagnostics));
EndDebuggingSession(debuggingSession);
@@ -2186,8 +2176,8 @@ public async Task HasChanges()
Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "NonexistentFile.cs", CancellationToken.None));
// All projects must have no errors.
- var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status);
// add a project:
@@ -2552,8 +2542,8 @@ public async Task Project_Add()
.AddTestDocument(sourceB1, path: sourceFileB.Path, out var documentBId).Project.Solution
.AddProjectReference(projectAId, new ProjectReference(projectBId));
- var runningProjects = ImmutableDictionary.Empty
- .Add(projectAId, new RunningProjectInfo() { AllowPartialUpdate = true, RestartWhenChangesHaveNoEffect = false });
+ var runningProjects = ImmutableDictionary.Empty
+ .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false });
var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None);
@@ -2593,8 +2583,8 @@ public async Task ProjectReference_Add()
// Add project reference A -> B
solution = solution.AddProjectReference(projectAId, new ProjectReference(projectBId));
- var runningProjects = ImmutableDictionary.Empty
- .Add(projectAId, new RunningProjectInfo() { AllowPartialUpdate = true, RestartWhenChangesHaveNoEffect = false });
+ var runningProjects = ImmutableDictionary.Empty
+ .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false });
var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None);
@@ -2687,11 +2677,11 @@ public async Task Project_Add_BinaryAlreadyLoaded()
// update document with a valid change:
solution = solution.WithDocumentText(documentB2.Id, CreateText("class B { int F() => 2; }"));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
// TODO: https://github.com/dotnet/roslyn/issues/1204
// verify valid update
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
ExitBreakState(debuggingSession);
@@ -2832,12 +2822,12 @@ int M()
"""));
// validate solution update status and emit
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check that no types have been updated. this used to throw
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.UpdatedTypes);
debuggingSession.DiscardSolutionUpdate();
@@ -2871,12 +2861,13 @@ public async Task Capabilities_SynthesizedNewType()
AssertEx.Empty(diagnostics);
// They are reported as emit diagnostics
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics));
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ AssertEx.Equal([$"proj: : Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(results.Diagnostics));
// no emitted delta:
- Assert.Empty(updates.Updates);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
@@ -2903,11 +2894,11 @@ public async Task ValidSignificantChange_EmitError()
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics));
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ AssertEx.Equal([$"proj: {document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(results.Diagnostics));
// no emitted delta:
- Assert.Empty(updates.Updates);
+ Assert.Empty(results.ModuleUpdates.Updates);
// no pending update:
Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
@@ -2919,9 +2910,9 @@ public async Task ValidSignificantChange_EmitError()
Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
// solution update status after discarding an update (still has update ready):
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
- AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics));
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status);
+ AssertEx.Equal([$"proj: {document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(results.Diagnostics));
EndDebuggingSession(debuggingSession);
@@ -2996,10 +2987,10 @@ public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDo
}
// EnC service queries for a document, which triggers read of the source file from disk.
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
ExitBreakState(debuggingSession);
@@ -3010,16 +3001,16 @@ public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDo
solution = solution.WithDocumentText(documentId, CreateTextFromFile(sourceFile.Path));
var document3 = solution.Projects.Single().Documents.Single();
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
if (saveDocument)
{
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
}
else
{
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
debuggingSession.DiscardSolutionUpdate();
}
@@ -3070,11 +3061,11 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes
AssertEx.Empty(diagnostics);
// since the document is out-of-sync we need to call update to determine whether we have changes to apply or not:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
// TODO: warning reported https://github.com/dotnet/roslyn/issues/78125
- // AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
+ // AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics));
// undo:
solution = solution.WithDocumentText(documentId, CreateText(source1));
@@ -3088,14 +3079,14 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes
Assert.Equal(CommittedSolution.DocumentState.OutOfSync, state);
sourceFile.WriteAllText(source1, Encoding.UTF8);
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
AssertEx.Equal(
[
- $"{project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourceFile.Path)}"
- ], InspectDiagnostics(emitDiagnostics));
+ $"test: {document3.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourceFile.Path)}"
+ ], InspectDiagnostics(results.Diagnostics));
// the content actually hasn't changed:
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
EndDebuggingSession(debuggingSession);
}
@@ -3158,10 +3149,10 @@ public async Task ValidSignificantChange_AddedFileNotObservedBeforeDebuggingSess
// AssertEx.Equal(new[] { $"({activeLineSpan1}, LeafFrame)" }, spans.Single().Select(s => s.ToString()));
// No changes.
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
- AssertEx.Empty(emitDiagnostics);
+ AssertEx.Empty(results.Diagnostics);
EndDebuggingSession(debuggingSession);
}
@@ -3197,10 +3188,10 @@ public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad)
EnterBreakState(debuggingSession);
// no changes have been made to the project
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(updates.Updates);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ Assert.Empty(results.Diagnostics);
// a file watcher observed a change and updated the document, so it now reflects the content on disk (the code that we compiled):
solution = solution.WithDocumentText(document1.Id, CreateText(sourceOnDisk));
@@ -3210,9 +3201,9 @@ public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad)
Assert.Empty(diagnostics);
// the content of the file is now exactly the same as the compiled document, so there is no change to be applied:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
EndDebuggingSession(debuggingSession);
@@ -3243,10 +3234,10 @@ public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool com
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- ValidateDelta(updates.Updates.Single());
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ ValidateDelta(results.ModuleUpdates.Updates.Single());
void ValidateDelta(ManagedHotReloadUpdate delta)
{
@@ -3265,7 +3256,7 @@ void ValidateDelta(ManagedHotReloadUpdate delta)
// the update should be stored on the service:
var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
var newBaseline = pendingUpdate.ProjectBaselines.Single();
- AssertEx.Equal(updates.Updates, pendingUpdate.Deltas);
+ AssertEx.Equal(results.ModuleUpdates.Updates, pendingUpdate.Deltas);
Assert.Equal(document2.Project.Id, newBaseline.ProjectId);
Assert.Equal(moduleId, newBaseline.EmitBaseline.OriginalMetadata.GetModuleVersionId());
@@ -3293,9 +3284,9 @@ void ValidateDelta(ManagedHotReloadUpdate delta)
Assert.Same(newBaseline.EmitBaseline, debuggingSession.GetTestAccessor().GetProjectBaselines(document2.Project.Id).Single().EmitBaseline);
// solution update status after committing an update:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
}
else
{
@@ -3305,11 +3296,11 @@ void ValidateDelta(ManagedHotReloadUpdate delta)
Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
// solution update status after committing an update:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
- ValidateDelta(updates.Updates.Single());
+ ValidateDelta(results.ModuleUpdates.Updates.Single());
debuggingSession.DiscardSolutionUpdate();
}
@@ -3381,12 +3372,12 @@ public async Task ValidSignificantChange_EmitSuccessful_UpdateDeferred(bool comm
var document2 = solution.GetDocument(document1.Id);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
// delta to apply:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -3432,9 +3423,9 @@ public async Task ValidSignificantChange_EmitSuccessful_UpdateDeferred(bool comm
var document3 = solution.GetDocument(document1.Id);
solution = solution.WithDocumentText(document3.Id, CreateText("class C1 { void M1() { int a = 3; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }"));
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
debuggingSession.DiscardSolutionUpdate();
}
else
@@ -3500,12 +3491,12 @@ partial class C { int Y = 2; }
"""));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -3517,10 +3508,9 @@ partial class C { int Y = 2; }
EndDebuggingSession(debuggingSession);
}
- [Theory]
- [CombinatorialData]
+ [Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78244")]
- public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit(bool allowPartialUpdate)
+ public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit()
{
using var _ = CreateWorkspace(out var solution, out var service);
@@ -3580,31 +3570,21 @@ void F() {}
InspectDiagnostics(docDiagnostics));
// validate solution update status and emit:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics));
- if (allowPartialUpdate)
- {
- AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRebuild);
- AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRestart.Keys);
-
- var delta = results.ModuleUpdates.Updates.Single();
- Assert.NotEmpty(delta.ILDelta);
- Assert.NotEmpty(delta.MetadataDelta);
- Assert.NotEmpty(delta.PdbDelta);
- Assert.Equal(1, delta.UpdatedMethods.Length);
+ AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRebuild);
+ AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRestart.Keys);
- debuggingSession.DiscardSolutionUpdate();
- }
- else
- {
- AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRebuild);
- AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRestart.Keys);
+ var delta = results.ModuleUpdates.Updates.Single();
+ Assert.NotEmpty(delta.ILDelta);
+ Assert.NotEmpty(delta.MetadataDelta);
+ Assert.NotEmpty(delta.PdbDelta);
+ Assert.Equal(1, delta.UpdatedMethods.Length);
- Assert.Empty(results.ModuleUpdates.Updates);
- }
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
@@ -3672,21 +3652,19 @@ static B()
// TODO: Set RestartWhenChangesHaveNoEffect=true and AllowPartialUpdate=true
// https://github.com/dotnet/roslyn/issues/78244
- var runningProjects = ImmutableDictionary.Empty
- .Add(projectAId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false })
- .Add(projectBId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false });
+ var runningProjects = ImmutableDictionary.Empty
+ .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false })
+ .Add(projectBId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false });
// emit updates:
- var result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None);
-
- AssertEx.SetEqual([], result.ProjectsToRestart.Select(p => p.Key.DebugName));
+ var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None);
- var updates = result.ModuleUpdates;
- AssertEx.SequenceEqual(["ENC0118"], InspectDiagnosticIds(result.GetAllDiagnostics()));
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ AssertEx.SetEqual([], results.ProjectsToRestart.Select(p => p.Key.DebugName));
+ AssertEx.SequenceEqual(["ENC0118"], InspectDiagnosticIds(results.GetAllDiagnostics()));
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- Assert.Equal(2, updates.Updates.Length);
+ Assert.Equal(2, results.ModuleUpdates.Updates.Length);
// Process will be restarted, so discard all updates:
debuggingSession.DiscardSolutionUpdate();
@@ -3737,12 +3715,12 @@ class C { int Y => 2; }
"""));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -3792,7 +3770,7 @@ class C { int Y => 2; }
"""));
// validate solution update status and emit:
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
var generatedFilePath = Path.Combine(
TempRoot.Root,
@@ -3804,6 +3782,7 @@ class C { int Y => 2; }
[$"proj: {generatedFilePath}: (0,0)-(0,56): Error ENC0021: {string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.attribute)}"],
InspectDiagnostics(results.Diagnostics));
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
@@ -3852,12 +3831,12 @@ int M()
"""));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
var lineUpdate = delta.SequencePoints.Single();
@@ -3902,12 +3881,12 @@ partial class C { int X = 1; }
"""));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -3954,12 +3933,12 @@ class C { int Y => 1; }
"""));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -4000,12 +3979,12 @@ class C { int Y => 1; }
solution = solution.WithAnalyzerConfigDocumentText(configDocument1.Id, GetAnalyzerConfigText(configV2));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -4041,12 +4020,12 @@ public async Task ValidSignificantChange_SourceGenerators_DocumentRemove()
solution = document1.Project.Solution.RemoveDocument(document1.Id);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -4079,9 +4058,9 @@ public async Task ValidInsignificantChange()
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
// solution has been updated:
var text = await debuggingSession.LastCommittedSolution.GetRequiredProject(document1.Project.Id).GetRequiredDocument(document1.Id).GetTextAsync();
@@ -4123,12 +4102,13 @@ public async Task RudeEdit()
AssertEx.Empty(diagnostics);
// They are reported as emit diagnostics
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics));
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ AssertEx.Equal([$"proj: : Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(results.Diagnostics));
// no emitted delta:
- Assert.Empty(updates.Updates);
+ Assert.Empty(results.ModuleUpdates.Updates);
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
}
@@ -4186,13 +4166,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source2));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
- var deltaA = updates.Updates.Single(d => d.Module == moduleIdA);
- var deltaB = updates.Updates.Single(d => d.Module == moduleIdB);
- Assert.Equal(2, updates.Updates.Length);
+ var deltaA = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdA);
+ var deltaB = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdB);
+ Assert.Equal(2, results.ModuleUpdates.Updates.Length);
// the update should be stored on the service:
var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
@@ -4219,9 +4199,9 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
Assert.Same(newBaselineA1, debuggingSession.GetTestAccessor().GetProjectBaselines(projectA.Id).Single().EmitBaseline);
Assert.Same(newBaselineB1, debuggingSession.GetTestAccessor().GetProjectBaselines(projectB.Id).Single().EmitBaseline);
- // solution update status after committing an update:(updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ // solution update status after committing an update:results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
ExitBreakState(debuggingSession);
EnterBreakState(debuggingSession);
@@ -4234,13 +4214,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source3));
// validate solution update status and emit:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
- deltaA = updates.Updates.Single(d => d.Module == moduleIdA);
- deltaB = updates.Updates.Single(d => d.Module == moduleIdB);
- Assert.Equal(2, updates.Updates.Length);
+ deltaA = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdA);
+ deltaB = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdB);
+ Assert.Equal(2, results.ModuleUpdates.Updates.Length);
// the update should be stored on the service:
pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
@@ -4273,9 +4253,9 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
Assert.Same(newBaselineB2, debuggingSession.GetTestAccessor().GetProjectBaselines(projectB.Id).Single().EmitBaseline);
// solution update status after committing an update:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
ExitBreakState(debuggingSession);
EndDebuggingSession(debuggingSession);
@@ -4330,10 +4310,10 @@ public async Task MultiTargetedPartiallyBuiltProjects()
solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2);
// delta emitted only for up-to-date project
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
- AssertEx.SequenceEqual([mvidA], updates.Updates.Select(u => u.Module));
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
+ AssertEx.SequenceEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module));
CommitSolutionUpdate(debuggingSession);
@@ -4346,10 +4326,10 @@ public async Task MultiTargetedPartiallyBuiltProjects()
solution = solution.WithDocumentText(documentA.Id, text0).WithDocumentText(documentB.Id, text0);
// both projects are up-to-date now, but B hasn't changed w.r.t. baseline:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
- AssertEx.SetEqual([mvidA], updates.Updates.Select(u => u.Module));
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
+ AssertEx.SetEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module));
CommitSolutionUpdate(debuggingSession);
@@ -4357,10 +4337,10 @@ public async Task MultiTargetedPartiallyBuiltProjects()
solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2);
// project B is considered stale until rebuilt (even though the document content matches now):
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
- AssertEx.SetEqual([mvidA], updates.Updates.Select(u => u.Module));
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
+ AssertEx.SetEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module));
CommitSolutionUpdate(debuggingSession);
@@ -4371,19 +4351,19 @@ public async Task MultiTargetedPartiallyBuiltProjects()
// no changes have been made:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
- Assert.Empty(emitDiagnostics);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
// update source file in the editor:
var text3 = CreateText(source3);
solution = solution.WithDocumentText(documentA.Id, text3).WithDocumentText(documentB.Id, text3);
// both modules updated now:
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
- Assert.Empty(emitDiagnostics);
- AssertEx.SetEqual([mvidA, mvidB2], updates.Updates.Select(u => u.Module));
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ Assert.Empty(results.Diagnostics);
+ AssertEx.SetEqual([mvidA, mvidB2], results.ModuleUpdates.Updates.Select(u => u.Module));
CommitSolutionUpdate(debuggingSession);
EndDebuggingSession(debuggingSession);
@@ -4430,13 +4410,13 @@ public async Task MultiTargeted_AllTargetsStale()
solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2);
// no updates
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Equal(ModuleUpdateStatus.None, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status);
AssertEx.Equal(
[
- $"{documentA.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}",
- $"{documentB.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}"
- ], InspectDiagnostics(emitDiagnostics));
+ $"A: {documentA.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}",
+ $"B: {documentB.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}"
+ ], InspectDiagnostics(results.Diagnostics));
EndDebuggingSession(debuggingSession);
}
@@ -4461,7 +4441,7 @@ public async Task ValidSignificantChange_BaselineCreationFailed_NoStream()
// change the source (valid edit):
solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
AssertEx.Equal(
[$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"],
InspectDiagnostics(results.Diagnostics));
@@ -4497,12 +4477,13 @@ public async Task ValidSignificantChange_BaselineCreationFailed_AssemblyReadErro
var document1 = solution.Projects.Single().Documents.Single();
solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
AssertEx.Equal(
[$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"],
InspectDiagnostics(results.Diagnostics));
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ debuggingSession.DiscardSolutionUpdate();
EndDebuggingSession(debuggingSession);
@@ -4961,12 +4942,12 @@ void F()
solution = solution.WithDocumentText(document1.Id, CreateText(source2));
// validate solution update status and emit:
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
// check emitted delta:
- var delta = updates.Updates.Single();
+ var delta = results.ModuleUpdates.Updates.Single();
Assert.Empty(delta.ActiveStatements);
Assert.NotEmpty(delta.ILDelta);
Assert.NotEmpty(delta.MetadataDelta);
@@ -5051,10 +5032,12 @@ int F()
[$"{document.FilePath}: (9,8)-(9,13): Error ENC0063: {string.Format(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application, CSharpFeaturesResources.catch_clause)}"],
InspectDiagnostics(docDiagnostics));
- var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
AssertEx.SequenceEqual(["ENC0063"], InspectDiagnosticIds(results.Diagnostics));
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
+ debuggingSession.DiscardSolutionUpdate();
+
// undo the change
solution = solution.WithDocumentText(document.Id, CreateText(source1));
document = solution.GetDocument(document.Id);
@@ -5068,7 +5051,7 @@ int F()
Assert.Empty(docDiagnostics);
// validate solution update status and emit (Hot Reload change):
- results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
Assert.Empty(results.Diagnostics);
Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
@@ -5134,11 +5117,11 @@ static void F()
solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV2)));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
- Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single());
+ Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single());
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -5154,11 +5137,11 @@ static void F()
solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV3)));
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
- Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single());
+ Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single());
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -5190,11 +5173,11 @@ static void F()
solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV4)));
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
- Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single());
+ Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single());
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -5309,11 +5292,11 @@ static void F()
var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None);
Assert.Empty(diagnostics);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
- Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single());
+ Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single());
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -5371,11 +5354,11 @@ static void F()
}
""")));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
- Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single());
+ Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single());
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
@@ -5478,10 +5461,10 @@ static void H()
Assert.Equal(1, oldProject.DocumentIds.Count);
Assert.Equal(2, newProject.DocumentIds.Count);
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, modifiedSolution);
- Assert.Empty(emitDiagnostics);
- Assert.False(updates.Updates.IsEmpty);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, modifiedSolution);
+ Assert.Empty(results.Diagnostics);
+ Assert.False(results.ModuleUpdates.Updates.IsEmpty);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
CommitSolutionUpdate(debuggingSession);
EndDebuggingSession(debuggingSession);
@@ -5524,14 +5507,14 @@ public async Task MultiSession()
var solution1 = solution.WithDocumentText(documentIdA, CreateText("class C { void M() { System.Console.WriteLine(" + i + "); } }"));
- var result1 = await encService.EmitSolutionUpdateAsync(sessionId, solution1, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None);
+ var result1 = await encService.EmitSolutionUpdateAsync(sessionId, solution1, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None);
Assert.Empty(result1.Diagnostics);
Assert.Equal(1, result1.ModuleUpdates.Updates.Length);
encService.DiscardSolutionUpdate(sessionId);
var solution2 = solution1.WithDocumentText(documentIdA, CreateText(source3));
- var result2 = await encService.EmitSolutionUpdateAsync(sessionId, solution2, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None);
+ var result2 = await encService.EmitSolutionUpdateAsync(sessionId, solution2, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None);
Assert.Equal("CS0103", result2.Diagnostics.Single().Diagnostics.Single().Id);
Assert.Empty(result2.ModuleUpdates.Updates);
@@ -5554,7 +5537,7 @@ public async Task Disposal()
EndDebuggingSession(debuggingSession);
// The folling methods shall not be called after the debugging session ended.
- await Assert.ThrowsAsync(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None));
+ await Assert.ThrowsAsync(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None));
Assert.Throws(() => debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState: true));
Assert.Throws(() => debuggingSession.DiscardSolutionUpdate());
Assert.Throws(() => debuggingSession.CommitSolutionUpdate());
@@ -5605,11 +5588,11 @@ public class C
// lib source is updated:
solution = solution.WithDocumentText(documentId, CreateText(libSource2));
- var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ var results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
- var update = updates.Updates.Single();
+ var update = results.ModuleUpdates.Updates.Single();
GetModuleIds(update.MetadataDelta, out var updateModuleId, out var baseId, out var genId, out var gen);
Assert.Equal(libMvid1, updateModuleId);
Assert.Equal(1, gen);
@@ -5629,13 +5612,13 @@ public class C
}
"""));
- (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
- Assert.Empty(emitDiagnostics);
- Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
+ results = await EmitSolutionUpdateAsync(debuggingSession, solution);
+ Assert.Empty(results.Diagnostics);
+ Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status);
- Assert.Equal(2, updates.Updates.Length);
- var libUpdate1 = updates.Updates.Single(u => u.Module == libMvid1);
- var libUpdate2 = updates.Updates.Single(u => u.Module == libMvid2);
+ Assert.Equal(2, results.ModuleUpdates.Updates.Length);
+ var libUpdate1 = results.ModuleUpdates.Updates.Single(u => u.Module == libMvid1);
+ var libUpdate2 = results.ModuleUpdates.Updates.Single(u => u.Module == libMvid2);
// update of the original library should chain to its previous delta:
GetModuleIds(libUpdate1.MetadataDelta, out var updateModuleId1, out var baseId1, out var genId1, out var gen1);
diff --git a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs
index 0e93680c4a32b..0b3120a89fa18 100644
--- a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs
+++ b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs
@@ -53,8 +53,8 @@ private static ImmutableArray CreateProjectRudeEdits(IEnumer
.OrderBy(g => g.Key)
.Select(g => new ProjectDiagnostics(g.Key, [.. g.Select(e => Diagnostic.Create(EditAndContinueDiagnosticDescriptors.GetDescriptor(e.kind), Location.None))]))];
- private static ImmutableDictionary CreateRunningProjects(IEnumerable<(ProjectId id, bool noEffectRestarts)> projectIds, bool allowPartialUpdate = true)
- => projectIds.ToImmutableDictionary(keySelector: e => e.id, elementSelector: e => new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = e.noEffectRestarts, AllowPartialUpdate = allowPartialUpdate });
+ private static ImmutableDictionary CreateRunningProjects(IEnumerable<(ProjectId id, bool noEffectRestarts)> projectIds)
+ => projectIds.ToImmutableDictionary(keySelector: e => e.id, elementSelector: e => new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = e.noEffectRestarts });
private static IEnumerable Inspect(ImmutableDictionary> projectsToRestart)
=> projectsToRestart
@@ -382,9 +382,8 @@ public void RunningProjects_NoEffectEditAndRudeEdit_SameProject()
AssertEx.SetEqual([a, b], projectsToRebuild);
}
- [Theory]
- [CombinatorialData]
- public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allowPartialUpdate)
+ [Fact]
+ public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects()
{
using var _ = CreateWorkspace(out var solution);
@@ -402,7 +401,7 @@ public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allow
CreateValidUpdates(p0, q),
CreateProjectRudeEdits(blocking: [p1, p2], noEffect: [q]),
addedUnbuiltProjects: [],
- CreateRunningProjects([(r0, noEffectRestarts: false), (r1, noEffectRestarts: false), (r2, noEffectRestarts: false)], allowPartialUpdate),
+ CreateRunningProjects([(r0, noEffectRestarts: false), (r1, noEffectRestarts: false), (r2, noEffectRestarts: false)]),
out var projectsToRestart,
out var projectsToRebuild);
@@ -413,24 +412,12 @@ public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allow
// ==> R0 has to restart due to rude edits in P1 and P2
// Q has update
// ==> R0 has to restart due to rude edits in P1 and P2
- if (allowPartialUpdate)
- {
- AssertEx.Equal(
- [
- "R0: [P1,P2]",
- "R1: [P1]",
- "R2: [P2]",
- ], Inspect(projectsToRestart));
- }
- else
- {
- AssertEx.Equal(
- [
- "R0: []",
- "R1: [P1]",
- "R2: [P2]",
- ], Inspect(projectsToRestart));
- }
+ AssertEx.Equal(
+ [
+ "R0: [P1,P2]",
+ "R1: [P1]",
+ "R2: [P2]",
+ ], Inspect(projectsToRestart));
AssertEx.SetEqual([r0, r1, r2], projectsToRebuild);
}
@@ -482,7 +469,7 @@ public void RunningProjects_RudeEditAndUpdate_DependentOnRebuiltProject()
CreateValidUpdates(c),
CreateProjectRudeEdits(blocking: [b], noEffect: []),
addedUnbuiltProjects: [],
- CreateRunningProjects([(a, noEffectRestarts: false)], allowPartialUpdate: true),
+ CreateRunningProjects([(a, noEffectRestarts: false)]),
out var projectsToRestart,
out var projectsToRebuild);
@@ -513,7 +500,7 @@ public void RunningProjects_AddedProject_NotImpactingRunningProject()
CreateValidUpdates(c),
CreateProjectRudeEdits(blocking: [], noEffect: []),
addedUnbuiltProjects: [b],
- CreateRunningProjects([(a, noEffectRestarts: false)], allowPartialUpdate: true),
+ CreateRunningProjects([(a, noEffectRestarts: false)]),
out var projectsToRestart,
out var projectsToRebuild);
@@ -540,7 +527,7 @@ public void RunningProjects_AddedProject_ImpactingRunningProject()
CreateValidUpdates(c),
CreateProjectRudeEdits(blocking: [], noEffect: []),
addedUnbuiltProjects: [b],
- CreateRunningProjects([(a, noEffectRestarts: false), (e, noEffectRestarts: false)], allowPartialUpdate: true),
+ CreateRunningProjects([(a, noEffectRestarts: false), (e, noEffectRestarts: false)]),
out var projectsToRestart,
out var projectsToRebuild);
@@ -556,9 +543,8 @@ public void RunningProjects_AddedProject_ImpactingRunningProject()
AssertEx.SetEqual([a, b, e], projectsToRebuild);
}
- [Theory]
- [CombinatorialData]
- public void RunningProjects_RudeEditAndUpdate_Independent(bool allowPartialUpdate)
+ [Fact]
+ public void RunningProjects_RudeEditAndUpdate_Independent()
{
using var _ = CreateWorkspace(out var solution);
@@ -573,31 +559,17 @@ public void RunningProjects_RudeEditAndUpdate_Independent(bool allowPartialUpdat
CreateValidUpdates(c),
CreateProjectRudeEdits(blocking: [d], noEffect: []),
addedUnbuiltProjects: [],
- CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)], allowPartialUpdate),
+ CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]),
out var projectsToRestart,
out var projectsToRebuild);
- if (allowPartialUpdate)
- {
- // D has rude edit => B has to restart
- AssertEx.Equal(["B: [D]"], Inspect(projectsToRestart));
- AssertEx.SetEqual([b], projectsToRebuild);
- }
- else
- {
- AssertEx.Equal(
- [
- "A: []",
- "B: [D]",
- ], Inspect(projectsToRestart));
-
- AssertEx.SetEqual([a, b], projectsToRebuild);
- }
+ // D has rude edit => B has to restart
+ AssertEx.Equal(["B: [D]"], Inspect(projectsToRestart));
+ AssertEx.SetEqual([b], projectsToRebuild);
}
- [Theory]
- [CombinatorialData]
- public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate)
+ [Fact]
+ public void RunningProjects_NoEffectEditAndUpdate()
{
using var _ = CreateWorkspace(out var solution);
@@ -612,7 +584,7 @@ public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate)
CreateValidUpdates(c, d),
CreateProjectRudeEdits(blocking: [], noEffect: [d]),
addedUnbuiltProjects: [],
- CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)], allowPartialUpdate),
+ CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)]),
out var projectsToRestart,
out var projectsToRebuild);
@@ -620,22 +592,11 @@ public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate)
// ==> B has to restart
// C has update, A -> C, B -> C, B restarting
// ==> A has to restart even though it does not restart on no-effect edits
- if (allowPartialUpdate)
- {
- AssertEx.Equal(
- [
- "A: [D]",
- "B: [D]",
- ], Inspect(projectsToRestart));
- }
- else
- {
- AssertEx.Equal(
- [
- "A: []",
- "B: [D]",
- ], Inspect(projectsToRestart));
- }
+ AssertEx.Equal(
+ [
+ "A: [D]",
+ "B: [D]",
+ ], Inspect(projectsToRestart));
AssertEx.SetEqual([a, b], projectsToRebuild);
}
diff --git a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs
index 40e1521986f9d..0001339a8a846 100644
--- a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs
+++ b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs
@@ -176,9 +176,9 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile);
- var runningProjects1 = new Dictionary
+ var runningProjects1 = new Dictionary
{
- { project.Id, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = true, AllowPartialUpdate = true} }
+ { project.Id, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = true } }
}.ToImmutableDictionary();
mockEncService.EmitSolutionUpdateImpl = (solution, runningProjects, activeStatementSpanProvider) =>
diff --git a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs
index 028161f3b8df0..8b1c1e1c1c1ab 100644
--- a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs
+++ b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs
@@ -37,15 +37,9 @@ private static Task GetCommittedDocumentTextAsync(WatchHotReloadServ
.GetRequiredDocument(documentId)
.GetTextAsync();
- [Theory]
- [CombinatorialData]
- public async Task Test(bool requireCommit)
+ [Fact]
+ public async Task Test()
{
- // See https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs#L125
-
- // Note that xUnit does not run test case of a theory in parallel, so we can set global state here:
- WatchHotReloadService.RequireCommit = requireCommit;
-
var source1 = "class C { void M() { System.Console.WriteLine(1); } }";
var source2 = "class C { void M() { System.Console.WriteLine(2); /*2*/} }";
var source3 = "class C { void M() { System.Console.WriteLine(2); /*3*/} }";
@@ -89,10 +83,7 @@ public async Task Test(bool requireCommit)
Assert.Equal(1, result.ProjectUpdates.Length);
AssertEx.Equal([0x02000002], result.ProjectUpdates[0].UpdatedTypes);
- if (requireCommit)
- {
- hotReload.CommitUpdate();
- }
+ hotReload.CommitUpdate();
var updatedText = await GetCommittedDocumentTextAsync(hotReload, documentIdA);
Assert.Equal(source2, updatedText.ToString());
@@ -124,12 +115,9 @@ public async Task Test(bool requireCommit)
AssertEx.SetEqual(["P"], result.ProjectsToRestart.Select(p => solution.GetRequiredProject(p.Key).Name));
AssertEx.SetEqual(["P"], result.ProjectsToRebuild.Select(p => solution.GetRequiredProject(p).Name));
- if (requireCommit)
- {
- // Emulate the user making choice to not restart.
- // dotnet-watch then waits until Ctrl+R forces restart.
- hotReload.DiscardUpdate();
- }
+ // Emulate the user making choice to not restart.
+ // dotnet-watch then waits until Ctrl+R forces restart.
+ hotReload.DiscardUpdate();
updatedText = await GetCommittedDocumentTextAsync(hotReload, documentIdA);
Assert.Equal(source3, updatedText.ToString());
diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs
index 308fc11242d2c..fe27c3d83ce62 100644
--- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs
+++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs
@@ -219,28 +219,14 @@ internal static void DiscardSolutionUpdate(DebuggingSession session)
internal static void EndDebuggingSession(DebuggingSession session)
=> session.EndSession(out _);
- internal static async Task<(ModuleUpdates updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync(
- DebuggingSession session,
- Solution solution,
- ActiveStatementSpanProvider? activeStatementSpanProvider = null)
- {
- var runningProjects = solution.ProjectIds.ToImmutableDictionary(
- keySelector: id => id,
- elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = false, RestartWhenChangesHaveNoEffect = false });
-
- var result = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None);
- return (result.ModuleUpdates, result.Diagnostics.OrderBy(d => d.ProjectId.DebugName).ToImmutableArray().ToDiagnosticData(solution));
- }
-
internal static async ValueTask EmitSolutionUpdateAsync(
DebuggingSession session,
Solution solution,
- bool allowPartialUpdate,
ActiveStatementSpanProvider? activeStatementSpanProvider = null)
{
var runningProjects = solution.ProjectIds.ToImmutableDictionary(
keySelector: id => id,
- elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = allowPartialUpdate, RestartWhenChangesHaveNoEffect = false });
+ elementSelector: id => new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false });
var results = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None);
@@ -249,26 +235,14 @@ internal static async ValueTask EmitSolutionUpdateAsy
Assert.Equal(hasTransientError, results.ProjectsToRestart.Any());
Assert.Equal(hasTransientError, results.ProjectsToRebuild.Any());
- if (!allowPartialUpdate)
- {
- // No updates should be produced if transient error is reported:
- Assert.True(!hasTransientError || results.ModuleUpdates.Updates.IsEmpty);
- }
-
return results;
}
- internal static IEnumerable InspectDiagnostics(ImmutableArray actual)
- => actual.Select(InspectDiagnostic);
-
- internal static string InspectDiagnostic(DiagnosticData diagnostic)
- => $"{(string.IsNullOrWhiteSpace(diagnostic.DataLocation.MappedFileSpan.Path) ? diagnostic.ProjectId.ToString() : diagnostic.DataLocation.MappedFileSpan.ToString())}: {diagnostic.Severity} {diagnostic.Id}: {diagnostic.Message}";
-
internal static IEnumerable InspectDiagnostics(ImmutableArray actual)
- => actual.SelectMany(pd => pd.Diagnostics.Select(d => $"{pd.ProjectId.DebugName}: {InspectDiagnostic(d)}"));
+ => actual.SelectMany(pd => pd.Diagnostics.Select(d => $"{pd.ProjectId.DebugName}: {InspectDiagnostic(d)}")).Order();
internal static IEnumerable InspectDiagnostics(ImmutableArray<(ProjectId project, ImmutableArray diagnostics)> diagnostics)
- => diagnostics.SelectMany(pd => pd.diagnostics.Select(d => $"{pd.project.DebugName}: {InspectDiagnostic(d)}"));
+ => diagnostics.SelectMany(pd => pd.diagnostics.Select(d => $"{pd.project.DebugName}: {InspectDiagnostic(d)}")).Order();
internal static string InspectDiagnostic(Diagnostic actual)
=> $"{Inspect(actual.Location)}: {actual.Severity} {actual.Id}: {actual.GetMessage()}";
diff --git a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs
index 4a90b5ecf172f..0d384f2ac1eee 100644
--- a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs
+++ b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs
@@ -23,10 +23,9 @@ internal sealed class MockEditAndContinueService() : IEditAndContinueService
public Func, bool, bool, DebuggingSessionId>? StartDebuggingSessionImpl;
public Action? EndDebuggingSessionImpl;
- public Func, ActiveStatementSpanProvider, EmitSolutionUpdateResults>? EmitSolutionUpdateImpl;
+ public Func, ActiveStatementSpanProvider, EmitSolutionUpdateResults>? EmitSolutionUpdateImpl;
public Action? OnSourceFileUpdatedImpl;
public Action? CommitSolutionUpdateImpl;
- public Action>? UpdateBaselinesImpl;
public Action? BreakStateOrCapabilitiesChangedImpl;
public Action? DiscardSolutionUpdateImpl;
public Func>? GetDocumentDiagnosticsImpl;
@@ -40,10 +39,7 @@ public void CommitSolutionUpdate(DebuggingSessionId sessionId)
public void DiscardSolutionUpdate(DebuggingSessionId sessionId)
=> DiscardSolutionUpdateImpl?.Invoke();
- public void UpdateBaselines(DebuggingSessionId sessionId, Solution solution, ImmutableArray rebuiltProjects)
- => UpdateBaselinesImpl?.Invoke(solution, rebuiltProjects);
-
- public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken)
+ public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken)
=> new((EmitSolutionUpdateImpl ?? throw new NotImplementedException()).Invoke(solution, runningProjects, activeStatementSpanProvider));
public void EndDebuggingSession(DebuggingSessionId sessionId)
diff --git a/src/Features/VisualBasic/Portable/DocumentationComments/VisualBasicDocumentationCommentFormattingService.vb b/src/Features/VisualBasic/Portable/DocumentationComments/VisualBasicDocumentationCommentFormattingService.vb
index ec71234351301..7fdc4e03d2cbe 100644
--- a/src/Features/VisualBasic/Portable/DocumentationComments/VisualBasicDocumentationCommentFormattingService.vb
+++ b/src/Features/VisualBasic/Portable/DocumentationComments/VisualBasicDocumentationCommentFormattingService.vb
@@ -8,9 +8,8 @@ Imports Microsoft.CodeAnalysis.DocumentationComments
Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.VisualBasic.DocumentationComments
-
- Friend Class VisualBasicDocumentationCommentFormattingService
+ Friend NotInheritable Class VisualBasicDocumentationCommentFormattingService
Inherits AbstractDocumentationCommentFormattingService
diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb
index 2f45f4a385428..95de2d1babd9a 100644
--- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb
+++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb
@@ -11,7 +11,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices
Partial Friend Class VisualBasicSymbolDisplayService
- Protected Class SymbolDescriptionBuilder
+ Protected NotInheritable Class SymbolDescriptionBuilder
Inherits AbstractSymbolDescriptionBuilder
Private Shared ReadOnly s_minimallyQualifiedFormat As SymbolDisplayFormat = SymbolDisplayFormat.MinimallyQualifiedFormat _
@@ -172,6 +172,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices
End If
End Sub
+ Protected Overrides Function GetCommentText(trivia As SyntaxTrivia) As String
+ Return trivia.ToFullString().Substring(1)
+ End Function
+
Protected Overrides ReadOnly Property MinimallyQualifiedFormat As SymbolDisplayFormat = s_minimallyQualifiedFormat
Protected Overrides ReadOnly Property MinimallyQualifiedFormatWithConstants As SymbolDisplayFormat = s_minimallyQualifiedFormatWithConstants
diff --git a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicQuickInfoService.vb b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicQuickInfoService.vb
index e245c1d97cf45..c827e6ba97b9c 100644
--- a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicQuickInfoService.vb
+++ b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicQuickInfoService.vb
@@ -9,7 +9,7 @@ Imports Microsoft.CodeAnalysis.QuickInfo
Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo
- Friend Class VisualBasicQuickInfoServiceFactory
+ Friend NotInheritable Class VisualBasicQuickInfoServiceFactory
Implements ILanguageServiceFactory
@@ -21,13 +21,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo
Return New VisualBasicQuickInfoService(languageServices.LanguageServices)
End Function
- End Class
-
- Friend Class VisualBasicQuickInfoService
- Inherits QuickInfoServiceWithProviders
+ Private NotInheritable Class VisualBasicQuickInfoService
+ Inherits QuickInfoServiceWithProviders
- Public Sub New(services As Host.LanguageServices)
- MyBase.New(services)
- End Sub
+ Public Sub New(services As LanguageServices)
+ MyBase.New(services)
+ End Sub
+ End Class
End Class
End Namespace
diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj
index c486450b640e0..9098badd0229d 100644
--- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj
+++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj
@@ -8,6 +8,9 @@
true
true
true
+
+
+ $(DefineConstants);MICROSOFT_CODEANALYSIS_THREADING_NO_CHANNELS
true
@@ -55,7 +58,6 @@
add an explicit reference to System.Threading.Tasks.Dataflow to ensure that the correct
BindingRedirect is included in the app.config of InteractiveHost.exe -->
-
diff --git a/src/LanguageServer/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/LanguageServer/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs
index 137716f79981a..1fe4074b6a56f 100644
--- a/src/LanguageServer/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs
+++ b/src/LanguageServer/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs
@@ -20,13 +20,12 @@ internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource sourc
public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken)
{
var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false);
- var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, textDocument)).ToImmutableArray();
+ var result = diagnostics.SelectAsArray(diagnostic => DiagnosticData.Create(diagnostic, textDocument));
return result;
}
public TextDocumentIdentifier? GetDocumentIdentifier() => new() { DocumentUri = textDocument.GetURI() };
public ProjectOrDocumentId GetId() => new(textDocument.Id);
public Project GetProject() => textDocument.Project;
- public bool IsLiveSource() => true;
public string ToDisplayString() => $"{this.GetType().Name}: {textDocument.FilePath ?? textDocument.Name} in {textDocument.Project.Name}";
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/FileBasedProgramsWorkspaceTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/FileBasedProgramsWorkspaceTests.cs
new file mode 100644
index 0000000000000..fd85df0abbd49
--- /dev/null
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/FileBasedProgramsWorkspaceTests.cs
@@ -0,0 +1,73 @@
+// 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.HostWorkspace;
+using Microsoft.CodeAnalysis.LanguageServer.UnitTests.Miscellaneous;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Shared.TestHooks;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.UnitTests;
+using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.Composition;
+using Roslyn.Test.Utilities;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests;
+
+public sealed class FileBasedProgramsWorkspaceTests : AbstractLspMiscellaneousFilesWorkspaceTests, IDisposable
+{
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly TestOutputLoggerProvider _loggerProvider;
+ private readonly TempRoot _tempRoot;
+ private readonly TempDirectory _mefCacheDirectory;
+
+ public FileBasedProgramsWorkspaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+ {
+ _loggerProvider = new TestOutputLoggerProvider(testOutputHelper);
+ _loggerFactory = new LoggerFactory([_loggerProvider]);
+ _tempRoot = new();
+ _mefCacheDirectory = _tempRoot.CreateDirectory();
+ }
+
+ public void Dispose()
+ {
+ _tempRoot.Dispose();
+ _loggerProvider.Dispose();
+ }
+
+ protected override async ValueTask CreateExportProviderAsync()
+ {
+ AsynchronousOperationListenerProvider.Enable(enable: true);
+
+ var (exportProvider, _) = await LanguageServerTestComposition.CreateExportProviderAsync(
+ _loggerFactory,
+ includeDevKitComponents: false,
+ cacheDirectory: _mefCacheDirectory.Path,
+ extensionPaths: []);
+
+ return exportProvider;
+ }
+
+ private protected override async ValueTask AddDocumentAsync(TestLspServer testLspServer, string filePath, string content)
+ {
+ // For the file-based programs, we want to put them in the real workspace via the real host service
+ var workspaceFactory = testLspServer.TestWorkspace.ExportProvider.GetExportedValue();
+ var project = await workspaceFactory.HostProjectFactory.CreateAndAddToWorkspaceAsync(
+ Guid.NewGuid().ToString(),
+ LanguageNames.CSharp,
+ new ProjectSystemProjectCreationInfo { AssemblyName = Guid.NewGuid().ToString() },
+ workspaceFactory.ProjectSystemHostInfo);
+
+ project.AddSourceFile(filePath);
+
+ return workspaceFactory.HostWorkspace.CurrentSolution.GetRequiredProject(project.Id).Documents.Single();
+ }
+
+ private protected override Workspace GetHostWorkspace(TestLspServer testLspServer)
+ {
+ var workspaceFactory = testLspServer.TestWorkspace.ExportProvider.GetExportedValue();
+ return workspaceFactory.HostWorkspace;
+ }
+}
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 0f61561851842..abc6163b26998 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,14 +15,10 @@
-
-
-
-
-
+
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
index bce854d90bd4c..d0fc5fe098665 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
@@ -31,7 +31,7 @@ internal sealed class LanguageServerTestComposition
var extensionManager = ExtensionAssemblyManager.Create(serverConfiguration, loggerFactory);
var assemblyLoader = new CustomExportAssemblyLoader(extensionManager, loggerFactory);
- var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory, CancellationToken.None);
+ var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(TestPaths.GetLanguageServerDirectory(), extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory, CancellationToken.None);
exportProvider.GetExportedValue().InitializeConfiguration(serverConfiguration);
return (exportProvider, assemblyLoader);
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestPaths.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestPaths.cs
index 5b392926b53c4..e925ef9184846 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestPaths.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestPaths.cs
@@ -21,6 +21,8 @@ public static string GetDevKitExtensionPath()
private const string LanguageServerSubdirectory = "RoslynLSP";
private const string LanguageServerAssemblyFileName = "Microsoft.CodeAnalysis.LanguageServer.dll";
+ public static string GetLanguageServerDirectory()
+ => Path.Combine(AppContext.BaseDirectory, LanguageServerSubdirectory);
public static string GetLanguageServerPath()
- => Path.Combine(AppContext.BaseDirectory, LanguageServerSubdirectory, LanguageServerAssemblyFileName);
+ => Path.Combine(GetLanguageServerDirectory(), LanguageServerAssemblyFileName);
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs
index 01f6754bd709f..498874b318996 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs
@@ -6,7 +6,6 @@
using Microsoft.CodeAnalysis.Features.Workspaces;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry;
-using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.ProjectSystem;
@@ -25,13 +24,11 @@ internal sealed class FileBasedProgramsProjectSystem : LanguageServerProjectLoad
{
private readonly ILspServices _lspServices;
private readonly ILogger _logger;
- private readonly IMetadataAsSourceFileService _metadataAsSourceFileService;
private readonly VirtualProjectXmlProvider _projectXmlProvider;
private readonly LanguageServerWorkspaceFactory _workspaceFactory;
public FileBasedProgramsProjectSystem(
ILspServices lspServices,
- IMetadataAsSourceFileService metadataAsSourceFileService,
VirtualProjectXmlProvider projectXmlProvider,
LanguageServerWorkspaceFactory workspaceFactory,
IFileChangeWatcher fileChangeWatcher,
@@ -54,7 +51,6 @@ public FileBasedProgramsProjectSystem(
{
_lspServices = lspServices;
_logger = loggerFactory.CreateLogger();
- _metadataAsSourceFileService = metadataAsSourceFileService;
_projectXmlProvider = projectXmlProvider;
_workspaceFactory = workspaceFactory;
}
@@ -74,14 +70,6 @@ public async ValueTask IsMiscellaneousFilesDocumentAsync(TextDocument docu
{
var documentFilePath = GetDocumentFilePath(uri);
- // https://github.com/dotnet/roslyn/issues/78421: MetadataAsSource should be its own workspace
- if (_metadataAsSourceFileService.TryAddDocumentToWorkspace(documentFilePath, documentText.Container, out var documentId))
- {
- var metadataWorkspace = _metadataAsSourceFileService.TryGetWorkspace();
- Contract.ThrowIfNull(metadataWorkspace);
- return metadataWorkspace.CurrentSolution.GetRequiredDocument(documentId);
- }
-
var primordialDoc = AddPrimordialDocument(uri, documentText, languageId);
Contract.ThrowIfNull(primordialDoc.FilePath);
@@ -120,14 +108,9 @@ TextDocument AddPrimordialDocument(DocumentUri uri, SourceText documentText, str
}
}
- public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool removeFromMetadataWorkspace)
+ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri)
{
var documentPath = GetDocumentFilePath(uri);
- if (removeFromMetadataWorkspace && _metadataAsSourceFileService.TryRemoveDocumentFromWorkspace(documentPath))
- {
- return;
- }
-
await UnloadProjectAsync(documentPath);
}
@@ -158,7 +141,7 @@ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool
var isFileBasedProgram = VirtualProjectXmlProvider.IsFileBasedProgram(documentPath, textAndVersion.Text);
const BuildHostProcessKind buildHostKind = BuildHostProcessKind.NetCore;
- var buildHost = await buildHostProcessManager.GetBuildHostAsync(buildHostKind, cancellationToken);
+ var buildHost = await buildHostProcessManager.GetBuildHostAsync(buildHostKind, virtualProjectPath, dotnetPath: null, cancellationToken);
var loadedFile = await buildHost.LoadProjectAsync(virtualProjectPath, virtualProjectContent, languageName: LanguageNames.CSharp, cancellationToken);
return new RemoteProjectLoadResult(
@@ -170,4 +153,9 @@ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool
Preferred: buildHostKind,
Actual: buildHostKind);
}
+
+ protected override async ValueTask OnProjectUnloadedAsync(string projectFilePath)
+ {
+ await _projectXmlProvider.UnloadCachedDiagnosticsAsync(projectFilePath);
+ }
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsWorkspaceProviderFactory.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsWorkspaceProviderFactory.cs
index 3754d38086a01..56dc2a9a3b657 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsWorkspaceProviderFactory.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsWorkspaceProviderFactory.cs
@@ -8,7 +8,6 @@
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry;
-using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.ProjectSystem;
@@ -19,7 +18,7 @@
namespace Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms;
///
-/// Service to create instances.
+/// Service to create instances.
/// This is not exported as a as it requires
/// special base language server dependencies such as the
///
@@ -27,7 +26,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms;
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class FileBasedProgramsWorkspaceProviderFactory(
- IMetadataAsSourceFileService metadataAsSourceFileService,
VirtualProjectXmlProvider projectXmlProvider,
LanguageServerWorkspaceFactory workspaceFactory,
IFileChangeWatcher fileChangeWatcher,
@@ -42,7 +40,6 @@ public ILspMiscellaneousFilesWorkspaceProvider CreateLspMiscellaneousFilesWorksp
{
return new FileBasedProgramsProjectSystem(
lspServices,
- metadataAsSourceFileService,
projectXmlProvider,
workspaceFactory,
fileChangeWatcher,
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/RunApiModels.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/RunApiModels.cs
index 5d59892adcdbd..e1605dd25d082 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/RunApiModels.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/RunApiModels.cs
@@ -43,7 +43,8 @@ public sealed class Project : RunApiOutput
public required ImmutableArray Diagnostics { get; init; }
}
}
- internal sealed class SimpleDiagnostic
+
+ internal sealed record SimpleDiagnostic
{
public required Position Location { get; init; }
public required string Message { get; init; }
@@ -51,20 +52,37 @@ internal sealed class SimpleDiagnostic
///
/// An adapter of that ensures we JSON-serialize only the necessary fields.
///
- public readonly struct Position
+ public readonly record struct Position
{
public string Path { get; init; }
- public LinePositionSpan Span { get; init; }
+ public LinePositionSpanInternal Span { get; init; }
+ }
+ }
- public static implicit operator Position(FileLinePositionSpan fileLinePositionSpan) => new()
- {
- Path = fileLinePositionSpan.Path,
- Span = fileLinePositionSpan.Span,
- };
+ internal record struct LinePositionInternal
+ {
+ public int Line { get; init; }
+ public int Character { get; init; }
+ }
+
+ ///
+ /// Workaround for inability to deserialize directly to .
+ ///
+ internal record struct LinePositionSpanInternal
+ {
+ public LinePositionInternal Start { get; init; }
+ public LinePositionInternal End { get; init; }
+
+ public LinePositionSpan ToLinePositionSpan()
+ {
+ return new LinePositionSpan(
+ start: new LinePosition(Start.Line, Start.Character),
+ end: new LinePosition(End.Line, End.Character));
}
}
[JsonSerializable(typeof(RunApiInput))]
[JsonSerializable(typeof(RunApiOutput))]
+ [JsonSerializable(typeof(LinePositionSpanInternal))]
internal partial class RunFileApiJsonSerializerContext : JsonSerializerContext;
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlDiagnosticSourceProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlDiagnosticSourceProvider.cs
new file mode 100644
index 0000000000000..fad1ea38ae007
--- /dev/null
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlDiagnosticSourceProvider.cs
@@ -0,0 +1,91 @@
+// 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.Composition;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.LanguageServer.Handler;
+using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.LanguageServer.Protocol;
+
+namespace Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms;
+
+[Export(typeof(IDiagnosticSourceProvider)), Shared]
+[method: ImportingConstructor]
+[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+internal sealed class VirtualProjectXmlDiagnosticSourceProvider(VirtualProjectXmlProvider virtualProjectXmlProvider) : IDiagnosticSourceProvider
+{
+ public const string FileBasedPrograms = nameof(FileBasedPrograms);
+
+ public bool IsDocument => true;
+ public string Name => FileBasedPrograms;
+
+ public bool IsEnabled(ClientCapabilities clientCapabilities) => true;
+
+ public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken)
+ {
+ ImmutableArray sources = context.Document is null
+ ? []
+ : [new VirtualProjectXmlDiagnosticSource(context.Document, virtualProjectXmlProvider)];
+
+ return ValueTask.FromResult(sources);
+ }
+
+ private sealed class VirtualProjectXmlDiagnosticSource(Document document, VirtualProjectXmlProvider virtualProjectXmlProvider) : IDiagnosticSource
+ {
+ public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(document.FilePath))
+ return [];
+
+ var simpleDiagnostics = await virtualProjectXmlProvider.GetCachedDiagnosticsAsync(document.FilePath, cancellationToken);
+ if (simpleDiagnostics.IsDefaultOrEmpty)
+ return [];
+
+ var diagnosticDatas = new FixedSizeArrayBuilder(simpleDiagnostics.Length);
+ foreach (var simpleDiagnostic in simpleDiagnostics)
+ {
+ var location = new FileLinePositionSpan(simpleDiagnostic.Location.Path, simpleDiagnostic.Location.Span.ToLinePositionSpan());
+ var diagnosticData = new DiagnosticData(
+ id: FileBasedPrograms,
+ category: FileBasedPrograms,
+ message: simpleDiagnostic.Message,
+ severity: DiagnosticSeverity.Error,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ // Warning level 0 is used as a placeholder when the diagnostic has error severity
+ warningLevel: 0,
+ // Mark these diagnostics as build errors so they can be overridden by diagnostics from an explicit build.
+ customTags: [WellKnownDiagnosticTags.Build],
+ properties: ImmutableDictionary.Empty,
+ projectId: document.Project.Id,
+ location: new DiagnosticDataLocation(location, document.Id)
+ );
+ diagnosticDatas.Add(diagnosticData);
+ }
+ return diagnosticDatas.MoveToImmutable();
+ }
+
+ public TextDocumentIdentifier? GetDocumentIdentifier()
+ {
+ return !string.IsNullOrEmpty(document.FilePath)
+ ? new VSTextDocumentIdentifier { ProjectContext = ProtocolConversions.ProjectToProjectContext(document.Project), DocumentUri = document.GetURI() }
+ : null;
+ }
+
+ public ProjectOrDocumentId GetId()
+ {
+ return new ProjectOrDocumentId(document.Id);
+ }
+
+ public Project GetProject()
+ {
+ return document.Project;
+ }
+
+ public string ToDisplayString() => nameof(VirtualProjectXmlDiagnosticSource);
+ }
+}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs
index a5143086c4046..a5fe8cd5606bf 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs
@@ -11,6 +11,7 @@
using System.Text.Json;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
@@ -21,9 +22,63 @@ namespace Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms;
[Export(typeof(VirtualProjectXmlProvider)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
-internal class VirtualProjectXmlProvider(DotnetCliHelper dotnetCliHelper)
+internal class VirtualProjectXmlProvider(IDiagnosticsRefresher diagnosticRefresher, DotnetCliHelper dotnetCliHelper)
{
+ private readonly SemaphoreSlim _gate = new(initialCount: 1);
+ private readonly Dictionary> _diagnosticsByFilePath = [];
+
+ internal async ValueTask> GetCachedDiagnosticsAsync(string path, CancellationToken cancellationToken)
+ {
+ using (await _gate.DisposableWaitAsync(cancellationToken))
+ {
+ _diagnosticsByFilePath.TryGetValue(path, out var diagnostics);
+ return diagnostics;
+ }
+ }
+
+ internal async ValueTask UnloadCachedDiagnosticsAsync(string path)
+ {
+ using (await _gate.DisposableWaitAsync(CancellationToken.None))
+ {
+ _diagnosticsByFilePath.Remove(path);
+ }
+ }
+
internal async Task<(string VirtualProjectXml, ImmutableArray Diagnostics)?> GetVirtualProjectContentAsync(string documentFilePath, ILogger logger, CancellationToken cancellationToken)
+ {
+ var result = await GetVirtualProjectContentImplAsync(documentFilePath, logger, cancellationToken);
+ if (result is { } project)
+ {
+ using (await _gate.DisposableWaitAsync(cancellationToken))
+ {
+ _diagnosticsByFilePath.TryGetValue(documentFilePath, out var previousCachedDiagnostics);
+ _diagnosticsByFilePath[documentFilePath] = project.Diagnostics;
+
+ // check for difference, and signal to host to update if so.
+ if (previousCachedDiagnostics.IsDefault || !project.Diagnostics.SequenceEqual(previousCachedDiagnostics))
+ diagnosticRefresher.RequestWorkspaceRefresh();
+ }
+ }
+ else
+ {
+ using (await _gate.DisposableWaitAsync(CancellationToken.None))
+ {
+ if (_diagnosticsByFilePath.TryGetValue(documentFilePath, out var diagnostics))
+ {
+ _diagnosticsByFilePath.Remove(documentFilePath);
+ if (!diagnostics.IsDefaultOrEmpty)
+ {
+ // diagnostics have changed from "non-empty" to "unloaded". refresh.
+ diagnosticRefresher.RequestWorkspaceRefresh();
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private async Task<(string VirtualProjectXml, ImmutableArray Diagnostics)?> GetVirtualProjectContentImplAsync(string documentFilePath, ILogger logger, CancellationToken cancellationToken)
{
var workingDirectory = Path.GetDirectoryName(documentFilePath);
var process = dotnetCliHelper.Run(["run-api"], workingDirectory, shouldLocalizeOutput: true, keepStandardInputOpen: true);
@@ -70,7 +125,6 @@ internal class VirtualProjectXmlProvider(DotnetCliHelper dotnetCliHelper)
if (response is RunApiOutput.Project project)
{
-
return (project.Content, project.Diagnostics);
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs
index 5dbc51ba3f3de..57d0d90555c93 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs
@@ -181,6 +181,14 @@ protected sealed record RemoteProjectLoadResult(RemoteProjectFile ProjectFile, P
protected abstract Task TryLoadProjectInMSBuildHostAsync(
BuildHostProcessManager buildHostProcessManager, string projectPath, CancellationToken cancellationToken);
+ /// Called after a project is unloaded to allow the subtype to clean up any resources associated with the project.
+ ///
+ /// Note that this refers to unloading of the project on the project-system level.
+ /// So, for example, changing the target frameworks of a project, or transitioning between
+ /// "file-based program" and "true miscellaneous file", will not result in this being called.
+ ///
+ protected abstract ValueTask OnProjectUnloadedAsync(string projectFilePath);
+
/// True if the project needs a NuGet restore, false otherwise.
private async Task ReloadProjectAsync(ProjectToLoad projectToLoad, ToastErrorReporter toastErrorReporter, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken)
{
@@ -445,5 +453,7 @@ protected async ValueTask UnloadProjectAsync(string projectPath)
throw ExceptionUtilities.UnexpectedValue(loadState);
}
}
+
+ await OnProjectUnloadedAsync(projectPath);
}
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs
index 7e86d7845f54d..9d52bb416242f 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs
@@ -92,4 +92,10 @@ public async Task OpenProjectsAsync(ImmutableArray projectFilePaths)
var loadedFile = await buildHost.LoadProjectFileAsync(projectPath, languageName, cancellationToken);
return new RemoteProjectLoadResult(loadedFile, _hostProjectFactory, IsMiscellaneousFile: false, preferredBuildHostKind, actualBuildHostKind);
}
+
+ protected override ValueTask OnProjectUnloadedAsync(string projectFilePath)
+ {
+ // Nothing else to unload for ordinary projects.
+ return ValueTask.CompletedTask;
+ }
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs
index 01ec48364d582..7dcc9c22864cc 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs
@@ -154,7 +154,7 @@ public void Dispose()
DocumentFileInfoComparer.Instance,
document => _projectSystemProject.AddSourceFile(document.FilePath, folders: document.Folders),
document => _projectSystemProject.RemoveSourceFile(document.FilePath),
- "Project {0} now has {1} source file(s).");
+ "Project {0} now has {1} source file(s). ({2} added, {3} removed.)");
var relativePathResolver = new RelativePathResolver(commandLineArguments.ReferencePaths, commandLineArguments.BaseDirectory);
var metadataReferences = commandLineArguments.MetadataReferences.Select(cr =>
@@ -167,7 +167,7 @@ public void Dispose()
FileUtilities.ResolveRelativePath(cr.Reference, commandLineArguments.BaseDirectory);
return absolutePath is not null ? new CommandLineReference(absolutePath, cr.Properties) : default;
- }).Where(static cr => cr.Reference is not null).ToImmutableArray();
+ }).WhereAsArray(static cr => cr.Reference is not null);
UpdateProjectSystemProjectCollection(
metadataReferences,
@@ -175,7 +175,7 @@ public void Dispose()
EqualityComparer.Default, // CommandLineReference already implements equality
reference => _projectSystemProject.AddMetadataReference(reference.Reference, reference.Properties),
reference => _projectSystemProject.RemoveMetadataReference(reference.Reference, reference.Properties),
- "Project {0} now has {1} reference(s).");
+ "Project {0} now has {1} reference(s). ({2} added, {3} removed.)");
// Now that we've updated it hold onto the old list of references so we can remove them if there's a later update
_mostRecentMetadataReferences = metadataReferences;
@@ -185,7 +185,7 @@ public void Dispose()
// Note that unlike regular references, we do not resolve these with the relative path resolver that searches reference paths
var absolutePath = FileUtilities.ResolveRelativePath(cr.FilePath, commandLineArguments.BaseDirectory);
return absolutePath is not null ? new CommandLineAnalyzerReference(absolutePath) : default;
- }).Where(static cr => cr.FilePath is not null).ToImmutableArray();
+ }).WhereAsArray(static cr => cr.FilePath is not null);
UpdateProjectSystemProjectCollection(
analyzerReferences,
@@ -193,7 +193,7 @@ public void Dispose()
EqualityComparer.Default, // CommandLineAnalyzerReference already implements equality
reference => _projectSystemProject.AddAnalyzerReference(reference.FilePath),
reference => _projectSystemProject.RemoveAnalyzerReference(reference.FilePath),
- "Project {0} now has {1} analyzer reference(s).");
+ "Project {0} now has {1} analyzer reference(s). ({2} added, {3} removed.)");
_mostRecentAnalyzerReferences = analyzerReferences;
@@ -203,7 +203,7 @@ public void Dispose()
DocumentFileInfoComparer.Instance,
document => _projectSystemProject.AddAdditionalFile(document.FilePath, folders: document.Folders),
document => _projectSystemProject.RemoveAdditionalFile(document.FilePath),
- "Project {0} now has {1} additional file(s).");
+ "Project {0} now has {1} additional file(s). ({2} added, {3} removed.)");
UpdateProjectSystemProjectCollection(
newProjectInfo.AnalyzerConfigDocuments,
@@ -211,7 +211,7 @@ public void Dispose()
DocumentFileInfoComparer.Instance,
document => _projectSystemProject.AddAnalyzerConfigFile(document.FilePath),
document => _projectSystemProject.RemoveAnalyzerConfigFile(document.FilePath),
- "Project {0} now has {1} analyzer config file(s).");
+ "Project {0} now has {1} analyzer config file(s). ({2} added, {3} removed.)");
UpdateProjectSystemProjectCollection(
newProjectInfo.AdditionalDocuments.Where(TreatAsIsDynamicFile),
@@ -219,7 +219,7 @@ public void Dispose()
DocumentFileInfoComparer.Instance,
document => _projectSystemProject.AddDynamicSourceFile(document.FilePath, folders: []),
document => _projectSystemProject.RemoveDynamicSourceFile(document.FilePath),
- "Project {0} now has {1} dynamic file(s).");
+ "Project {0} now has {1} dynamic file(s). ({2} added, {3} removed.)");
WatchProjectAssetsFile(newProjectInfo);
@@ -243,33 +243,32 @@ public void Dispose()
var telemetryInfo = new ProjectLoadTelemetryReporter.TelemetryInfo { OutputKind = outputKind, MetadataReferences = metadataReferences };
return (telemetryInfo, needsRestore);
- // logMessage should be a string with two placeholders; the first is the project name, the second is the number of items.
+ // logMessage must have 4 placeholders: project name, number of items, added items count, and removed items count.
void UpdateProjectSystemProjectCollection(IEnumerable loadedCollection, IEnumerable? oldLoadedCollection, IEqualityComparer comparer, Action addItem, Action removeItem, string logMessage)
{
var newItems = new HashSet(loadedCollection, comparer);
- var oldItems = new HashSet(comparer);
- var oldItemsCount = oldItems.Count;
+ var oldItems = new HashSet(oldLoadedCollection ?? [], comparer);
- if (oldLoadedCollection != null)
- {
- foreach (var item in oldLoadedCollection)
- oldItems.Add(item);
- }
+ var addedCount = 0;
foreach (var newItem in newItems)
{
// If oldItems already has this, we don't need to add it again. We'll remove it, and what is left in oldItems is stuff to remove
if (!oldItems.Remove(newItem))
+ {
addItem(newItem);
+ addedCount++;
+ }
}
+ var removedCount = oldItems.Count;
foreach (var oldItem in oldItems)
{
removeItem(oldItem);
}
- if (newItems.Count != oldItemsCount)
- logger.LogTrace(logMessage, projectFullPathWithTargetFramework, newItems.Count);
+ if (addedCount != 0 || removedCount != 0)
+ logger.LogTrace(logMessage, projectFullPathWithTargetFramework, newItems.Count, addedCount, removedCount);
}
void WatchProjectAssetsFile(ProjectFileInfo currentProjectInfo)
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectTelemetry/ProjectLoadTelemetryReporter.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectTelemetry/ProjectLoadTelemetryReporter.cs
index 086f2f35824e5..eb69562f72a75 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectTelemetry/ProjectLoadTelemetryReporter.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectTelemetry/ProjectLoadTelemetryReporter.cs
@@ -103,8 +103,7 @@ private static ImmutableDictionary GetUniqueHashedFileExtensionsAnd
var sourceFiles = projectFileInfo.Documents
.Concat(projectFileInfo.AdditionalDocuments)
.Concat(projectFileInfo.AnalyzerConfigDocuments)
- .Where(d => !d.IsGenerated)
- .SelectAsArray(d => d.FilePath);
+ .SelectAsArray(d => !d.IsGenerated, d => d.FilePath);
var allFiles = contentFiles.Concat(sourceFiles);
var fileCounts = new Dictionary();
foreach (var file in allFiles)
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs
index 82eb3cf9d89d7..64623a1dc24b4 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs
@@ -31,6 +31,7 @@ private LanguageServerExportProviderBuilder(
}
public static async Task CreateExportProviderAsync(
+ string baseDirectory,
ExtensionAssemblyManager extensionManager,
IAssemblyLoader assemblyLoader,
string? devKitDependencyPath,
@@ -38,8 +39,6 @@ public static async Task CreateExportProviderAsync(
ILoggerFactory loggerFactory,
CancellationToken cancellationToken)
{
- var baseDirectory = AppContext.BaseDirectory;
-
// Load any Roslyn assemblies from the extension directory
using var _ = ArrayBuilder.GetInstance(out var assemblyPathsBuilder);
@@ -90,13 +89,13 @@ protected override Task CreateExportProviderAsync(CancellationTo
return base.CreateExportProviderAsync(cancellationToken);
}
- protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions)
+ protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts)
{
// Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior.
var expectedErrorPartsSet = new HashSet(["CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor"]);
var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part));
- return hasUnexpectedErroredParts || !partDiscoveryExceptions.IsEmpty;
+ return hasUnexpectedErroredParts;
}
protected override Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, CancellationToken cancellationToken)
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
index 435c54303a75f..f37d5caf2900e 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
@@ -103,7 +103,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation
var cacheDirectory = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location)!, "cache");
- using var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory, cancellationToken);
+ using var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(AppContext.BaseDirectory, extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory, cancellationToken);
// LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to
// 'automatic' for now.
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/LspExtractClassOptionsService.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/LspExtractClassOptionsService.cs
index 5a297e8549fd2..d6e8d61270273 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/LspExtractClassOptionsService.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/LspExtractClassOptionsService.cs
@@ -32,7 +32,7 @@ internal sealed class LspExtractClassOptionsService() : IExtractClassOptionsServ
})
: selectedMembers;
- var memberAnalysisResults = symbolsToUse.Select(m => new ExtractClassMemberAnalysisResult(m, makeAbstract: false)).ToImmutableArray();
+ var memberAnalysisResults = symbolsToUse.SelectAsArray(m => new ExtractClassMemberAnalysisResult(m, makeAbstract: false));
const string name = "NewBaseType";
var extension = document.Project.Language == LanguageNames.CSharp ? ".cs" : ".vb";
return new(
diff --git a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs
similarity index 63%
rename from src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs
rename to src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs
index 7319ac6a09ab2..ab29e37ae4f5d 100644
--- a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs
+++ b/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs
@@ -2,9 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Shared.TestHooks;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Text;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Xunit;
@@ -13,19 +18,18 @@
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Miscellaneous;
-public sealed class LspMiscellaneousFilesWorkspaceTests : AbstractLanguageServerProtocolTests
+public abstract class AbstractLspMiscellaneousFilesWorkspaceTests : AbstractLanguageServerProtocolTests
{
- public LspMiscellaneousFilesWorkspaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+ public AbstractLspMiscellaneousFilesWorkspaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}
[Theory, CombinatorialData]
public async Task TestLooseFile_Opened(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file and make a request to verify it gets added to the misc workspace.
var looseFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(@"C:\SomeFile.cs");
@@ -45,14 +49,10 @@ void M()
[Theory, CombinatorialData]
public async Task TestLooseFile_Changed(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- var miscWorkspace = testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace();
- testLspServer.TestWorkspace.GetService().Register(miscWorkspace);
-
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
var looseFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(@"C:\SomeFile.cs");
@@ -61,7 +61,7 @@ public async Task TestLooseFile_Changed(bool mutatingLspWorkspace)
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
// Assert that the misc workspace contains the initial document.
- var miscWorkspaceText = await GetMiscellaneousDocument(testLspServer)!.GetTextAsync(CancellationToken.None);
+ var miscWorkspaceText = await (await GetMiscellaneousDocumentAsync(testLspServer))!.GetTextAsync(CancellationToken.None);
Assert.Empty(miscWorkspaceText.ToString());
// Make a text change to the loose file and verify requests appropriately reflect the changes.
@@ -79,17 +79,16 @@ void M()
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
// Assert that the misc workspace contains the updated document.
- miscWorkspaceText = await GetMiscellaneousDocument(testLspServer)!.GetTextAsync(CancellationToken.None);
+ miscWorkspaceText = await (await GetMiscellaneousDocumentAsync(testLspServer))!.GetTextAsync(CancellationToken.None);
Assert.Contains("class A", miscWorkspaceText.ToString());
}
[Theory, CombinatorialData]
public async Task TestLooseFile_Closed(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file and make a request to verify it gets added to the misc workspace.
var looseFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(@"C:\SomeFile.cs");
@@ -105,7 +104,7 @@ void M()
// Verify the loose file is removed from the misc workspace on close.
await testLspServer.CloseDocumentAsync(looseFileUri).ConfigureAwait(false);
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
}
[Theory, CombinatorialData]
@@ -122,13 +121,14 @@ void M()
""";
// Create a server that supports LSP misc files and verify no misc files present.
- await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ await using var testLspServer = await CreateTestLspServerAsync("", mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open a file that is part of a registered workspace and verify it is not present in the misc workspace.
- var fileInWorkspaceUri = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single().GetURI();
- await testLspServer.OpenDocumentAsync(fileInWorkspaceUri).ConfigureAwait(false);
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ var document = await AddDocumentAsync(testLspServer, "C:\\SomeFile.cs", markup).ConfigureAwait(false);
+ var fileInWorkspaceUri = document.GetURI();
+ await testLspServer.OpenDocumentAsync(fileInWorkspaceUri, markup).ConfigureAwait(false);
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
}
[Theory, CombinatorialData]
@@ -146,7 +146,7 @@ void M()
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file and make a request to verify it gets added to the misc workspace.
// Include some Unicode characters to test URL handling.
@@ -164,34 +164,24 @@ void M()
var documentPath = ProtocolConversions.GetDocumentFilePathFromUri(looseFileUri.GetRequiredParsedUri());
// Update the workspace to contain the loose file.
- var project = testLspServer.GetCurrentSolution().Projects.Single();
- var documentInfo = DocumentInfo.Create(
- DocumentId.CreateNewId(project.Id),
- documentPath,
- sourceCodeKind: SourceCodeKind.Regular,
- loader: new TestTextLoader(source),
- filePath: documentPath);
- testLspServer.TestWorkspace.OnDocumentAdded(documentInfo);
- await WaitForWorkspaceOperationsAsync(testLspServer.TestWorkspace);
-
- Assert.Contains(documentPath, testLspServer.GetCurrentSolution().Projects.Single().Documents.Select(d => d.FilePath));
+ await AddDocumentAsync(testLspServer, documentPath, source);
+ Assert.Contains(documentPath, GetHostWorkspace(testLspServer).CurrentSolution.Projects.Single().Documents.Select(d => d.FilePath));
// Verify that the manager returns the file that has been added to the main workspace.
await AssertFileInMainWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
// Make sure doc was removed from misc workspace.
Assert.False(miscWorkspace.CurrentSolution.ContainsDocument(miscDocument.Id));
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
}
[Theory, CombinatorialData]
public async Task TestLooseFile_RazorFile(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
- Assert.Null(GetMiscellaneousAdditionalDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
+ Assert.Null(await GetMiscellaneousAdditionalDocumentAsync(testLspServer));
// Open an empty loose file and make a request to verify it gets added to the misc workspace.
var looseFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(@"C:\SomeFile.razor");
@@ -199,25 +189,24 @@ public async Task TestLooseFile_RazorFile(bool mutatingLspWorkspace)
// Trigger a request and assert we got a file in the misc workspace.
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
- Assert.Null(GetMiscellaneousDocument(testLspServer));
- Assert.NotNull(GetMiscellaneousAdditionalDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
+ Assert.NotNull(await GetMiscellaneousAdditionalDocumentAsync(testLspServer));
// Trigger another request and assert we got a file in the misc workspace.
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
- Assert.NotNull(GetMiscellaneousAdditionalDocument(testLspServer));
+ Assert.NotNull(await GetMiscellaneousAdditionalDocumentAsync(testLspServer));
await testLspServer.CloseDocumentAsync(looseFileUri).ConfigureAwait(false);
- Assert.Null(GetMiscellaneousDocument(testLspServer));
- Assert.Null(GetMiscellaneousAdditionalDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
+ Assert.Null(await GetMiscellaneousAdditionalDocumentAsync(testLspServer));
}
[Theory, CombinatorialData]
public async Task TestLooseFile_RequestedTwiceAndClosed(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file and make a request to verify it gets added to the misc workspace.
var looseFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(@"C:\SomeFile.cs");
@@ -237,16 +226,15 @@ void M()
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
await testLspServer.CloseDocumentAsync(looseFileUri).ConfigureAwait(false);
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
}
[Theory, CombinatorialData]
public async Task TestLooseFile_OpenedWithLanguageId(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file that hasn't been saved with a name.
@@ -263,7 +251,7 @@ void M()
// Verify file is added to the misc file workspace.
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
- var miscDoc = GetMiscellaneousDocument(testLspServer);
+ var miscDoc = await GetMiscellaneousDocumentAsync(testLspServer);
AssertEx.NotNull(miscDoc);
Assert.Equal(LanguageNames.CSharp, miscDoc.Project.Language);
}
@@ -271,10 +259,9 @@ void M()
[Theory, CombinatorialData]
public async Task TestLooseFile_OpenedWithLanguageIdWithSubsequentRequest(bool mutatingLspWorkspace)
{
-
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
- Assert.Null(GetMiscellaneousDocument(testLspServer));
+ Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
// Open an empty loose file that hasn't been saved with a name.
@@ -303,7 +290,7 @@ void M()
// Verify file was added to the misc file workspace.
await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false);
- var miscDoc = GetMiscellaneousDocument(testLspServer);
+ var miscDoc = await GetMiscellaneousDocumentAsync(testLspServer);
AssertEx.NotNull(miscDoc);
Assert.Equal(LanguageNames.CSharp, miscDoc.Project.Language);
@@ -315,26 +302,92 @@ void M()
Assert.Equal(7, result.Single().Range.End.Character);
}
- private static async Task AssertFileInMiscWorkspaceAsync(TestLspServer testLspServer, DocumentUri fileUri)
+ [Theory, CombinatorialData]
+ public async Task TestLspTransfersFromMiscellaneousFilesToHostWorkspaceAsync(bool mutatingLspWorkspace, bool waitForWorkspace, bool fileBasedProgramContent)
{
- var (lspWorkspace, _, _) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = fileUri }, CancellationToken.None);
- Assert.Equal(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace(), lspWorkspace);
+ var markup = fileBasedProgramContent ? "Console.WriteLine();" : "class C { }";
+
+ // Create a server that includes the LSP misc files workspace so we can test transfers to and from it.
+ await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
+
+ // Include some Unicode characters to test URL handling.
+ using var tempRoot = new TempRoot();
+ var newDocumentFilePath = Path.Combine(tempRoot.CreateDirectory().Path, "ue25b\ud86d\udeac.cs");
+
+ // If this is file based, we're going to be inspecting the actual content on disk as a part of a dotnet run-api invocation
+ if (fileBasedProgramContent)
+ File.WriteAllText(newDocumentFilePath, markup);
+ var newDocumentUri = ProtocolConversions.CreateAbsoluteDocumentUri(newDocumentFilePath);
+
+ // Open the document via LSP before the workspace sees it.
+ await testLspServer.OpenDocumentAsync(newDocumentUri, "LSP text");
+
+ // Verify it is a miscellaneous document of some kind
+ var (_, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
+ Assert.NotNull(miscDocument);
+ Assert.True(await testLspServer.GetManagerAccessor().IsMiscellaneousFilesDocumentAsync(miscDocument));
+ Assert.Equal("LSP text", (await miscDocument.GetTextAsync(CancellationToken.None)).ToString());
+
+ if (waitForWorkspace)
+ {
+ // Optionally wait for the workspace so we can test what happens if we're seeing if it's a file-based program; otherwise we can test what
+ // happens if that analysis is still happening while we're loading real solutions.
+ await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync();
+ }
+
+ // Make a change and verify the misc document is updated.
+ await testLspServer.InsertTextAsync(newDocumentUri, (0, 0, "More LSP text"));
+ (_, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
+ AssertEx.NotNull(miscDocument);
+ var miscText = await miscDocument.GetTextAsync(CancellationToken.None);
+ Assert.Equal("More LSP textLSP text", miscText.ToString());
+
+ // Update the registered workspace with the new document.
+ var newDocumentId = (await AddDocumentAsync(testLspServer, newDocumentFilePath, "New Doc")).Id;
+
+ // Verify that the newly added document in the registered workspace is returned.
+ var (documentWorkspace, document) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
+ AssertEx.NotNull(document);
+ Assert.Equal(GetHostWorkspace(testLspServer), documentWorkspace);
+ Assert.False(await testLspServer.GetManagerAccessor().IsMiscellaneousFilesDocumentAsync(document));
+ Assert.Equal(newDocumentId, document.Id);
+ // Verify we still are using the tracked LSP text for the document.
+ var documentText = await document.GetTextAsync(CancellationToken.None);
+ Assert.Equal("More LSP textLSP text", documentText.ToString());
}
- private static async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspServer, DocumentUri fileUri)
+ private protected abstract ValueTask AddDocumentAsync(TestLspServer testLspServer, string filePath, string content);
+ private protected abstract Workspace GetHostWorkspace(TestLspServer testLspServer);
+
+ private static async Task<(Workspace? workspace, Document? document)> GetLspWorkspaceAndDocumentAsync(DocumentUri uri, TestLspServer testLspServer)
{
- var (lspWorkspace, _, _) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = fileUri }, CancellationToken.None).ConfigureAwait(false);
- Assert.Equal(testLspServer.TestWorkspace, lspWorkspace);
+ var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(CreateTextDocumentIdentifier(uri), CancellationToken.None).ConfigureAwait(false);
+ return (workspace, document as Document);
+ }
+
+ private static async ValueTask GetMiscellaneousDocumentAsync(TestLspServer testLspServer)
+ {
+ var documents = await testLspServer.GetManagerAccessor().GetMiscellaneousDocumentsAsync(static p => p.Documents).ToImmutableArrayAsync(CancellationToken.None);
+ return documents.SingleOrDefault();
+ }
+
+ private static async ValueTask GetMiscellaneousAdditionalDocumentAsync(TestLspServer testLspServer)
+ {
+ var documents = await testLspServer.GetManagerAccessor().GetMiscellaneousDocumentsAsync(static p => p.AdditionalDocuments).ToImmutableArrayAsync(CancellationToken.None);
+ return documents.SingleOrDefault();
}
- private static Document? GetMiscellaneousDocument(TestLspServer testLspServer)
+ private static async Task AssertFileInMiscWorkspaceAsync(TestLspServer testLspServer, DocumentUri fileUri)
{
- return testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.Documents.SingleOrDefault();
+ var (_, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = fileUri }, CancellationToken.None);
+ Assert.NotNull(document);
+ Assert.True(await testLspServer.GetManagerAccessor().IsMiscellaneousFilesDocumentAsync(document));
}
- private static TextDocument? GetMiscellaneousAdditionalDocument(TestLspServer testLspServer)
+ private async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspServer, DocumentUri fileUri)
{
- return testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.AdditionalDocuments.SingleOrDefault();
+ var (lspWorkspace, _, _) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = fileUri }, CancellationToken.None).ConfigureAwait(false);
+ Assert.Equal(GetHostWorkspace(testLspServer), lspWorkspace);
}
private static async Task RunGetHoverAsync(TestLspServer testLspServer, LSP.Location caret)
diff --git a/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs
index 9de1171a566ac..9cad08424bb7c 100644
--- a/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs
+++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs
@@ -21,12 +21,14 @@
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion;
+using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Options;
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.Composition;
using Nerdbank.Streams;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
@@ -101,6 +103,7 @@ private protected sealed class OrderLocations : Comparer
public override int Compare(LSP.Location? x, LSP.Location? y) => CompareLocations(x, y);
}
+ protected virtual ValueTask CreateExportProviderAsync() => ValueTask.FromResult(Composition.ExportProviderFactory.CreateExportProvider());
protected virtual TestComposition Composition => FeaturesLspComposition;
private protected virtual TestAnalyzerReferenceByLanguage CreateTestAnalyzersReference()
@@ -306,12 +309,12 @@ private protected Task CreateTestLspServerAsync([StringSyntax(Pre
private protected Task CreateVisualBasicTestLspServerAsync(string markup, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, TestComposition? composition = null)
=> CreateTestLspServerAsync([markup], LanguageNames.VisualBasic, mutatingLspWorkspace, initializationOptions, composition);
- private protected Task CreateTestLspServerAsync(
+ private protected async Task CreateTestLspServerAsync(
string[] markups, string languageName, bool mutatingLspWorkspace, InitializationOptions? initializationOptions, TestComposition? composition = null, bool commonReferences = true)
{
var lspOptions = initializationOptions ?? new InitializationOptions();
- var workspace = CreateWorkspace(lspOptions, workspaceKind: null, mutatingLspWorkspace, composition);
+ var workspace = await CreateWorkspaceAsync(lspOptions, workspaceKind: null, mutatingLspWorkspace, composition);
workspace.InitializeDocuments(
LspTestWorkspace.CreateWorkspaceElement(
@@ -323,10 +326,10 @@ private protected Task CreateTestLspServerAsync(
commonReferences: commonReferences),
openDocuments: false);
- return CreateTestLspServerAsync(workspace, lspOptions, languageName);
+ return await CreateTestLspServerAsync(workspace, lspOptions, languageName);
}
- private async Task CreateTestLspServerAsync(LspTestWorkspace workspace, InitializationOptions initializationOptions, string languageName)
+ private protected async Task CreateTestLspServerAsync(LspTestWorkspace workspace, InitializationOptions initializationOptions, string languageName)
{
var solution = workspace.CurrentSolution;
@@ -348,7 +351,7 @@ private protected async Task CreateXmlTestLspServerAsync(
{
var lspOptions = initializationOptions ?? new InitializationOptions();
- var workspace = CreateWorkspace(lspOptions, workspaceKind, mutatingLspWorkspace);
+ var workspace = await CreateWorkspaceAsync(lspOptions, workspaceKind, mutatingLspWorkspace);
workspace.InitializeDocuments(XElement.Parse(xmlContent), openDocuments: false);
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([CreateTestAnalyzersReference()]));
@@ -356,14 +359,24 @@ private protected async Task CreateXmlTestLspServerAsync(
return await TestLspServer.CreateAsync(workspace, lspOptions, TestOutputLspLogger);
}
- internal LspTestWorkspace CreateWorkspace(
+ internal async Task CreateWorkspaceAsync(
InitializationOptions? options, string? workspaceKind, bool mutatingLspWorkspace, TestComposition? composition = null)
{
var workspace = new LspTestWorkspace(
- composition ?? Composition, workspaceKind, configurationOptions: new WorkspaceConfigurationOptions(ValidateCompilationTrackerStates: true), supportsLspMutation: mutatingLspWorkspace);
+ composition?.ExportProviderFactory.CreateExportProvider() ?? await CreateExportProviderAsync(),
+ workspaceKind,
+ supportsLspMutation: mutatingLspWorkspace);
options?.OptionUpdater?.Invoke(workspace.GetService());
- workspace.GetService().Register(workspace);
+ // By default in most MEF containers, workspace event listeners are disabled in tests. Explicitly enable the LSP workspace registration event listener
+ // to ensure that the lsp workspace registration service sees all workspaces. If we're running tests against our full LSP server
+ // composition, we don't expect the mock to exist and the real thing is running.
+ var listenerProvider = workspace.ExportProvider.GetExportedValues().SingleOrDefault();
+ if (listenerProvider is not null)
+ {
+ var lspWorkspaceRegistrationListener = (LspWorkspaceRegistrationEventListener)workspace.ExportProvider.GetExports().Single(e => e.Value is LspWorkspaceRegistrationEventListener).Value;
+ listenerProvider.EventListeners = [lspWorkspaceRegistrationListener];
+ }
return workspace;
}
@@ -606,10 +619,6 @@ internal async Task InitializeAsync()
// Initialize the language server
_ = _languageServer.Value;
- // 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)
{
_initializeResult = await this.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams
@@ -850,9 +859,6 @@ internal async ValueTask RunCodeAnalysisAsync(ProjectId? projectId)
public async ValueTask DisposeAsync()
{
- TestWorkspace.GetService().Deregister(TestWorkspace);
- TestWorkspace.GetService().Deregister(GetManagerAccessor().GetLspMiscellaneousFilesWorkspace());
-
// 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.Value.GetTestAccessor().HasShutdownStarted())
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 91693179de616..aa9aef09e9e2f 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
@@ -16,6 +16,7 @@
+
diff --git a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs
index 1623033092eb7..025e30f45eba0 100644
--- a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs
+++ b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs
@@ -8,7 +8,7 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Text;
-using Roslyn.Utilities;
+using Microsoft.VisualStudio.Composition;
namespace Microsoft.CodeAnalysis.Test.Utilities;
@@ -17,14 +17,14 @@ public sealed partial class LspTestWorkspace : TestWorkspace, ILspWorkspace
private readonly bool _supportsLspMutation;
internal LspTestWorkspace(
- TestComposition? composition = null,
+ ExportProvider exportProvider,
string? workspaceKind = WorkspaceKind.Host,
Guid solutionTelemetryId = default,
bool disablePartialSolutions = true,
bool ignoreUnchangeableDocumentsWhenApplyingChanges = true,
WorkspaceConfigurationOptions? configurationOptions = null,
bool supportsLspMutation = false)
- : base(composition,
+ : base(exportProvider,
workspaceKind,
solutionTelemetryId,
disablePartialSolutions,
diff --git a/src/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs
index a00b1f647ebfe..fd93f3c3ff9df 100644
--- a/src/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs
+++ b/src/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs
@@ -135,6 +135,15 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities)
// Using VS server capabilities because we have our own custom client.
capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] };
+ var diagnosticDynamicRegistationCapabilities = clientCapabilities.TextDocument?.Diagnostic?.DynamicRegistration;
+ if (diagnosticDynamicRegistationCapabilities is false)
+ {
+ capabilities.DiagnosticOptions = new DiagnosticOptions()
+ {
+ InterFileDependencies = true
+ };
+ }
+
return capabilities;
}
diff --git a/src/LanguageServer/Protocol/Extensions/Extensions.cs b/src/LanguageServer/Protocol/Extensions/Extensions.cs
index 75621b0a39a85..b5c55b447bed2 100644
--- a/src/LanguageServer/Protocol/Extensions/Extensions.cs
+++ b/src/LanguageServer/Protocol/Extensions/Extensions.cs
@@ -114,20 +114,26 @@ public static ImmutableArray GetDocumentIds(this Solution solution,
/// Finds the TextDocument for a TextDocumentIdentifier, potentially returning a source-generated file.
///
public static async ValueTask GetTextDocumentAsync(this Solution solution, TextDocumentIdentifier documentIdentifier, CancellationToken cancellationToken)
+ {
+ var documents = await solution.GetTextDocumentsAsync(documentIdentifier.DocumentUri, cancellationToken).ConfigureAwait(false);
+ return documents.Length == 0
+ ? null
+ : documents.FindDocumentInProjectContext(documentIdentifier, (sln, id) => sln.GetRequiredTextDocument(id));
+ }
+
+ public static async ValueTask> GetTextDocumentsAsync(this Solution solution, DocumentUri documentUri, CancellationToken cancellationToken)
{
// If it's the URI scheme for source generated files, delegate to our other helper, otherwise we can handle anything else here.
- if (documentIdentifier.DocumentUri.ParsedUri?.Scheme == SourceGeneratedDocumentUri.Scheme)
+ if (documentUri.ParsedUri?.Scheme == SourceGeneratedDocumentUri.Scheme)
{
// In the case of a URI scheme for source generated files, we generate a different URI for each project, thus this URI cannot be linked into multiple projects;
// this means we can safely call .SingleOrDefault() and not worry about calling FindDocumentInProjectContext.
- var documentId = solution.GetDocumentIds(documentIdentifier.DocumentUri).SingleOrDefault();
- return await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
+ var documentId = solution.GetDocumentIds(documentUri).SingleOrDefault();
+ var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
+ return document is not null ? [document] : [];
}
- var documents = solution.GetTextDocuments(documentIdentifier.DocumentUri);
- return documents.Length == 0
- ? null
- : documents.FindDocumentInProjectContext(documentIdentifier, (sln, id) => sln.GetRequiredTextDocument(id));
+ return solution.GetTextDocuments(documentUri);
}
private static T FindItemInProjectContext(
@@ -183,7 +189,7 @@ public static T FindDocumentInProjectContext(this ImmutableArray documents
return null;
}
- var projects = solution.Projects.Where(project => project.FilePath == projectIdentifier.DocumentUri.ParsedUri.LocalPath).ToImmutableArray();
+ var projects = solution.Projects.WhereAsArray(project => project.FilePath == projectIdentifier.DocumentUri.ParsedUri.LocalPath);
return !projects.Any()
? null
: FindItemInProjectContext(projects, projectIdentifier, projectIdGetter: (item) => item.Id, defaultGetter: () => projects[0]);
diff --git a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs
index 0bf58091d3323..4d7216e45b5b0 100644
--- a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs
+++ b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.Diagnostics.cs
@@ -17,23 +17,33 @@ namespace Microsoft.CodeAnalysis.LanguageServer;
internal static partial class ProtocolConversions
{
+ internal static ImmutableArray AddBuildTagIfNotPresent(ImmutableArray diagnostics)
+ {
+ return diagnostics.SelectAsArray(static d =>
+ {
+ if (d.CustomTags.Contains(WellKnownDiagnosticTags.Build))
+ return d;
+
+ return d.WithCustomTags(d.CustomTags.Add(WellKnownDiagnosticTags.Build));
+ });
+ }
+
///
/// Converts from to
///
/// The diagnostic to convert
/// Whether the client is Visual Studio
/// The project the diagnostic is relevant to
- /// Whether the diagnostic is considered "live" and should supersede others
/// Whether the diagnostic is potentially a duplicate to a build diagnostic
/// The global options service
- public static ImmutableArray ConvertDiagnostic(DiagnosticData diagnosticData, bool supportsVisualStudioExtensions, Project project, bool isLiveSource, bool potentialDuplicate, IGlobalOptionService globalOptionService)
+ public static ImmutableArray ConvertDiagnostic(DiagnosticData diagnosticData, bool supportsVisualStudioExtensions, Project project, bool potentialDuplicate, IGlobalOptionService globalOptionService)
{
if (!ShouldIncludeHiddenDiagnostic(diagnosticData, supportsVisualStudioExtensions))
{
return [];
}
- var diagnostic = CreateLspDiagnostic(diagnosticData, project, isLiveSource, potentialDuplicate, supportsVisualStudioExtensions);
+ var diagnostic = CreateLspDiagnostic(diagnosticData, project, potentialDuplicate, supportsVisualStudioExtensions);
// Check if we need to handle the unnecessary tag (fading).
if (!diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary))
@@ -65,7 +75,7 @@ internal static partial class ProtocolConversions
diagnosticsBuilder.Add(diagnostic);
foreach (var location in unnecessaryLocations)
{
- var additionalDiagnostic = CreateLspDiagnostic(diagnosticData, project, isLiveSource, potentialDuplicate, supportsVisualStudioExtensions);
+ var additionalDiagnostic = CreateLspDiagnostic(diagnosticData, project, potentialDuplicate, supportsVisualStudioExtensions);
additionalDiagnostic.Severity = LSP.DiagnosticSeverity.Hint;
additionalDiagnostic.Range = GetRange(location);
additionalDiagnostic.Tags = [DiagnosticTag.Unnecessary, VSDiagnosticTags.HiddenInEditor, VSDiagnosticTags.HiddenInErrorList, VSDiagnosticTags.SuppressEditorToolTip];
@@ -94,7 +104,6 @@ internal static partial class ProtocolConversions
private static LSP.VSDiagnostic CreateLspDiagnostic(
DiagnosticData diagnosticData,
Project project,
- bool isLiveSource,
bool potentialDuplicate,
bool supportsVisualStudioExtensions)
{
@@ -108,7 +117,7 @@ private static LSP.VSDiagnostic CreateLspDiagnostic(
CodeDescription = ProtocolConversions.HelpLinkToCodeDescription(diagnosticData.GetValidHelpLinkUri()),
Message = diagnosticData.Message,
Severity = ConvertDiagnosticSeverity(diagnosticData.Severity),
- Tags = ConvertTags(diagnosticData, isLiveSource, potentialDuplicate),
+ Tags = ConvertTags(diagnosticData, potentialDuplicate),
DiagnosticRank = ConvertRank(diagnosticData),
Range = GetRange(diagnosticData.DataLocation)
};
@@ -223,7 +232,7 @@ private static LSP.DiagnosticSeverity ConvertDiagnosticSeverity(DiagnosticSeveri
/// If you make change in this method, please also update the corresponding file in
/// src\VisualStudio\Xaml\Impl\Implementation\LanguageServer\Handler\Diagnostics\AbstractPullDiagnosticHandler.cs
///
- private static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource, bool potentialDuplicate)
+ private static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool potentialDuplicate)
{
using var _ = ArrayBuilder.GetInstance(out var result);
@@ -246,11 +255,8 @@ private static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool i
if (potentialDuplicate)
result.Add(VSDiagnosticTags.PotentialDuplicate);
- // Mark this also as a build error. That way an explicitly kicked off build from a source like CPS can
+ // If tagged as build, mark this also as a build error. That way an explicitly kicked off build from a source like CPS can
// override it.
- if (!isLiveSource)
- result.Add(VSDiagnosticTags.BuildError);
-
result.Add(diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Build)
? VSDiagnosticTags.BuildError
: VSDiagnosticTags.IntellisenseError);
diff --git a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
index 9c4c254d8ee7a..2c8495ffc5537 100644
--- a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
+++ b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
@@ -417,7 +417,7 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text)
}
// Map all the text changes' spans for this document.
- var mappedResults = await GetMappedSpanResultAsync(oldDocument, [.. textChanges.Select(tc => tc.Span)], cancellationToken).ConfigureAwait(false);
+ var mappedResults = await SpanMappingHelper.TryGetMappedSpanResultAsync(oldDocument, [.. textChanges.Select(tc => tc.Span)], cancellationToken).ConfigureAwait(false);
if (mappedResults == null)
{
// There's no span mapping available, just create text edits from the original text changes.
@@ -472,7 +472,9 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text)
{
Debug.Assert(document.FilePath != null);
- var result = await GetMappedSpanResultAsync(document, [textSpan], cancellationToken).ConfigureAwait(false);
+ var result = document is Document d
+ ? await SpanMappingHelper.TryGetMappedSpanResultAsync(d, [textSpan], cancellationToken).ConfigureAwait(false)
+ : null;
if (result == null)
return await ConvertTextSpanToLocationAsync(document, textSpan, isStale, cancellationToken).ConfigureAwait(false);
@@ -1008,25 +1010,6 @@ static string GetStyledText(TaggedText taggedText, bool isInCodeBlock)
}
}
- private static async Task?> GetMappedSpanResultAsync(TextDocument textDocument, ImmutableArray textSpans, CancellationToken cancellationToken)
- {
- if (textDocument is not Document document)
- {
- return null;
- }
-
- var spanMappingService = document.DocumentServiceProvider.GetService();
- if (spanMappingService == null)
- {
- return null;
- }
-
- var mappedSpanResult = await spanMappingService.MapSpansAsync(document, textSpans, cancellationToken).ConfigureAwait(false);
- Contract.ThrowIfFalse(textSpans.Length == mappedSpanResult.Length,
- $"The number of input spans {textSpans.Length} should match the number of mapped spans returned {mappedSpanResult.Length}");
- return mappedSpanResult;
- }
-
private static LSP.Range MappedSpanResultToRange(MappedSpanResult mappedSpanResult)
{
return new LSP.Range
diff --git a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs
index c4b2b704f4e96..db886e9f5f633 100644
--- a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs
+++ b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs
@@ -18,9 +18,6 @@ internal static partial class EditAndContinueDiagnosticSource
{
private sealed class OpenDocumentSource(Document document) : AbstractDocumentDiagnosticSource(document)
{
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken)
{
var designTimeDocument = Document;
diff --git a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs
index 4188a319bc83e..9069b39d0e3fc 100644
--- a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs
+++ b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs
@@ -19,8 +19,6 @@ internal static partial class EditAndContinueDiagnosticSource
{
private sealed class ProjectSource(Project project, ImmutableArray diagnostics) : AbstractProjectDiagnosticSource(project)
{
- public override bool IsLiveSource()
- => true;
public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken)
=> Task.FromResult(diagnostics);
@@ -28,8 +26,6 @@ public override Task> GetDiagnosticsAsync(Request
private sealed class ClosedDocumentSource(TextDocument document, ImmutableArray diagnostics) : AbstractWorkspaceDocumentDiagnosticSource(document)
{
- public override bool IsLiveSource()
- => true;
public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken)
=> Task.FromResult(diagnostics);
diff --git a/src/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs b/src/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs
index ba54d187372e1..1b192d32ff53a 100644
--- a/src/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs
+++ b/src/LanguageServer/Protocol/Features/FindUsages/SimpleFindUsagesContext.cs
@@ -68,7 +68,7 @@ public override ValueTask OnDefinitionFoundAsync(DefinitionItem definition, Canc
public override async ValueTask OnReferencesFoundAsync(IAsyncEnumerable references, CancellationToken cancellationToken)
{
- await foreach (var reference in references)
+ await foreach (var reference in references.ConfigureAwait(false))
{
lock (_gate)
{
diff --git a/src/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs
index 147461645c514..77219716714e5 100644
--- a/src/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs
+++ b/src/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs
@@ -290,11 +290,11 @@ private static ImmutableArray PrioritizeFixGroups(
{
var actions = map[groupKey];
- var nonSuppressionActions = actions.Where(a => !IsTopLevelSuppressionAction(a.OriginalCodeAction)).ToImmutableArray();
+ var nonSuppressionActions = actions.WhereAsArray(a => !IsTopLevelSuppressionAction(a.OriginalCodeAction));
AddUnifiedSuggestedActionsSet(originalSolution, text, nonSuppressionActions, groupKey, nonSuppressionSets);
- var suppressionActions = actions.Where(a => IsTopLevelSuppressionAction(a.OriginalCodeAction) &&
- !IsBulkConfigurationAction(a.OriginalCodeAction)).ToImmutableArray();
+ var suppressionActions = actions.WhereAsArray(a => IsTopLevelSuppressionAction(a.OriginalCodeAction) &&
+ !IsBulkConfigurationAction(a.OriginalCodeAction));
AddUnifiedSuggestedActionsSet(originalSolution, text, suppressionActions, groupKey, suppressionSets);
bulkConfigurationActions.AddRange(actions.Where(a => IsBulkConfigurationAction(a.OriginalCodeAction)));
diff --git a/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensHandler.cs b/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensHandler.cs
index 490e9e35c41ff..028b16114706c 100644
--- a/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensHandler.cs
@@ -42,11 +42,13 @@ public CodeLensHandler(IGlobalOptionService globalOptionService)
public LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeLensParams request)
=> request.TextDocument;
- public async Task HandleRequestAsync(LSP.CodeLensParams request, RequestContext context, CancellationToken cancellationToken)
+ public Task HandleRequestAsync(LSP.CodeLensParams request, RequestContext context, CancellationToken cancellationToken)
+ => GetCodeLensAsync(request.TextDocument, context.GetRequiredDocument(), _globalOptionService, cancellationToken);
+
+ internal static async Task GetCodeLensAsync(LSP.TextDocumentIdentifier textDocumentIdentifier, Document document, IGlobalOptionService globalOptionService, CancellationToken cancellationToken)
{
- var document = context.GetRequiredDocument();
- var referencesCodeLensEnabled = _globalOptionService.GetOption(LspOptionsStorage.LspEnableReferencesCodeLens, document.Project.Language);
- var testsCodeLensEnabled = _globalOptionService.GetOption(LspOptionsStorage.LspEnableTestsCodeLens, document.Project.Language);
+ var referencesCodeLensEnabled = globalOptionService.GetOption(LspOptionsStorage.LspEnableReferencesCodeLens, document.Project.Language);
+ var testsCodeLensEnabled = globalOptionService.GetOption(LspOptionsStorage.LspEnableTestsCodeLens, document.Project.Language);
if (!referencesCodeLensEnabled && !testsCodeLensEnabled)
{
@@ -67,13 +69,13 @@ public LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeLensParams r
if (referencesCodeLensEnabled)
{
- await AddReferencesCodeLensAsync(codeLenses, members, document, text, request.TextDocument, cancellationToken).ConfigureAwait(false);
+ await AddReferencesCodeLensAsync(codeLenses, members, document, text, textDocumentIdentifier, cancellationToken).ConfigureAwait(false);
}
- if (!_globalOptionService.GetOption(LspOptionsStorage.LspUsingDevkitFeatures) && testsCodeLensEnabled)
+ if (!globalOptionService.GetOption(LspOptionsStorage.LspUsingDevkitFeatures) && testsCodeLensEnabled)
{
// Only return test codelenses if we're not using devkit.
- AddTestCodeLens(codeLenses, members, document, text, request.TextDocument);
+ AddTestCodeLens(codeLenses, members, document, text, textDocumentIdentifier);
}
return codeLenses.ToArray();
diff --git a/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensResolveHandler.cs b/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensResolveHandler.cs
index 9ccffd4c5a595..fdbdd7eb15080 100644
--- a/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensResolveHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/CodeLens/CodeLensResolveHandler.cs
@@ -33,9 +33,14 @@ internal sealed class CodeLensResolveHandler() : ILspServiceDocumentRequestHandl
public LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeLens request)
=> GetCodeLensResolveData(request).TextDocument;
- public async Task HandleRequestAsync(LSP.CodeLens request, RequestContext context, CancellationToken cancellationToken)
+ public Task HandleRequestAsync(LSP.CodeLens request, RequestContext context, CancellationToken cancellationToken)
{
var document = context.GetRequiredDocument();
+ return ResolveCodeLensAsync(request, document, cancellationToken);
+ }
+
+ internal static async Task ResolveCodeLensAsync(LSP.CodeLens request, Document document, CancellationToken cancellationToken)
+ {
var currentDocumentSyntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false);
var resolveData = GetCodeLensResolveData(request);
diff --git a/src/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs b/src/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs
index 9634aea2cb4ae..4952c56ab0c60 100644
--- a/src/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs
@@ -236,8 +236,7 @@ private static (CompletionList CompletionList, bool IsIncomplete, bool isHardSel
var filteredList = matchResultsBuilder
.Take(completionListMaxSize)
.Concat(matchResultsBuilder.Skip(completionListMaxSize).Where(match => match.CompletionItem.Rules.MatchPriority == MatchPriority.Preselect))
- .Select(matchResult => matchResult.CompletionItem)
- .ToImmutableArray();
+ .SelectAsArray(matchResult => matchResult.CompletionItem);
var newCompletionList = completionList.WithItemsList(filteredList);
// Per the LSP spec, the completion list should be marked with isIncomplete = false when further insertions will
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
index 33ce7c29a33fc..b1490635f95ef 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
@@ -313,7 +313,6 @@ private void HandleRemovedDocuments(RequestContext context, HashSet GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities)
- => _nameToDocumentProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key);
+ => _nameToDocumentProviderMap.SelectAsArray(
+ predicate: kvp => kvp.Value.IsEnabled(clientCapabilities),
+ selector: kvp => kvp.Key);
public ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities)
- => _nameToWorkspaceProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key);
+ => _nameToWorkspaceProviderMap.SelectAsArray(
+ predicate: kvp => kvp.Value.IsEnabled(clientCapabilities),
+ selector: kvp => kvp.Key);
public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken)
=> CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken);
@@ -78,7 +81,7 @@ private static async ValueTask> CreateDiagnost
}
else
{
- // VS Code (and legacy VS ?) pass null sourceName when requesting all sources.
+ // Some clients (legacy VS/VSCode, Razor) do not support multiple sources - a null source indicates that diagnostics from all sources should be returned.
using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder);
foreach (var (name, provider) in nameToProviderMap)
{
@@ -106,7 +109,6 @@ public static ImmutableArray AggregateSourcesIfNeeded(Immutab
if (isDocument)
{
// Group all document sources into a single source.
- Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live");
sources = [new AggregatedDocumentDiagnosticSource(sources)];
}
else
@@ -115,7 +117,7 @@ public static ImmutableArray AggregateSourcesIfNeeded(Immutab
// will have same value for GetDocumentIdentifier and GetProject(). Thus can be
// aggregated in a single source which will return same values. See
// AggregatedDocumentDiagnosticSource implementation for more details.
- sources = [.. sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s).SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g))];
+ sources = [.. sources.GroupBy(s => s.GetId(), s => s).SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g))];
}
return sources;
@@ -141,7 +143,6 @@ public static ImmutableArray AggregateIfNeeded(IEnumerable true;
public Project GetProject() => sources[0].GetProject();
public ProjectOrDocumentId GetId() => sources[0].GetId();
public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier();
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs
index c92822a6b9cd6..c77cfb567f96e 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs
@@ -16,8 +16,6 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do
public TDocument Document { get; } = document;
public Solution Solution => this.Document.Project.Solution;
- public abstract bool IsLiveSource();
-
public abstract Task> GetDiagnosticsAsync(
RequestContext context, CancellationToken cancellationToken);
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs
index a2e58d7ac8a3d..04e70a6e08763 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.LanguageServer;
using Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
@@ -23,7 +24,6 @@ public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagn
public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService)
=> new CodeAnalysisDiagnosticSource(project, codeAnalysisService);
- public abstract bool IsLiveSource();
public abstract Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken);
public ProjectOrDocumentId GetId() => new(Project.Id);
@@ -37,12 +37,6 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P
private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer)
: AbstractProjectDiagnosticSource(project)
{
- ///
- /// This is a normal project source that represents live/fresh diagnostics that should supersede everything else.
- ///
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken)
@@ -64,19 +58,17 @@ public override async Task> GetDiagnosticsAsync(
private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService)
: AbstractProjectDiagnosticSource(project)
{
- ///
- /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the
- /// user. As such, it is definitely not "live" data, and it should be overridden by any subsequent fresh data
- /// that has been produced.
- ///
- public override bool IsLiveSource()
- => false;
-
public override Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken)
{
- return Task.FromResult(codeAnalysisService.GetLastComputedProjectDiagnostics(Project.Id));
+ var diagnostics = codeAnalysisService.GetLastComputedProjectDiagnostics(Project.Id);
+
+ // This source provides the results of the *last* explicitly kicked off "run code analysis" command from the
+ // user. As such, it is definitely not "live" data, and it should be overridden by any subsequent fresh data
+ // that has been produced.
+ diagnostics = ProtocolConversions.AddBuildTagIfNotPresent(diagnostics);
+ return Task.FromResult(diagnostics);
}
}
}
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs
index 9e0a50253403e..e243c04ef9404 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs
@@ -32,12 +32,6 @@ private sealed class FullSolutionAnalysisDiagnosticSource(
///
private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new();
- ///
- /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else.
- ///
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken)
@@ -92,19 +86,17 @@ AsyncLazy> GetLazyDiagnostics()
private sealed class CodeAnalysisDiagnosticSource(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService)
: AbstractWorkspaceDocumentDiagnosticSource(document)
{
- ///
- /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the
- /// user. As such, it is definitely not "live" data, and it should be overridden by any subsequent fresh data
- /// that has been produced.
- ///
- public override bool IsLiveSource()
- => false;
-
public override Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken)
{
- return Task.FromResult(codeAnalysisService.GetLastComputedDocumentDiagnostics(Document.Id));
+ var diagnostics = codeAnalysisService.GetLastComputedDocumentDiagnostics(Document.Id);
+
+ // This source provides the results of the *last* explicitly kicked off "run code analysis" command from the
+ // user. As such, it is definitely not "live" data, and it should be overridden by any subsequent fresh data
+ // that has been produced.
+ diagnostics = ProtocolConversions.AddBuildTagIfNotPresent(diagnostics);
+ return Task.FromResult(diagnostics);
}
}
}
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs
index 9960d4dc5a7e5..448a9e5b9497e 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs
@@ -15,12 +15,6 @@ internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, Te
{
public DiagnosticKind DiagnosticKind { get; } = diagnosticKind;
- ///
- /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else.
- ///
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(
RequestContext context, CancellationToken cancellationToken)
{
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs
index e8cb70bc1de4c..030cdd7d924b0 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs
@@ -20,17 +20,6 @@ internal interface IDiagnosticSource
ProjectOrDocumentId GetId();
TextDocumentIdentifier? GetDocumentIdentifier();
string ToDisplayString();
-
- ///
- /// True if this source produces diagnostics that are considered 'live' or not. Live errors represent up to date
- /// information that should supersede other sources. Non 'live' errors (aka "build errors") are recognized to
- /// potentially represent stale results from a point in the past when the computation occurred. The only time
- /// Roslyn produces non-live errors through an explicit user gesture to "run code analysis". Because these represent
- /// errors from the past, we do want them to be superseded by a more recent live run, or a more recent build from
- /// another source.
- ///
- bool IsLiveSource();
-
Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken);
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs
index 2551c6592a82b..4b6df55b2bf57 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs
@@ -16,9 +16,6 @@ internal sealed class NonLocalDocumentDiagnosticSource(
{
private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer;
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(
RequestContext context,
CancellationToken cancellationToken)
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs
index fe6ebc182c6f9..f3c0adfe559fc 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs
@@ -29,9 +29,6 @@ internal sealed class TaskListDiagnosticSource(Document document, IGlobalOptionS
private readonly IGlobalOptionService _globalOptions = globalOptions;
- public override bool IsLiveSource()
- => true;
-
public override async Task> GetDiagnosticsAsync(
RequestContext context, CancellationToken cancellationToken)
{
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs
index c8c824eca7a04..80387153f7113 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsPullCache.cs
@@ -29,21 +29,16 @@ internal abstract partial class AbstractPullDiagnosticHandler
private sealed class DiagnosticsPullCache(IGlobalOptionService globalOptions, string uniqueKey)
- : VersionedPullCache<(int globalStateVersion, VersionStamp? dependentVersion), (int globalStateVersion, Checksum dependentChecksum), DiagnosticsRequestState, ImmutableArray>(uniqueKey)
+ : VersionedPullCache<(int globalStateVersion, Checksum dependentChecksum), DiagnosticsRequestState, ImmutableArray>(uniqueKey)
{
private readonly IGlobalOptionService _globalOptions = globalOptions;
- public override async Task<(int globalStateVersion, VersionStamp? dependentVersion)> ComputeCheapVersionAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
- {
- return (state.GlobalStateVersion, await state.Project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false));
- }
-
- public override async Task<(int globalStateVersion, Checksum dependentChecksum)> ComputeExpensiveVersionAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
+ public override async Task<(int globalStateVersion, Checksum dependentChecksum)> ComputeVersionAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
{
return (state.GlobalStateVersion, await state.Project.GetDiagnosticChecksumAsync(cancellationToken).ConfigureAwait(false));
}
- ///
+ ///
public override async Task> ComputeDataAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
{
var diagnostics = await state.DiagnosticSource.GetDiagnosticsAsync(state.Context, cancellationToken).ConfigureAwait(false);
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs
index 06bcae4f8a9bd..39acee6fc7315 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs
@@ -85,14 +85,14 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi
protected override ImmutableArray? GetPreviousResults(WorkspaceDiagnosticParams diagnosticsParams)
{
- return diagnosticsParams.PreviousResultId.Select(id => new PreviousPullResult
+ return diagnosticsParams.PreviousResultId.SelectAsArray(id => new PreviousPullResult
{
PreviousResultId = id.Value,
TextDocument = new TextDocumentIdentifier
{
DocumentUri = id.Uri
}
- }).ToImmutableArray();
+ });
}
internal override TestAccessor GetTestAccessor() => new(this);
diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs
index 9710bd9a508ed..c6cc04b029bb2 100644
--- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs
@@ -55,7 +55,7 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi
}
protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams)
- => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray();
+ => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).SelectAsArray(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!));
protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress)
{
diff --git a/src/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs
index b35fb881c029e..d58ddd991b575 100644
--- a/src/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs
@@ -111,7 +111,7 @@ private static FoldingRange[] GetFoldingRanges(BlockStructure blockStructure, So
BlockTypes.Imports => FoldingRangeKind.Imports,
BlockTypes.PreprocessorRegion => FoldingRangeKind.Region,
BlockTypes.Member => VSFoldingRangeKind.Implementation,
- _ => null,
+ _ => span.AutoCollapse ? VSFoldingRangeKind.Implementation : null,
};
foldingRanges.Add(new FoldingRange()
diff --git a/src/LanguageServer/Protocol/Handler/InlineCompletions/XmlSnippetParser.CodeSnippet.cs b/src/LanguageServer/Protocol/Handler/InlineCompletions/XmlSnippetParser.CodeSnippet.cs
index e9855779a1fac..95a90ec975478 100644
--- a/src/LanguageServer/Protocol/Handler/InlineCompletions/XmlSnippetParser.CodeSnippet.cs
+++ b/src/LanguageServer/Protocol/Handler/InlineCompletions/XmlSnippetParser.CodeSnippet.cs
@@ -78,7 +78,7 @@ public static CodeSnippet ReadSnippetFromFile(string filePath, string snippetTit
if (codeSnippetsElement.Name.LocalName.Equals("CodeSnippets", StringComparison.OrdinalIgnoreCase))
{
- return codeSnippetsElement.Elements().Where(e => e.Name.LocalName.Equals("CodeSnippet", StringComparison.OrdinalIgnoreCase)).ToImmutableArray();
+ return codeSnippetsElement.Elements().WhereAsArray(e => e.Name.LocalName.Equals("CodeSnippet", StringComparison.OrdinalIgnoreCase));
}
else if (codeSnippetsElement.Name.LocalName.Equals("CodeSnippet", StringComparison.OrdinalIgnoreCase))
{
diff --git a/src/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs
index 516899c0e30c7..cd6c950489a5a 100644
--- a/src/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs
@@ -54,7 +54,7 @@ internal sealed class OnAutoInsertHandler(
if (!onAutoInsertEnabled)
return SpecializedTasks.Null();
- var servicesForDocument = _braceCompletionServices.Where(s => s.Metadata.Language == document.Project.Language).SelectAsArray(s => s.Value);
+ var servicesForDocument = _braceCompletionServices.SelectAsArray(s => s.Metadata.Language == document.Project.Language, s => s.Value);
var isRazorRequest = context.ServerKind == WellKnownLspServerKinds.RazorLspServer;
var position = ProtocolConversions.PositionToLinePosition(request.Position);
return GetOnAutoInsertResponseAsync(_globalOptions, servicesForDocument, document, position, request.Character, request.Options, isRazorRequest, cancellationToken);
diff --git a/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.CacheItem.cs b/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.CacheItem.cs
index 1ecbf8fcc33e8..c78d9a6493264 100644
--- a/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.CacheItem.cs
+++ b/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.CacheItem.cs
@@ -9,10 +9,10 @@
namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
-internal abstract partial class VersionedPullCache
+internal abstract partial class VersionedPullCache
{
///
- /// Internal cache item that updates state for a particular and in
+ /// Internal cache item that updates state for a particular and in
/// This type ensures that the state for a particular key is never updated concurrently for the same key (but different key states can be concurrent).
///
private sealed class CacheItem(string uniqueKey)
@@ -44,7 +44,7 @@ private sealed class CacheItem(string uniqueKey)
///
///
///
- private (string resultId, TCheapVersion cheapVersion, TExpensiveVersion expensiveVersion, Checksum dataChecksum)? _lastResult;
+ private (string resultId, TVersion version, Checksum dataChecksum)? _lastResult;
///
/// Updates the values for this cache entry. Guarded by
@@ -52,7 +52,7 @@ private sealed class CacheItem(string uniqueKey)
/// Returns if the previousPullResult can be re-used, otherwise returns a new resultId and the new data associated with it.
///
public async Task<(string, TComputedData)?> UpdateCacheItemAsync(
- VersionedPullCache cache,
+ VersionedPullCache cache,
PreviousPullResult? previousPullResult,
bool isFullyLoaded,
TState state,
@@ -63,38 +63,27 @@ private sealed class CacheItem(string uniqueKey)
// This means that the computation of new data for this item only occurs sequentially.
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
- TCheapVersion cheapVersion;
- TExpensiveVersion expensiveVersion;
+ TVersion version;
// Check if the version we have in the cache matches the request version. If so we can re-use the resultId.
if (isFullyLoaded &&
_lastResult is not null &&
_lastResult.Value.resultId == previousPullResult?.PreviousResultId)
{
- cheapVersion = await cache.ComputeCheapVersionAsync(state, cancellationToken).ConfigureAwait(false);
- if (cheapVersion != null && cheapVersion.Equals(_lastResult.Value.cheapVersion))
- {
- // The client's resultId matches our cached resultId and the cheap version is an
- // exact match for our current cheap version. We return early here to avoid calculating
- // expensive versions as we know nothing is changed.
- return null;
- }
-
// The current cheap version does not match the last reported. This may be because we've forked
// or reloaded a project, so fall back to calculating the full expensive version to determine if
// anything is actually changed.
- expensiveVersion = await cache.ComputeExpensiveVersionAsync(state, cancellationToken).ConfigureAwait(false);
- if (expensiveVersion != null && expensiveVersion.Equals(_lastResult.Value.expensiveVersion))
+ version = await cache.ComputeVersionAsync(state, cancellationToken).ConfigureAwait(false);
+ if (version != null && version.Equals(_lastResult.Value.version))
{
return null;
}
}
else
{
- // The versions we have in our cache (if any) do not match the ones provided by the client (if any).
+ // The version we have in our cache does not match the one provided by the client (if any).
// We need to calculate new results.
- cheapVersion = await cache.ComputeCheapVersionAsync(state, cancellationToken).ConfigureAwait(false);
- expensiveVersion = await cache.ComputeExpensiveVersionAsync(state, cancellationToken).ConfigureAwait(false);
+ version = await cache.ComputeVersionAsync(state, cancellationToken).ConfigureAwait(false);
}
// Compute the new result for the request.
@@ -111,7 +100,7 @@ _lastResult is not null &&
// subsequent requests will always fail the version comparison check (the resultId is still associated with the older version even
// though we reused it here for a newer version) and will trigger re-computation.
// By storing the updated version with the resultId we can short circuit earlier in the version checks.
- _lastResult = (_lastResult.Value.resultId, cheapVersion, expensiveVersion, dataChecksum);
+ _lastResult = (_lastResult.Value.resultId, version, dataChecksum);
return null;
}
else
@@ -127,7 +116,7 @@ _lastResult is not null &&
// Note that we can safely update the map before computation as any cancellation or exception
// during computation means that the client will never recieve this resultId and so cannot ask us for it.
newResultId = $"{uniqueKey}:{cache.GetNextResultId()}";
- _lastResult = (newResultId, cheapVersion, expensiveVersion, dataChecksum);
+ _lastResult = (newResultId, version, dataChecksum);
return (newResultId, data);
}
}
diff --git a/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.cs b/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.cs
index f378b4b092ecf..28b123f876e5d 100644
--- a/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.cs
+++ b/src/LanguageServer/Protocol/Handler/PullHandlers/VersionedPullCache.cs
@@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
/// that existing results can be reused, or if new results need to be computed. Multiple keys can be used,
/// with different computation costs to determine if the previous cached data is still valid.
///
-internal abstract partial class VersionedPullCache(string uniqueKey)
+internal abstract partial class VersionedPullCache(string uniqueKey)
{
///
/// Map of workspace and diagnostic source to the data used to make the last pull report.
@@ -37,19 +37,12 @@ internal abstract partial class VersionedPullCache
- /// Computes a cheap version of the current state. This is compared to the cached version we calculated for the client's previous resultId.
+ /// Computes the version of the current state. We compare the version of the current state against the
+ /// version we have cached for the client's previous resultId.
///
/// Note - this will run under the semaphore in .
///
- public abstract Task ComputeCheapVersionAsync(TState state, CancellationToken cancellationToken);
-
- ///
- /// Computes a more expensive version of the current state. If the cheap versions are mismatched, we then compare the expensive version of the current state against the
- /// expensive version we have cached for the client's previous resultId.
- ///
- /// Note - this will run under the semaphore in .
- ///
- public abstract Task ComputeExpensiveVersionAsync(TState state, CancellationToken cancellationToken);
+ public abstract Task ComputeVersionAsync(TState state, CancellationToken cancellationToken);
///
/// Computes new data for this request. This data must be hashable as it we store the hash with the requestId to determine if
diff --git a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs
index 7998035ba6d07..69ee3b7d73fc7 100644
--- a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs
+++ b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs
@@ -136,7 +136,7 @@ public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition
public override async ValueTask OnReferencesFoundAsync(IAsyncEnumerable references, CancellationToken cancellationToken)
{
- await foreach (var reference in references)
+ await foreach (var reference in references.ConfigureAwait(false))
{
using (await _semaphore.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
diff --git a/src/LanguageServer/Protocol/Handler/Rename/PrepareRenameHandler.cs b/src/LanguageServer/Protocol/Handler/Rename/PrepareRenameHandler.cs
index 0e3eb9b0cc8d5..829c4dba5ba40 100644
--- a/src/LanguageServer/Protocol/Handler/Rename/PrepareRenameHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Rename/PrepareRenameHandler.cs
@@ -33,7 +33,7 @@ public LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.PrepareRenamePar
var position = await document.GetPositionFromLinePositionAsync(linePosition, cancellationToken).ConfigureAwait(false);
var symbolicRenameInfo = await SymbolicRenameInfo.GetRenameInfoAsync(
- document, position, includeSourceGenerated: false, cancellationToken).ConfigureAwait(false);
+ document, position, cancellationToken).ConfigureAwait(false);
if (symbolicRenameInfo.IsError)
return null;
diff --git a/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs b/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs
index 186c0218cfda4..3ab023d511b5b 100644
--- a/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs
+++ b/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs
@@ -28,24 +28,23 @@ internal sealed class RenameHandler() : ILspServiceDocumentRequestHandler request.TextDocument;
public Task HandleRequestAsync(RenameParams request, RequestContext context, CancellationToken cancellationToken)
- => GetRenameEditAsync(context.GetRequiredDocument(), ProtocolConversions.PositionToLinePosition(request.Position), request.NewName, includeSourceGenerated: false, cancellationToken);
+ => GetRenameEditAsync(context.GetRequiredDocument(), ProtocolConversions.PositionToLinePosition(request.Position), request.NewName, cancellationToken);
- internal static async Task GetRenameEditAsync(Document document, LinePosition linePosition, string newName, bool includeSourceGenerated, CancellationToken cancellationToken)
+ internal static async Task GetRenameEditAsync(Document document, LinePosition linePosition, string newName, CancellationToken cancellationToken)
{
var oldSolution = document.Project.Solution;
var position = await document.GetPositionFromLinePositionAsync(linePosition, cancellationToken).ConfigureAwait(false);
var symbolicRenameInfo = await SymbolicRenameInfo.GetRenameInfoAsync(
- document, position, includeSourceGenerated, cancellationToken).ConfigureAwait(false);
+ document, position, cancellationToken).ConfigureAwait(false);
if (symbolicRenameInfo.IsError)
return null;
var options = new SymbolRenameOptions(
- renameOverloads: false,
- renameInStrings: false,
- renameInComments: false,
- renameFile: false,
- renameInSourceGeneratedDocuments: includeSourceGenerated);
+ RenameOverloads: false,
+ RenameInStrings: false,
+ RenameInComments: false,
+ RenameFile: false);
var renameLocationSet = await Renamer.FindRenameLocationsAsync(
oldSolution,
@@ -70,8 +69,6 @@ internal sealed class RenameHandler() : ILspServiceDocumentRequestHandler p.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true))
diff --git a/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
index d81dbe025b6b2..31fe98d5180f7 100644
--- a/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
+++ b/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
@@ -12,7 +12,6 @@
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using LSP = Roslyn.LanguageServer.Protocol;
@@ -21,8 +20,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens;
internal static class SemanticTokensHelpers
{
- private static readonly ObjectPool> s_tokenListPool = new(() => new List(capacity: 1000));
-
/// The ranges to get semantic tokens for. If null then the entire document will be
/// processed.
internal static async Task HandleRequestHelperAsync(
@@ -241,12 +238,7 @@ private static int[] ComputeTokens(
var lastStartCharacter = 0;
var tokenTypeMap = SemanticTokensSchema.GetSchema(supportsVisualStudioExtensions).TokenTypeMap;
-
- using var pooledData = s_tokenListPool.GetPooledObject();
- var data = pooledData.Object;
-
- // Items in the pool may not have been cleared
- data.Clear();
+ var data = AllocateTokenArray(classifiedSpans);
for (var currentClassifiedSpanIndex = 0; currentClassifiedSpanIndex < classifiedSpans.Count; currentClassifiedSpanIndex++)
{
@@ -263,7 +255,31 @@ private static int[] ComputeTokens(
data.Add(tokenModifiers);
}
- return [.. data];
+ return data.MoveToArray();
+ }
+
+ // This method allocates an array of integers to hold the semantic tokens data.
+ // NOTE: The number of items in the array is based on the number of unique classified spans
+ // in the provided list and is closely tied with how ComputeNextToken's loop works
+ private static FixedSizeArrayBuilder AllocateTokenArray(SegmentedList classifiedSpans)
+ {
+ if (classifiedSpans.Count == 0)
+ return new FixedSizeArrayBuilder(0);
+
+ var uniqueSpanCount = 1;
+ var lastSpan = classifiedSpans[0].TextSpan;
+
+ for (var index = 1; index < classifiedSpans.Count; index++)
+ {
+ var currentSpan = classifiedSpans[index].TextSpan;
+ if (currentSpan != lastSpan)
+ {
+ uniqueSpanCount++;
+ lastSpan = currentSpan;
+ }
+ }
+
+ return new FixedSizeArrayBuilder(5 * uniqueSpanCount);
}
private static int ComputeNextToken(
@@ -315,6 +331,8 @@ private static int ComputeNextToken(
var tokenTypeIndex = 0;
// Classified spans with the same text span should be combined into one token.
+ // NOTE: The update of currentClassifiedSpanIndex is closely tied to the allocation
+ // of the data array in AllocateTokenArray.
while (classifiedSpans[currentClassifiedSpanIndex].TextSpan == originalTextSpan)
{
var classificationType = classifiedSpans[currentClassifiedSpanIndex].ClassificationType;
diff --git a/src/LanguageServer/Protocol/Handler/ServerLifetime/LspServiceLifeCycleManager.cs b/src/LanguageServer/Protocol/Handler/ServerLifetime/LspServiceLifeCycleManager.cs
index 94ca5ca32f332..e8bec0292d0fe 100644
--- a/src/LanguageServer/Protocol/Handler/ServerLifetime/LspServiceLifeCycleManager.cs
+++ b/src/LanguageServer/Protocol/Handler/ServerLifetime/LspServiceLifeCycleManager.cs
@@ -43,7 +43,10 @@ public async Task ShutdownAsync(string message = "Shutting down")
// Shutting down is not cancellable.
var cancellationToken = CancellationToken.None;
- var hostWorkspace = _lspWorkspaceRegistrationService.GetAllRegistrations().SingleOrDefault(w => w.Kind == WorkspaceKind.Host);
+ // HACK: we're doing FirstOrDefault rather than SingleOrDefault because right now in unit tests we might have more than one. Tests that derive from
+ // AbstractLanguageServerProtocolTests create a TestLspWorkspace, even if the ExportProvider already has some other workspace registered.
+ // Since we're only using this as a proxy to fetch a workspace service that won't differ between the workspaces, we can pick any of them.
+ var hostWorkspace = _lspWorkspaceRegistrationService.GetAllRegistrations().FirstOrDefault(w => w.Kind == WorkspaceKind.Host);
if (hostWorkspace is not null)
{
var service = hostWorkspace.Services.GetRequiredService();
diff --git a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratedDocumentCache.cs b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratedDocumentCache.cs
index 31ebb7e377f02..2802028a06405 100644
--- a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratedDocumentCache.cs
+++ b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratedDocumentCache.cs
@@ -14,9 +14,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SourceGenerators;
internal record struct SourceGeneratedDocumentGetTextState(Document Document);
-internal sealed class SourceGeneratedDocumentCache(string uniqueKey) : VersionedPullCache<(SourceGeneratorExecutionVersion, VersionStamp), object?, SourceGeneratedDocumentGetTextState, SourceText?>(uniqueKey), ILspService
+internal sealed class SourceGeneratedDocumentCache(string uniqueKey) : VersionedPullCache<(SourceGeneratorExecutionVersion, VersionStamp), SourceGeneratedDocumentGetTextState, SourceText?>(uniqueKey), ILspService
{
- public override async Task<(SourceGeneratorExecutionVersion, VersionStamp)> ComputeCheapVersionAsync(SourceGeneratedDocumentGetTextState state, CancellationToken cancellationToken)
+ public override async Task<(SourceGeneratorExecutionVersion, VersionStamp)> ComputeVersionAsync(SourceGeneratedDocumentGetTextState state, CancellationToken cancellationToken)
{
// The execution version and the dependent version must be considered as one version cached together -
// it is not correct to say that if the execution version is the same then we can re-use results (as in automatic mode the execution version never changes).
@@ -25,11 +25,6 @@ internal sealed class SourceGeneratedDocumentCache(string uniqueKey) : Versioned
return (executionVersion, dependentVersion);
}
- public override Task
- ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool removeFromMetadataWorkspace);
+ ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri);
}
diff --git a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProvider.cs b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProvider.cs
index 2c48b294a8c42..1146d56854d60 100644
--- a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProvider.cs
+++ b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProvider.cs
@@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Features.Workspaces;
using Microsoft.CodeAnalysis.Host;
-using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CommonLanguageServerProtocol.Framework;
@@ -26,7 +25,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer;
/// Future work for this workspace includes supporting basic metadata references (mscorlib, System dlls, etc),
/// but that is dependent on having a x-plat mechanism for retrieving those references from the framework / sdk.
///
-internal sealed class LspMiscellaneousFilesWorkspaceProvider(ILspServices lspServices, IMetadataAsSourceFileService metadataAsSourceFileService, HostServices hostServices)
+internal sealed class LspMiscellaneousFilesWorkspaceProvider(ILspServices lspServices, HostServices hostServices)
: Workspace(hostServices, WorkspaceKind.MiscellaneousFiles), ILspMiscellaneousFilesWorkspaceProvider, ILspWorkspace
{
public bool SupportsMutation => true;
@@ -40,7 +39,7 @@ public ValueTask IsMiscellaneousFilesDocumentAsync(TextDocument document,
///
/// Takes in a file URI and text and creates a misc project and document for the file.
///
- /// Calls to this method and are made
+ /// Calls to this method and are made
/// from LSP text sync request handling which do not run concurrently.
///
public ValueTask AddMiscellaneousDocumentAsync(DocumentUri uri, SourceText documentText, string languageId, ILspLogger logger)
@@ -54,15 +53,6 @@ public ValueTask IsMiscellaneousFilesDocumentAsync(TextDocument document,
documentFilePath = ProtocolConversions.GetDocumentFilePathFromUri(uri.ParsedUri);
}
- var container = new StaticSourceTextContainer(documentText);
- if (metadataAsSourceFileService.TryAddDocumentToWorkspace(documentFilePath, container, out var documentId))
- {
- var metadataWorkspace = metadataAsSourceFileService.TryGetWorkspace();
- Contract.ThrowIfNull(metadataWorkspace);
- var document = metadataWorkspace.CurrentSolution.GetRequiredDocument(documentId);
- return document;
- }
-
var languageInfoProvider = lspServices.GetRequiredService();
if (!languageInfoProvider.TryGetLanguageInformation(uri, languageId, out var languageInformation))
{
@@ -93,13 +83,8 @@ public ValueTask IsMiscellaneousFilesDocumentAsync(TextDocument document,
/// Calls to this method and are made
/// from LSP text sync request handling which do not run concurrently.
///
- public ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool removeFromMetadataWorkspace)
+ public ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri)
{
- if (removeFromMetadataWorkspace && uri.ParsedUri is not null && metadataAsSourceFileService.TryRemoveDocumentFromWorkspace(ProtocolConversions.GetDocumentFilePathFromUri(uri.ParsedUri)))
- {
- return ValueTask.CompletedTask;
- }
-
// We'll only ever have a single document matching this URI in the misc solution.
var matchingDocument = CurrentSolution.GetDocumentIds(uri).SingleOrDefault();
if (matchingDocument != null)
diff --git a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProviderFactory.cs b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProviderFactory.cs
index 568903cd80a81..7a5c6fe44966d 100644
--- a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProviderFactory.cs
+++ b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspaceProviderFactory.cs
@@ -20,10 +20,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer;
[ExportCSharpVisualBasicStatelessLspService(typeof(ILspMiscellaneousFilesWorkspaceProviderFactory)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
-internal sealed class LspMiscellaneousFilesWorkspaceProviderFactory(IMetadataAsSourceFileService metadataAsSourceFileService) : ILspMiscellaneousFilesWorkspaceProviderFactory
+internal sealed class LspMiscellaneousFilesWorkspaceProviderFactory() : ILspMiscellaneousFilesWorkspaceProviderFactory
{
public ILspMiscellaneousFilesWorkspaceProvider CreateLspMiscellaneousFilesWorkspaceProvider(ILspServices lspServices, HostServices hostServices)
{
- return new LspMiscellaneousFilesWorkspaceProvider(lspServices, metadataAsSourceFileService, hostServices);
+ return new LspMiscellaneousFilesWorkspaceProvider(lspServices, hostServices);
}
}
diff --git a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs
index a23b3e4bb0693..832fb857a1508 100644
--- a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs
+++ b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs
@@ -13,7 +13,6 @@
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.DocumentChanges;
using Microsoft.CodeAnalysis.PooledObjects;
-using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CommonLanguageServerProtocol.Framework;
@@ -53,7 +52,7 @@ internal sealed class LspWorkspaceManager : IDocumentChangeTracker, ILspService
/// workspace).
/// Access to this is guaranteed to be serial by the
///
- private readonly Dictionary _cachedLspSolutions = [];
+ private readonly Dictionary _cachedLspSolutions = [];
///
/// Stores the current source text for each URI that is being tracked by LSP. Each time an LSP text sync
@@ -153,12 +152,12 @@ public async ValueTask StopTrackingAsync(DocumentUri uri, CancellationToken canc
// If LSP changed, we need to compare against the workspace again to get the updated solution.
_cachedLspSolutions.Clear();
- // Also remove it from our loose files or metadata workspace if it is still there.
+ // Also remove it from our loose files if it is still there.
if (_lspMiscellaneousFilesWorkspaceProvider is not null)
{
try
{
- await _lspMiscellaneousFilesWorkspaceProvider.TryRemoveMiscellaneousDocumentAsync(uri, removeFromMetadataWorkspace: true).ConfigureAwait(false);
+ await _lspMiscellaneousFilesWorkspaceProvider.TryRemoveMiscellaneousDocumentAsync(uri).ConfigureAwait(false);
}
catch (Exception ex) when (FatalError.ReportAndCatch(ex))
{
@@ -251,30 +250,43 @@ public void UpdateTrackedDocument(DocumentUri uri, SourceText newSourceText)
// Find the matching document from the LSP solutions.
foreach (var (workspace, lspSolution, isForked) in lspSolutions)
{
- var document = await lspSolution.GetTextDocumentAsync(textDocumentIdentifier, cancellationToken).ConfigureAwait(false);
- if (document != null)
+ var documents = await lspSolution.GetTextDocumentsAsync(textDocumentIdentifier.DocumentUri, cancellationToken).ConfigureAwait(false);
+ if (documents.Length > 0)
{
- // Record metadata on how we got this document.
- var workspaceKind = document.Project.Solution.WorkspaceKind;
- _requestTelemetryLogger.UpdateFindDocumentTelemetryData(success: true, workspaceKind);
- _requestTelemetryLogger.UpdateUsedForkedSolutionCounter(isForked);
- _logger.LogDebug($"{document.FilePath} found in workspace {workspaceKind}");
+ // We have at least one document, so find the one in the right project context
+ var document = documents.FindDocumentInProjectContext(textDocumentIdentifier, (sln, id) => sln.GetRequiredTextDocument(id));
- // If we found the document in a non-misc workspace, also attempt to remove it from the misc workspace
- // if it happens to be in there as well.
- if (_lspMiscellaneousFilesWorkspaceProvider is not null && !await _lspMiscellaneousFilesWorkspaceProvider.IsMiscellaneousFilesDocumentAsync(document, cancellationToken).ConfigureAwait(false))
+ if (_lspMiscellaneousFilesWorkspaceProvider is not null)
{
- try
+ // If we started with multiple documents and didn't have specific context information, it's possible we picked a miscellaneous files document when
+ // we could have picked a real one.
+ if (documents.Length > 1 && await _lspMiscellaneousFilesWorkspaceProvider.IsMiscellaneousFilesDocumentAsync(document, cancellationToken).ConfigureAwait(false))
{
- // Do not attempt to remove the file from the metadata workspace (the document is still open).
- await _lspMiscellaneousFilesWorkspaceProvider.TryRemoveMiscellaneousDocumentAsync(uri, removeFromMetadataWorkspace: false).ConfigureAwait(false);
+ // Pick a different one; our choice here is arbitrary, since if we had a specified context in the first place we would have picked the right one.
+ document = documents.First(d => d != document);
}
- catch (Exception ex) when (FatalError.ReportAndCatch(ex))
+
+ // If we found the document in a non-misc workspace (either immediately or by the correction above), also attempt to remove it from the misc workspace
+ // if it happens to be in there as well.
+ if (_lspMiscellaneousFilesWorkspaceProvider is not null && !await _lspMiscellaneousFilesWorkspaceProvider.IsMiscellaneousFilesDocumentAsync(document, cancellationToken).ConfigureAwait(false))
{
- _logger.LogException(ex);
+ try
+ {
+ await _lspMiscellaneousFilesWorkspaceProvider.TryRemoveMiscellaneousDocumentAsync(uri).ConfigureAwait(false);
+ }
+ catch (Exception ex) when (FatalError.ReportAndCatch(ex))
+ {
+ _logger.LogException(ex);
+ }
}
}
+ // Record metadata on how we got this document.
+ var workspaceKind = document.Project.Solution.WorkspaceKind;
+ _requestTelemetryLogger.UpdateFindDocumentTelemetryData(success: true, workspaceKind);
+ _requestTelemetryLogger.UpdateUsedForkedSolutionCounter(isForked);
+ _logger.LogDebug($"{document.FilePath} found in workspace {workspaceKind}");
+
return (workspace, document.Project.Solution, document);
}
}
@@ -380,8 +392,9 @@ .. registeredWorkspaces.Where(workspace => workspace.Kind == WorkspaceKind.Misce
_trackedDocuments.Keys.Where(static trackedDocument => trackedDocument.ParsedUri?.Scheme == SourceGeneratedDocumentUri.Scheme)
// We know we have a non null URI with a source generated scheme.
.Select(uri => (identity: SourceGeneratedDocumentUri.DeserializeIdentity(workspaceCurrentSolution, uri.ParsedUri!), _trackedDocuments[uri].Text))
- .Where(tuple => tuple.identity.HasValue)
- .SelectAsArray(tuple => (tuple.identity!.Value, DateTime.Now, tuple.Text));
+ .SelectAsArray(
+ predicate: tuple => tuple.identity.HasValue,
+ selector: tuple => (tuple.identity!.Value, DateTime.Now, tuple.Text));
// First we check if normal document text matches the workspace solution.
// This does not look at source generated documents.
@@ -394,13 +407,20 @@ .. registeredWorkspaces.Where(workspace => workspace.Kind == WorkspaceKind.Misce
if (doesAllTextMatch && doesAllSourceGeneratedTextMatch)
{
// Remember that the current LSP text matches the text in this workspace solution.
- _cachedLspSolutions[workspace] = (forkedFromVersion: null, workspaceCurrentSolution);
+ _cachedLspSolutions[workspace] = (forkedFromVersion: null, sourceGeneratorChecksum: null, workspaceCurrentSolution);
return (workspaceCurrentSolution, IsForked: false);
}
+ var forkedFromVersion = workspaceCurrentSolution.SolutionStateContentVersion;
+ var sourceGeneratorChecksum = workspaceCurrentSolution.CompilationState.SourceGeneratorExecutionVersionMap.GetChecksum();
+
// Step 4: See if we can reuse a previously forked solution.
- if (cachedSolution != default && cachedSolution.forkedFromVersion == workspaceCurrentSolution.WorkspaceVersion)
+ if (cachedSolution != default &&
+ cachedSolution.forkedFromVersion == forkedFromVersion &&
+ cachedSolution.sourceGeneratorChecksum == sourceGeneratorChecksum)
+ {
return (cachedSolution.solution, IsForked: true);
+ }
// Step 5: Fork a new solution from the workspace with the LSP text applied.
var lspSolution = workspaceCurrentSolution;
@@ -418,7 +438,7 @@ .. registeredWorkspaces.Where(workspace => workspace.Kind == WorkspaceKind.Misce
}
// Remember this forked solution and the workspace version it was forked from.
- _cachedLspSolutions[workspace] = (workspaceCurrentSolution.WorkspaceVersion, lspSolution);
+ _cachedLspSolutions[workspace] = (forkedFromVersion, sourceGeneratorChecksum, lspSolution);
return (lspSolution, IsForked: true);
}
@@ -564,11 +584,21 @@ internal readonly struct TestAccessor
public TestAccessor(LspWorkspaceManager manager)
=> _manager = manager;
- public Workspace? GetLspMiscellaneousFilesWorkspace()
+ public ValueTask IsMiscellaneousFilesDocumentAsync(TextDocument document)
{
- // For purposes of testing, we test against the implementation that is also a Workspace.
- // TODO: once we also test the FileBasedPrograms implementation, we need to do something else here.
- return _manager._lspMiscellaneousFilesWorkspaceProvider as Workspace;
+ return _manager._lspMiscellaneousFilesWorkspaceProvider!.IsMiscellaneousFilesDocumentAsync(document, CancellationToken.None);
+ }
+
+ public async IAsyncEnumerable GetMiscellaneousDocumentsAsync(Func> documentSelector) where T : TextDocument
+ {
+ foreach (var workspace in _manager._lspWorkspaceRegistrationService.GetAllRegistrations())
+ {
+ foreach (var document in workspace.CurrentSolution.Projects.SelectMany(documentSelector))
+ {
+ if (await IsMiscellaneousFilesDocumentAsync(document).ConfigureAwait(false))
+ yield return document;
+ }
+ }
}
public bool IsWorkspaceRegistered(Workspace workspace)
diff --git a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs
index 1b7a230ddb6b6..35f3406c1bbf1 100644
--- a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs
+++ b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs
@@ -39,9 +39,7 @@ public virtual void Register(Workspace? workspace)
m["WorkspacePartialSemanticsEnabled"] = workspace.PartialSemanticsEnabled;
}, workspace));
- // Forward workspace change events for all registered LSP workspaces. Requires main thread as it
- // fires LspSolutionChanged which hasn't been guaranteed to be thread safe.
- var workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnLspWorkspaceChanged, WorkspaceEventOptions.RequiresMainThreadOptions);
+ var workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnLspWorkspaceChanged);
lock (_gate)
{
@@ -94,9 +92,7 @@ public void Dispose()
}
///
- /// Indicates whether the LSP solution has changed in a non-tracked document context.
- ///
- /// IMPORTANT: Implementations of this event handler should do as little synchronous work as possible since this will block.
+ /// Indicates whether the LSP solution has changed in a non-tracked document context. May be raised on any thread.
///
public EventHandler? LspSolutionChanged;
}
diff --git a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
index af1aaba629419..e393c941f80aa 100644
--- a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
@@ -711,7 +711,7 @@ void M()
Assert.NotNull(completionResult.ItemDefaults.Data);
Assert.NotNull(completionResult.ItemDefaults.CommitCharacters);
- var myClassItems = completionResult.Items.Where(i => i.Label == "MyClass").ToImmutableArray();
+ var myClassItems = completionResult.Items.WhereAsArray(i => i.Label == "MyClass");
var itemFromNS1 = myClassItems.Single(i => i.LabelDetails?.Description == "Namespace1");
var itemFromNS2 = myClassItems.Single(i => i.LabelDetails?.Description == "Namespace2");
diff --git a/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs b/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs
index b9d5731e0dd29..417c89abb919d 100644
--- a/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs
@@ -4,10 +4,15 @@
#nullable disable
+using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Xunit;
@@ -22,6 +27,8 @@ public GoToDefinitionTests(ITestOutputHelper testOutputHelper) : base(testOutput
{
}
+ protected override TestComposition Composition => base.Composition.AddParts(typeof(TestSourceGeneratedDocumentSpanMappingService));
+
[Theory, CombinatorialData]
public async Task TestGotoDefinitionAsync(bool mutatingLspWorkspace)
{
@@ -331,6 +338,42 @@ void M()
Assert.True(results.Single().DocumentUri.GetRequiredParsedUri().OriginalString.EndsWith("String.cs"));
}
+ [Theory, CombinatorialData]
+ public async Task TestGotoDefinitionAsync_WithRazorSourceGeneratedFile(bool mutatingLspWorkspace)
+ {
+ var generatedMarkup = """
+ public class B
+ {
+ public void {|definition:M|}()
+ {
+ }
+ }
+ """;
+ await using var testLspServer = await CreateTestLspServerAsync("""
+ public class A
+ {
+ public void M()
+ {
+ new B().{|caret:M|}();
+ }
+ }
+ """, mutatingLspWorkspace);
+
+ TestFileMarkupParser.GetSpans(generatedMarkup, out var generatedCode, out ImmutableDictionary> spans);
+ var generatedSourceText = SourceText.From(generatedCode);
+
+ var razorGenerator = new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator((c) => c.AddSource("generated_file.cs", generatedCode));
+ var workspace = testLspServer.TestWorkspace;
+ var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(new TestGeneratorReference(razorGenerator));
+ workspace.TryApplyChanges(project.Solution);
+
+ var results = await RunGotoDefinitionAsync(testLspServer, testLspServer.GetLocations("caret").Single());
+ Assert.True(results.Single().DocumentUri.GetRequiredParsedUri().LocalPath.EndsWith("generated_file.cs"));
+
+ var service = Assert.IsType(workspace.Services.GetService());
+ Assert.True(service.DidMapSpans);
+ }
+
private static async Task RunGotoDefinitionAsync(TestLspServer testLspServer, LSP.Location caret)
{
return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName,
diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs
index a40f429582e35..037f2a333e3ca 100644
--- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs
@@ -217,8 +217,6 @@ public Task> GetDiagnosticsAsync(RequestContext c
public Project GetProject() => textDocument.Project;
- public bool IsLiveSource() => true;
-
public string ToDisplayString() => textDocument.ToString()!;
}
}
diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs
index c6630cc56b93f..89a986b06713a 100644
--- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs
@@ -8,8 +8,10 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using ICSharpCode.Decompiler.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public;
+using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using StreamJsonRpc;
@@ -25,7 +27,7 @@ public DiagnosticRegistrationTests(ITestOutputHelper? testOutputHelper) : base(t
}
[Theory, CombinatorialData]
- public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mutatingLspWorkspace)
+ public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mutatingLspWorkspace, bool dynamicRegistration)
{
var clientCapabilities = new ClientCapabilities
{
@@ -33,7 +35,7 @@ public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mut
{
Diagnostic = new DiagnosticSetting
{
- DynamicRegistration = true,
+ DynamicRegistration = dynamicRegistration,
}
}
};
@@ -49,42 +51,54 @@ public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mut
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, initializationOptions);
var registrations = clientCallbackTarget.GetRegistrations();
+ var serverCapabilities = testLspServer.GetServerCapabilities();
// Get all registrations for diagnostics (note that workspace registrations are registered against document method name).
var diagnosticRegistrations = registrations
.Where(r => r.Method == Methods.TextDocumentDiagnosticName)
.Select(r => JsonSerializer.Deserialize((JsonElement)r.RegisterOptions!, ProtocolConversions.LspJsonSerializerOptions)!);
- Assert.NotEmpty(diagnosticRegistrations);
-
- string[] documentSources = [
- PullDiagnosticCategories.DocumentCompilerSyntax,
- PullDiagnosticCategories.DocumentCompilerSemantic,
- PullDiagnosticCategories.DocumentAnalyzerSyntax,
- PullDiagnosticCategories.DocumentAnalyzerSemantic,
- PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal
- ];
-
- string[] documentAndWorkspaceSources = [
- PullDiagnosticCategories.EditAndContinue,
- PullDiagnosticCategories.WorkspaceDocumentsAndProject
- ];
-
- // Verify document only sources are present (and do not set the workspace diagnostic option).
- foreach (var documentSource in documentSources)
+ if (dynamicRegistration)
{
- var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == documentSource);
- Assert.False(options.WorkspaceDiagnostics);
- Assert.True(options.InterFileDependencies);
- }
+ Assert.NotEmpty(diagnosticRegistrations);
+
+ string[] documentSources = [
+ PullDiagnosticCategories.DocumentCompilerSyntax,
+ PullDiagnosticCategories.DocumentCompilerSemantic,
+ PullDiagnosticCategories.DocumentAnalyzerSyntax,
+ PullDiagnosticCategories.DocumentAnalyzerSemantic,
+ PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal
+ ];
+
+ string[] documentAndWorkspaceSources = [
+ PullDiagnosticCategories.EditAndContinue,
+ PullDiagnosticCategories.WorkspaceDocumentsAndProject
+ ];
+
+ // Verify document only sources are present (and do not set the workspace diagnostic option).
+ foreach (var documentSource in documentSources)
+ {
+ var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == documentSource);
+ Assert.False(options.WorkspaceDiagnostics);
+ Assert.True(options.InterFileDependencies);
+ }
- // Verify workspace sources are present (and do set the workspace diagnostic option).
- foreach (var workspaceSource in documentAndWorkspaceSources)
+ // Verify workspace sources are present (and do set the workspace diagnostic option).
+ foreach (var workspaceSource in documentAndWorkspaceSources)
+ {
+ var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == workspaceSource);
+ Assert.True(options.WorkspaceDiagnostics);
+ Assert.True(options.InterFileDependencies);
+ Assert.True(options.WorkDoneProgress);
+ }
+ }
+ else
{
- var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == workspaceSource);
- Assert.True(options.WorkspaceDiagnostics);
- Assert.True(options.InterFileDependencies);
- Assert.True(options.WorkDoneProgress);
+ var diagnosticOptions = (DiagnosticOptions?)serverCapabilities.DiagnosticOptions;
+
+ Assert.Empty(diagnosticRegistrations);
+ Assert.NotNull(diagnosticOptions);
+ Assert.True(diagnosticOptions.InterFileDependencies);
}
// Verify task diagnostics are not present.
diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs
index 1017a75137049..ab67bd1e23670 100644
--- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs
@@ -125,11 +125,6 @@ public override Task> GetDiagnosticsAsync(Request
isEnabledByDefault: true, warningLevel: 0, [], ImmutableDictionary.Empty,context.Document!.Project.Id,
new DiagnosticDataLocation(new FileLinePositionSpan(context.Document!.FilePath!, new Text.LinePosition(0, 0), new Text.LinePosition(0, 0))))]);
}
-
- public override bool IsLiveSource()
- {
- return true;
- }
}
[Export(typeof(IDiagnosticSourceProvider)), Shared, PartNotDiscoverable]
diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs
index d8d0c218a7c00..3e60dcc6da199 100644
--- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs
@@ -680,6 +680,7 @@ public partial class C
// 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 firstResultId = results.Single().ResultId;
var diagnostic = AssertEx.Single(results.Single().Diagnostics);
Assert.Equal("CS0103", diagnostic.Code);
@@ -703,7 +704,8 @@ public partial class C
}
await testLspServer.WaitForSourceGeneratorsAsync();
- results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics);
+ results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: firstResultId);
+ var secondResultId = results.Single().ResultId;
if (executionPreference == SourceGeneratorExecutionPreference.Automatic)
{
@@ -712,15 +714,18 @@ public partial class C
}
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);
+ // In balanced mode, the diagnostic should be unchanged until there is a manual source generator run that updates the sg text.
+ Assert.Null(results.Single().Diagnostics);
+ Assert.Equal(firstResultId, secondResultId);
testLspServer.TestWorkspace.EnqueueUpdateSourceGeneratorVersion(document.Project.Id, forceRegeneration: false);
await testLspServer.WaitForSourceGeneratorsAsync();
- results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics);
+ results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: secondResultId);
+ var thirdResultId = results.Single().ResultId;
+ AssertEx.NotNull(results.Single().Diagnostics);
Assert.Empty(results.Single().Diagnostics!);
+ Assert.NotEqual(firstResultId, thirdResultId);
}
}
@@ -2014,7 +2019,7 @@ class A : B { }
// Get updated workspace diagnostics for the change.
var previousResults = CreateDiagnosticParamsFromPreviousReports(results);
- var previousResultIds = previousResults.Select(param => param.resultId).ToImmutableArray();
+ var previousResultIds = previousResults.SelectAsArray(param => param.resultId);
results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResults);
// Verify that since no actual changes have been made we report unchanged diagnostics.
diff --git a/src/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs b/src/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs
index 4fb10c9fe770c..5b0de3bd09d1a 100644
--- a/src/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/FoldingRanges/FoldingRangesTests.cs
@@ -53,12 +53,24 @@ public void M(){|implementation:
}|}
""");
+ [Theory, CombinatorialData]
+ public Task TestGetFoldingRangeAsync_AutoCollapse(bool mutatingLspWorkspace)
+ => AssertFoldingRanges(mutatingLspWorkspace, """
+ class C{|foldingRange:
+ {
+ private Action Foo(){|implementation: => i =>{|foldingRange:
+ {
+ };|}|}
+ }|}
+ """);
+
private async Task AssertFoldingRanges(bool mutatingLspWorkspace, string markup, string collapsedText = null)
{
var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
var expected = testLspServer.GetLocations()
.SelectMany(kvp => kvp.Value.Select(location => CreateFoldingRange(kvp.Key, location.Range, collapsedText ?? "...")))
.OrderByDescending(range => range.StartLine)
+ .ThenByDescending(range => range.StartCharacter)
.ToArray();
var results = await RunGetFoldingRangeAsync(testLspServer);
diff --git a/src/LanguageServer/ProtocolUnitTests/Initialize/LocaleTests.cs b/src/LanguageServer/ProtocolUnitTests/Initialize/LocaleTests.cs
index 681db0ed4b8e8..c96b532168aa4 100644
--- a/src/LanguageServer/ProtocolUnitTests/Initialize/LocaleTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Initialize/LocaleTests.cs
@@ -41,10 +41,10 @@ public async Task TestUsesLspLocalePerServer(bool mutatingLspWorkspace)
Locale = "ja"
});
- await using var testLspServerTwo = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions
+ await using var testLspServerTwo = await CreateTestLspServerAsync(testLspServerOne.TestWorkspace, new InitializationOptions
{
Locale = "zh"
- });
+ }, LanguageNames.CSharp);
var resultOne = await testLspServerOne.ExecuteRequestAsync(LocaleTestHandler.MethodName, new Request(), CancellationToken.None);
var resultTwo = await testLspServerTwo.ExecuteRequestAsync(LocaleTestHandler.MethodName, new Request(), CancellationToken.None);
diff --git a/src/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs b/src/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs
index d6bcb287fa3be..9f12a828f806a 100644
--- a/src/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs
@@ -66,7 +66,7 @@ public async Task LanguageServerCleansUpOnUnexpectedJsonRpcDisconnectAsync(bool
public async Task LanguageServerHasSeparateServiceInstances(bool mutatingLspWorkspace)
{
await using var serverOne = await CreateTestLspServerAsync("", mutatingLspWorkspace);
- await using var serverTwo = await CreateTestLspServerAsync("", mutatingLspWorkspace);
+ await using var serverTwo = await CreateTestLspServerAsync(serverOne.TestWorkspace, initializationOptions: default, LanguageNames.CSharp);
// Get an LSP service and verify each server has its own instance per server.
Assert.NotSame(serverOne.GetRequiredLspService(), serverTwo.GetRequiredLspService());
diff --git a/src/LanguageServer/ProtocolUnitTests/LspServicesTests.cs b/src/LanguageServer/ProtocolUnitTests/LspServicesTests.cs
index e3d31c12adab9..ec70cac816d6e 100644
--- a/src/LanguageServer/ProtocolUnitTests/LspServicesTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/LspServicesTests.cs
@@ -78,7 +78,7 @@ public async Task ReturnsLspServiceForMatchingServer(bool mutatingLspWorkspace)
var lspService = server.GetRequiredLspService();
Assert.True(lspService is CSharpLspService);
- await using var server2 = await CreateTestLspServerAsync("", mutatingLspWorkspace, initializationOptions: new() { ServerKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer }, composition);
+ await using var server2 = await CreateTestLspServerAsync(server.TestWorkspace, initializationOptions: new() { ServerKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer }, LanguageNames.CSharp);
var lspService2 = server2.GetRequiredLspService();
Assert.True(lspService2 is AlwaysActiveCSharpLspService);
diff --git a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs
index 31001ce1d526c..01bed10bb2fbb 100644
--- a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs
@@ -5,7 +5,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.MetadataAsSource;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Xunit;
@@ -48,12 +47,13 @@ void M()
await testLspServer.OpenDocumentAsync(definition.Single().DocumentUri, text: string.Empty).ConfigureAwait(false);
Assert.Equal(WorkspaceKind.MetadataAsSource, (await GetWorkspaceForDocument(testLspServer, definition.Single().DocumentUri)).Kind);
- AssertMiscFileWorkspaceEmpty(testLspServer);
+ await AssertMiscFileWorkspaceEmpty(testLspServer);
- // Close the metadata file and verify it gets removed from the metadata workspace.
+ // Close the metadata file - the file will still be present in MAS.
await testLspServer.CloseDocumentAsync(definition.Single().DocumentUri).ConfigureAwait(false);
- AssertMetadataFileWorkspaceEmpty(testLspServer);
+ Assert.Equal(WorkspaceKind.MetadataAsSource, (await GetWorkspaceForDocument(testLspServer, definition.Single().DocumentUri)).Kind);
+ await AssertMiscFileWorkspaceEmpty(testLspServer);
}
[Theory, CombinatorialData]
@@ -93,7 +93,7 @@ public static void WriteLine(string value) {}
""").ConfigureAwait(false);
var workspaceForDocument = await GetWorkspaceForDocument(testLspServer, definition.Single().DocumentUri);
Assert.Equal(WorkspaceKind.MetadataAsSource, workspaceForDocument.Kind);
- AssertMiscFileWorkspaceEmpty(testLspServer);
+ await AssertMiscFileWorkspaceEmpty(testLspServer);
// Manually register the workspace for followup requests - the workspace event listener that
// normally registers it on creation is not running in test code.
@@ -123,16 +123,9 @@ private static async Task GetWorkspaceForDocument(TestLspServer testL
return lspWorkspace!;
}
- private static void AssertMiscFileWorkspaceEmpty(TestLspServer testLspServer)
- {
- var doc = testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.Documents.SingleOrDefault();
- Assert.Null(doc);
- }
-
- private static void AssertMetadataFileWorkspaceEmpty(TestLspServer testLspServer)
+ private static async Task AssertMiscFileWorkspaceEmpty(TestLspServer testLspServer)
{
- var provider = testLspServer.TestWorkspace.ExportProvider.GetExportedValue();
- var metadataDocument = provider.TryGetWorkspace()?.CurrentSolution.Projects.SingleOrDefault()?.Documents.SingleOrDefault();
- Assert.Null(metadataDocument);
+ var docs = await testLspServer.GetManagerAccessor().GetMiscellaneousDocumentsAsync(static p => p.Documents).ToImmutableArrayAsync(CancellationToken.None);
+ Assert.Empty(docs);
}
}
diff --git a/src/LanguageServer/ProtocolUnitTests/MiscellaneousFiles/LspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/MiscellaneousFiles/LspMiscellaneousFilesWorkspaceTests.cs
new file mode 100644
index 0000000000000..6d492bd7664c3
--- /dev/null
+++ b/src/LanguageServer/ProtocolUnitTests/MiscellaneousFiles/LspMiscellaneousFilesWorkspaceTests.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.LanguageServer.UnitTests.Miscellaneous;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Roslyn.Test.Utilities;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.MiscellaneousFiles;
+
+///
+/// This class runs all the tests in against the base implementation.
+///
+public sealed class LspMiscellaneousFilesWorkspaceTests : AbstractLspMiscellaneousFilesWorkspaceTests
+{
+ public LspMiscellaneousFilesWorkspaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+ {
+ }
+
+ private protected override async ValueTask AddDocumentAsync(TestLspServer testLspServer, string filePath, string content)
+ {
+ var projectId = testLspServer.TestWorkspace.CurrentSolution.ProjectIds.Single();
+ var documentId = DocumentId.CreateNewId(projectId, filePath);
+ await testLspServer.TestWorkspace.AddDocumentAsync(
+ DocumentInfo.Create(
+ documentId,
+ name: filePath,
+ filePath: filePath,
+ loader: new TestTextLoader(content)));
+
+ return testLspServer.TestWorkspace.CurrentSolution.GetRequiredDocument(documentId);
+ }
+
+ private protected override Workspace GetHostWorkspace(TestLspServer testLspServer)
+ {
+ return testLspServer.TestWorkspace;
+ }
+}
diff --git a/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs b/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs
index f44f51312ecc7..7807f7f209a6c 100644
--- a/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs
@@ -24,7 +24,9 @@ public sealed class SolutionAnalyzerConfigOptionsUpdaterTests
private static TestWorkspace CreateWorkspace()
{
var workspace = new LspTestWorkspace(LspTestCompositions.LanguageServerProtocol
- .RemoveParts(typeof(MockFallbackAnalyzerConfigOptionsProvider)));
+ .RemoveParts(typeof(MockFallbackAnalyzerConfigOptionsProvider))
+ .ExportProviderFactory
+ .CreateExportProvider());
var updater = (SolutionAnalyzerConfigOptionsUpdater)workspace.ExportProvider.GetExports().Single(e => e.Value is SolutionAnalyzerConfigOptionsUpdater).Value;
var listenerProvider = workspace.GetService();
diff --git a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs
index ac634aebb0e1c..b36ed1edb78c2 100644
--- a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs
@@ -6,12 +6,17 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.ReferenceHighlighting;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Text.Adornments;
using Roslyn.Utilities;
@@ -23,6 +28,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.References;
public sealed class FindAllReferencesHandlerTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper)
{
+ protected override TestComposition Composition => base.Composition.AddParts(typeof(TestSourceGeneratedDocumentSpanMappingService));
+
[Theory, CombinatorialData]
public async Task TestFindAllReferencesAsync(bool mutatingLspWorkspace)
{
@@ -341,6 +348,43 @@ void M()
AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Skip(1).Select(r => r.Location));
}
+ [Theory, CombinatorialData]
+ public async Task TestFindReferencesAsync_WithRazorSourceGeneratedFile(bool mutatingLspWorkspace)
+ {
+ var generatedMarkup = """
+ public class B
+ {
+ public void {|reference:M|}()
+ {
+ }
+ }
+ """;
+ await using var testLspServer = await CreateTestLspServerAsync("""
+ public class A
+ {
+ public void M()
+ {
+ new B().{|caret:M|}();
+ }
+ }
+ """, mutatingLspWorkspace, CapabilitiesWithVSExtensions);
+
+ TestFileMarkupParser.GetSpans(generatedMarkup, out var generatedCode, out ImmutableDictionary> spans);
+ var generatedSourceText = SourceText.From(generatedCode);
+
+ var razorGenerator = new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator((c) => c.AddSource("generated_file.cs", generatedCode));
+ var workspace = testLspServer.TestWorkspace;
+ var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(new TestGeneratorReference(razorGenerator));
+ workspace.TryApplyChanges(project.Solution);
+
+ var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First());
+ Assert.Equal(2, results.Length);
+ Assert.True(results[1].Location.DocumentUri.GetRequiredParsedUri().LocalPath.EndsWith("generated_file.cs"));
+
+ var service = Assert.IsType(workspace.Services.GetService());
+ Assert.True(service.DidMapSpans);
+ }
+
private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret, IProgress progress)
=> new LSP.ReferenceParams()
{
diff --git a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs
index 2e344bb92fbc6..bd79c74a4839e 100644
--- a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs
@@ -4,11 +4,17 @@
#nullable disable
+using System;
using System.Collections.Immutable;
+using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.LanguageServer.Handler;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Text;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Xunit;
@@ -19,6 +25,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Rename;
public sealed class RenameTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper)
{
+ protected override TestComposition Composition => base.Composition.AddParts(typeof(TestSourceGeneratedDocumentSpanMappingService));
+
[Theory, CombinatorialData]
public async Task TestRenameAsync(bool mutatingLspWorkspace)
{
@@ -189,10 +197,10 @@ class B
{
void M()
{
- new A().{|renamed:M|}();
+ new A().M();
var a = new A();
- a.{|renamed:M|}();
+ a.M();
}
}
""";
@@ -215,15 +223,62 @@ void M2()
});
var renameLocation = testLspServer.GetLocations("caret").First();
- var renamePosition = ProtocolConversions.PositionToLinePosition(renameLocation.Range.Start);
- var document = await testLspServer.GetDocumentAsync(renameLocation.DocumentUri);
var renameValue = "RENAME";
var expectedEdits = testLspServer.GetLocations("renamed").Select(location => new LSP.TextEdit() { NewText = renameValue, Range = location.Range });
- var results = await RenameHandler.GetRenameEditAsync(document, renamePosition, renameValue, includeSourceGenerated: true, CancellationToken.None);
+ var results = await RunRenameAsync(testLspServer, CreateRenameParams(renameLocation, renameValue));
AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).SelectMany(e => e.Edits));
}
+ [Theory, CombinatorialData]
+ public async Task TestRename_WithRazorSourceGeneratedFile(bool mutatingLspWorkspace)
+ {
+ var generatedMarkup = """
+ class B
+ {
+ void M()
+ {
+ new A().{|renamed:M|}();
+
+ var a = new A();
+ a.{|renamed:M|}();
+ }
+ }
+ """;
+ await using var testLspServer = await CreateTestLspServerAsync("""
+ public class A
+ {
+ public void {|caret:|}{|renamed:M|}()
+ {
+ }
+
+ void M2()
+ {
+ {|renamed:M|}()
+ }
+ }
+ """, mutatingLspWorkspace);
+
+ TestFileMarkupParser.GetSpans(generatedMarkup, out var generatedCode, out ImmutableDictionary> spans);
+ var generatedSourceText = SourceText.From(generatedCode);
+
+ var razorGenerator = new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator((c) => c.AddSource("generated_file.cs", generatedCode));
+ var workspace = testLspServer.TestWorkspace;
+ var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(new TestGeneratorReference(razorGenerator));
+ workspace.TryApplyChanges(project.Solution);
+
+ var renameLocation = testLspServer.GetLocations("caret").First();
+ var renameValue = "RENAME";
+ var expectedEdits = testLspServer.GetLocations("renamed").Select(location => new LSP.TextEdit() { NewText = renameValue, Range = location.Range });
+ var expectedGeneratedEdits = spans["renamed"].Select(span => new LSP.TextEdit() { NewText = renameValue, Range = ProtocolConversions.TextSpanToRange(span, generatedSourceText) });
+
+ var results = await RunRenameAsync(testLspServer, CreateRenameParams(renameLocation, renameValue));
+ AssertJsonEquals(expectedEdits.Concat(expectedGeneratedEdits), ((TextDocumentEdit[])results.DocumentChanges).SelectMany(e => e.Edits));
+
+ var service = Assert.IsType(workspace.Services.GetService());
+ Assert.True(service.DidMapSpans);
+ }
+
[Theory, CombinatorialData]
public async Task TestRename_OriginateInSourceGeneratedFile(bool mutatingLspWorkspace)
{
@@ -251,20 +306,27 @@ void M2()
{|renamed:M|}()
}
}
- """, mutatingLspWorkspace,
- new InitializationOptions()
- {
- SourceGeneratedMarkups = [generatedMarkup]
- });
+ """, mutatingLspWorkspace);
- var renameLocation = testLspServer.GetLocations("caret").First();
- var renamePosition = ProtocolConversions.PositionToLinePosition(renameLocation.Range.Start);
- var document = await testLspServer.GetDocumentAsync(renameLocation.DocumentUri);
+ TestFileMarkupParser.GetSpans(generatedMarkup, out var generatedCode, out ImmutableDictionary> spans);
+ var generatedSourceText = SourceText.From(generatedCode);
+
+ var razorGenerator = new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator((c) => c.AddSource("generated_file.cs", generatedCode));
+ var workspace = testLspServer.TestWorkspace;
+ var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(new TestGeneratorReference(razorGenerator));
+ workspace.TryApplyChanges(project.Solution);
+ var generatedDocument = (await project.GetSourceGeneratedDocumentsAsync()).First();
+
+ var renameLocation = await ProtocolConversions.TextSpanToLocationAsync(generatedDocument, spans["caret"].First(), isStale: false, CancellationToken.None);
var renameValue = "RENAME";
var expectedEdits = testLspServer.GetLocations("renamed").Select(location => new LSP.TextEdit() { NewText = renameValue, Range = location.Range });
+ var expectedGeneratedEdits = spans["renamed"].Select(span => new LSP.TextEdit() { NewText = renameValue, Range = ProtocolConversions.TextSpanToRange(span, generatedSourceText) });
- var results = await RenameHandler.GetRenameEditAsync(document, renamePosition, renameValue, includeSourceGenerated: true, CancellationToken.None);
- AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).SelectMany(e => e.Edits));
+ var results = await RunRenameAsync(testLspServer, CreateRenameParams(renameLocation, renameValue));
+ AssertJsonEquals(expectedEdits.Concat(expectedGeneratedEdits), ((TextDocumentEdit[])results.DocumentChanges).SelectMany(e => e.Edits));
+
+ var service = Assert.IsType(workspace.Services.GetService());
+ Assert.True(service.DidMapSpans);
}
private static LSP.RenameParams CreateRenameParams(LSP.Location location, string newName)
diff --git a/src/LanguageServer/ProtocolUnitTests/TestSourceGeneratedDocumentSpanMappingService.cs b/src/LanguageServer/ProtocolUnitTests/TestSourceGeneratedDocumentSpanMappingService.cs
new file mode 100644
index 0000000000000..d7962b84b14ec
--- /dev/null
+++ b/src/LanguageServer/ProtocolUnitTests/TestSourceGeneratedDocumentSpanMappingService.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable disable
+
+using System;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests;
+
+[ExportWorkspaceService(typeof(ISourceGeneratedDocumentSpanMappingService))]
+[Shared]
+[method: ImportingConstructor]
+[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+internal class TestSourceGeneratedDocumentSpanMappingService() : ISourceGeneratedDocumentSpanMappingService
+{
+ public bool DidMapSpans { get; private set; }
+
+ public bool CanMapSpans(SourceGeneratedDocument sourceGeneratedDocument)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetMappedTextChangesAsync(SourceGeneratedDocument oldDocument, SourceGeneratedDocument newDocument, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray spans, CancellationToken cancellationToken)
+ {
+ if (document.IsRazorSourceGeneratedDocument())
+ {
+ DidMapSpans = true;
+ }
+
+ return Task.FromResult(ImmutableArray.Empty);
+ }
+}
diff --git a/src/LanguageServer/ProtocolUnitTests/UriTests.cs b/src/LanguageServer/ProtocolUnitTests/UriTests.cs
index 03030c16a44fa..020f9db691c8e 100644
--- a/src/LanguageServer/ProtocolUnitTests/UriTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/UriTests.cs
@@ -50,9 +50,9 @@ void M()
""", languageId: "csharp").ConfigureAwait(false);
// Verify file is added to the misc file workspace.
- var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = looseFileUri }, CancellationToken.None);
- Assert.Equal(testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace(), workspace);
- AssertEx.NotNull(document);
+ var (_, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = looseFileUri }, CancellationToken.None);
+ Assert.NotNull(document);
+ Assert.True(await testLspServer.GetManager().GetTestAccessor().IsMiscellaneousFilesDocumentAsync(document));
Assert.Equal(looseFileUri, document.GetURI());
Assert.Equal(filePath, document.FilePath);
}
@@ -76,9 +76,9 @@ void M()
""", languageId: "csharp").ConfigureAwait(false);
// Verify file is added to the misc file workspace.
- var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = looseFileUri }, CancellationToken.None);
- Assert.Equal(testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace(), workspace);
- AssertEx.NotNull(document);
+ var (_, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = looseFileUri }, CancellationToken.None);
+ Assert.NotNull(document);
+ Assert.True(await testLspServer.GetManager().GetTestAccessor().IsMiscellaneousFilesDocumentAsync(document));
Assert.Equal(looseFileUri, document.GetURI());
Assert.Equal(looseFileUri.UriString, document.FilePath);
}
@@ -99,7 +99,7 @@ public class A
""";
- await using var testLspServer = await CreateXmlTestLspServerAsync(markup, mutatingLspWorkspace);
+ await using var testLspServer = await CreateXmlTestLspServerAsync(markup, mutatingLspWorkspace, initializationOptions: new() { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
var workspaceDocument = testLspServer.TestWorkspace.CurrentSolution.Projects.Single().Documents.Single();
var expectedDocumentUri = ProtocolConversions.CreateAbsoluteDocumentUri(documentFilePath);
@@ -109,8 +109,8 @@ public class A
// Verify file is not added to the misc file workspace.
{
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = expectedDocumentUri }, CancellationToken.None);
- Assert.NotEqual(testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace(), workspace);
- AssertEx.NotNull(document);
+ Assert.NotNull(document);
+ Assert.False(await testLspServer.GetManager().GetTestAccessor().IsMiscellaneousFilesDocumentAsync(document));
Assert.Equal(expectedDocumentUri, document.GetURI());
Assert.Equal(documentFilePath, document.FilePath);
}
@@ -119,9 +119,9 @@ public class A
{
var lowercaseUri = ProtocolConversions.CreateAbsoluteDocumentUri(documentFilePath.ToLowerInvariant());
Assert.NotEqual(expectedDocumentUri.GetRequiredParsedUri().AbsolutePath, lowercaseUri.GetRequiredParsedUri().AbsolutePath);
- var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = lowercaseUri }, CancellationToken.None);
- Assert.NotEqual(testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace(), workspace);
- AssertEx.NotNull(document);
+ var (_, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = lowercaseUri }, CancellationToken.None);
+ Assert.NotNull(document);
+ Assert.False(await testLspServer.GetManager().GetTestAccessor().IsMiscellaneousFilesDocumentAsync(document));
Assert.Equal(expectedDocumentUri, document.GetURI());
Assert.Equal(documentFilePath, document.FilePath);
}
@@ -301,8 +301,8 @@ public async Task TestDoesNotCrashIfUnableToDetermineLanguageInfo(bool mutatingL
// Verify file is added to the misc file workspace.
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { DocumentUri = looseFileUri }, CancellationToken.None);
- Assert.Equal(testLspServer.GetManager().GetTestAccessor().GetLspMiscellaneousFilesWorkspace(), workspace);
- AssertEx.NotNull(document);
+ Assert.NotNull(document);
+ Assert.True(await testLspServer.GetManager().GetTestAccessor().IsMiscellaneousFilesDocumentAsync(document));
Assert.Equal(looseFileUri, document.GetURI());
Assert.Equal(looseFileUri.UriString, document.FilePath);
diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs
index e661f144a2c14..ceddfdad77ebb 100644
--- a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs
@@ -3,7 +3,6 @@
// 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;
@@ -15,17 +14,15 @@
using Roslyn.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
+using Roslyn.Utilities;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Workspaces;
-public sealed class LspWorkspaceManagerTests : AbstractLanguageServerProtocolTests
+public sealed class LspWorkspaceManagerTests(ITestOutputHelper testOutputHelper)
+ : AbstractLanguageServerProtocolTests(testOutputHelper)
{
- public LspWorkspaceManagerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
- {
- }
-
[Theory, CombinatorialData]
public async Task TestUsesLspTextOnOpenCloseAsync(bool mutatingLspWorkspace)
{
@@ -232,51 +229,6 @@ public async Task TestLspFindsNewDocumentAsync(bool mutatingLspWorkspace)
Assert.Equal(testLspServer.TestWorkspace.CurrentSolution, lspDocument.Project.Solution);
}
- [Theory, CombinatorialData]
- public async Task TestLspTransfersDocumentToNewWorkspaceAsync(bool mutatingLspWorkspace)
- {
- var markup = "One";
-
- // Create a server that includes the LSP misc files workspace so we can test transfers to and from it.
- await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
-
- // Create a new document, but do not update the workspace solution yet.
- var newDocumentId = DocumentId.CreateNewId(testLspServer.TestWorkspace.CurrentSolution.ProjectIds[0]);
-
- // Include some Unicode characters to test URL handling.
- var newDocumentFilePath = "C:\\NewDoc\\\ue25b\ud86d\udeac.cs";
- var newDocumentInfo = DocumentInfo.Create(newDocumentId, "NewDoc.cs", filePath: newDocumentFilePath, loader: new TestTextLoader("New Doc"));
- var newDocumentUri = ProtocolConversions.CreateAbsoluteDocumentUri(newDocumentFilePath);
-
- // Open the document via LSP before the workspace sees it.
- await testLspServer.OpenDocumentAsync(newDocumentUri, "LSP text");
-
- // Verify it is in the lsp misc workspace.
- var (miscWorkspace, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
- AssertEx.NotNull(miscDocument);
- Assert.Equal(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace(), miscWorkspace);
- Assert.Equal("LSP text", (await miscDocument.GetTextAsync(CancellationToken.None)).ToString());
-
- // Make a change and verify the misc document is updated.
- await testLspServer.InsertTextAsync(newDocumentUri, (0, 0, "More LSP text"));
- (_, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
- AssertEx.NotNull(miscDocument);
- var miscText = await miscDocument.GetTextAsync(CancellationToken.None);
- Assert.Equal("More LSP textLSP text", miscText.ToString());
-
- // Update the registered workspace with the new document.
- await testLspServer.TestWorkspace.AddDocumentAsync(newDocumentInfo);
-
- // Verify that the newly added document in the registered workspace is returned.
- var (documentWorkspace, document) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false);
- AssertEx.NotNull(document);
- Assert.Equal(testLspServer.TestWorkspace, documentWorkspace);
- Assert.Equal(newDocumentId, document.Id);
- // Verify we still are using the tracked LSP text for the document.
- var documentText = await document.GetTextAsync(CancellationToken.None);
- Assert.Equal("More LSP textLSP text", documentText.ToString());
- }
-
[Theory, CombinatorialData]
public async Task TestUsesRegisteredHostWorkspace(bool mutatingLspWorkspace)
{
@@ -302,10 +254,10 @@ public async Task TestUsesRegisteredHostWorkspace(bool mutatingLspWorkspace)
// Verify 1 workspace registered to start with.
Assert.True(IsWorkspaceRegistered(testLspServer.TestWorkspace, testLspServer));
- using var testWorkspaceTwo = LspTestWorkspace.Create(
+ using var testWorkspaceTwo = TestWorkspace.Create(
XElement.Parse(secondWorkspaceXml),
- workspaceKind: "OtherWorkspaceKind",
- composition: testLspServer.TestWorkspace.Composition);
+ exportProvider: testLspServer.TestWorkspace.ExportProvider,
+ workspaceKind: "OtherWorkspaceKind");
// Wait for workspace creation operations for the second workspace to complete.
await WaitForWorkspaceOperationsAsync(testWorkspaceTwo);
@@ -362,7 +314,7 @@ public async Task TestLspUpdatesCorrectWorkspaceWithMultipleWorkspacesAsync(bool
""";
await using var testLspServer = await CreateXmlTestLspServerAsync(firstWorkspaceXml, mutatingLspWorkspace);
- using var testWorkspaceTwo = CreateWorkspace(options: null, WorkspaceKind.MSBuild, mutatingLspWorkspace);
+ using var testWorkspaceTwo = await CreateWorkspaceAsync(options: null, WorkspaceKind.MSBuild, mutatingLspWorkspace);
testWorkspaceTwo.InitializeDocuments(XElement.Parse($"""
@@ -421,7 +373,7 @@ public async Task TestWorkspaceEventUpdatesCorrectWorkspaceWithMultipleWorkspace
""";
await using var testLspServer = await CreateXmlTestLspServerAsync(firstWorkspaceXml, mutatingLspWorkspace);
- using var testWorkspaceTwo = CreateWorkspace(options: null, workspaceKind: WorkspaceKind.MSBuild, mutatingLspWorkspace);
+ using var testWorkspaceTwo = await CreateWorkspaceAsync(options: null, workspaceKind: WorkspaceKind.MSBuild, mutatingLspWorkspace);
testWorkspaceTwo.InitializeDocuments(XElement.Parse($"""
@@ -466,7 +418,7 @@ public async Task TestWorkspaceEventUpdatesCorrectWorkspaceWithMultipleWorkspace
[Theory, CombinatorialData]
public async Task TestSeparateWorkspaceManagerPerServerAsync(bool mutatingLspWorkspace)
{
- using var testWorkspace = CreateWorkspace(options: null, workspaceKind: null, mutatingLspWorkspace);
+ using var testWorkspace = await CreateWorkspaceAsync(options: null, workspaceKind: null, mutatingLspWorkspace);
testWorkspace.InitializeDocuments(XElement.Parse($"""
@@ -583,7 +535,7 @@ await testLspServer.TestWorkspace.ChangeSolutionAsync(
(await testLspServer.TestWorkspace.CurrentSolution.Projects.Single().Documents.Single().GetTextAsync()).ToString());
// The file should not be in the misc workspace.
- Assert.Empty(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects);
+ Assert.Empty(await testLspServer.GetManagerAccessor().GetMiscellaneousDocumentsAsync(static p => p.Documents).ToImmutableArrayAsync(CancellationToken.None));
// Now, if the project system removes the file, we will still see the lsp version of, but back to the misc workspace.
await testLspServer.TestWorkspace.ChangeSolutionAsync(
@@ -673,8 +625,8 @@ await testLspServer.ReplaceTextAsync(documentUri,
// incremental parsing may be. For example, sometimes it will conservatively not reuse a node because that node
// touches a node that is getting recreated.
var syntaxFacts = originalDocument.GetRequiredLanguageService();
- var oldClassDeclarations = originalRoot.DescendantNodes().Where(n => syntaxFacts.IsClassDeclaration(n)).ToImmutableArray();
- var newClassDeclarations = newRoot.DescendantNodes().Where(n => syntaxFacts.IsClassDeclaration(n)).ToImmutableArray();
+ var oldClassDeclarations = originalRoot.DescendantNodes().WhereAsArray(n => syntaxFacts.IsClassDeclaration(n));
+ var newClassDeclarations = newRoot.DescendantNodes().WhereAsArray(n => syntaxFacts.IsClassDeclaration(n));
Assert.Equal(oldClassDeclarations.Length, newClassDeclarations.Length);
Assert.Equal(3, oldClassDeclarations.Length);
@@ -683,8 +635,8 @@ await testLspServer.ReplaceTextAsync(documentUri,
Assert.False(oldClassDeclarations[2].IsIncrementallyIdenticalTo(newClassDeclarations[2]));
// All the methods will get reused.
- var oldMethodDeclarations = originalRoot.DescendantNodes().Where(n => syntaxFacts.IsMethodLevelMember(n)).ToImmutableArray();
- var newMethodDeclarations = newRoot.DescendantNodes().Where(n => syntaxFacts.IsMethodLevelMember(n)).ToImmutableArray();
+ var oldMethodDeclarations = originalRoot.DescendantNodes().WhereAsArray(n => syntaxFacts.IsMethodLevelMember(n));
+ var newMethodDeclarations = newRoot.DescendantNodes().WhereAsArray(n => syntaxFacts.IsMethodLevelMember(n));
Assert.Equal(oldMethodDeclarations.Length, newMethodDeclarations.Length);
Assert.Equal(3, oldMethodDeclarations.Length);
diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs
index fb298c5fbcc1e..99af09f681261 100644
--- a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs
+++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -11,7 +12,6 @@
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
-using Roslyn.Utilities;
using Xunit;
using Xunit.Abstractions;
using LSP = Roslyn.LanguageServer.Protocol;
@@ -223,6 +223,103 @@ internal async Task TestReturnsGeneratedSourceWhenManuallyRefreshed(bool mutatin
Assert.Equal("// callCount: 1", secondRequest.Text);
}
+ [Theory, CombinatorialData]
+ internal async Task TestCanRunSourceGeneratorAndApplyChangesConcurrently(
+ bool mutatingLspWorkspace,
+ bool majorVersionUpdate,
+ SourceGeneratorExecutionPreference sourceGeneratorExecution)
+ {
+ await using var testLspServer = await CreateTestLspServerAsync("""
+ class C
+ {
+ }
+ """, mutatingLspWorkspace);
+
+ var configService = testLspServer.TestWorkspace.ExportProvider.GetExportedValue();
+ configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: sourceGeneratorExecution);
+
+ var callCount = 0;
+ var generatorReference = await AddGeneratorAsync(new CallbackGenerator(() => ("hintName.cs", "// callCount: " + callCount++)), testLspServer.TestWorkspace);
+
+ var sourceGeneratedDocuments = await testLspServer.GetCurrentSolution().Projects.Single().GetSourceGeneratedDocumentsAsync();
+ var sourceGeneratedDocumentIdentity = sourceGeneratedDocuments.Single().Identity;
+ var sourceGeneratorDocumentUri = SourceGeneratedDocumentUri.Create(sourceGeneratedDocumentIdentity);
+
+ var text = await testLspServer.ExecuteRequestAsync(SourceGeneratedDocumentGetTextHandler.MethodName,
+ new SourceGeneratorGetTextParams(new LSP.TextDocumentIdentifier { DocumentUri = sourceGeneratorDocumentUri }, ResultId: null), CancellationToken.None);
+
+ AssertEx.NotNull(text);
+ Assert.Equal("// callCount: 0", text.Text);
+
+ var initialSolution = testLspServer.GetCurrentSolution();
+ var initialExecutionMap = initialSolution.CompilationState.SourceGeneratorExecutionVersionMap.Map;
+
+ // Updating the execution version should trigger source generators to run in both automatic and balanced mode.
+ var forceRegeneration = majorVersionUpdate;
+ testLspServer.TestWorkspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration);
+ await testLspServer.WaitForSourceGeneratorsAsync();
+
+ var solutionWithChangedExecutionVersion = testLspServer.GetCurrentSolution();
+
+ var secondRequest = await testLspServer.ExecuteRequestAsync(SourceGeneratedDocumentGetTextHandler.MethodName,
+ new SourceGeneratorGetTextParams(new LSP.TextDocumentIdentifier { DocumentUri = sourceGeneratorDocumentUri }, ResultId: text.ResultId), CancellationToken.None);
+ AssertEx.NotNull(secondRequest);
+
+ if (forceRegeneration)
+ {
+ Assert.NotEqual(text.ResultId, secondRequest.ResultId);
+ Assert.Equal("// callCount: 1", secondRequest.Text);
+ }
+ else
+ {
+ Assert.Equal(text.ResultId, secondRequest.ResultId);
+ Assert.Null(secondRequest.Text);
+ }
+
+ var projectId1 = initialSolution.ProjectIds.Single();
+ var solutionWithDocumentChanged = initialSolution.WithDocumentText(
+ initialSolution.Projects.Single().Documents.Single().Id,
+ SourceText.From("class D { }"));
+
+ var expectVersionChange = sourceGeneratorExecution is SourceGeneratorExecutionPreference.Balanced || forceRegeneration;
+
+ // The content forked solution should have an SG execution version *less than* the one we just changed.
+ // Note: this will be patched up once we call TryApplyChanges.
+ if (expectVersionChange)
+ {
+ Assert.True(
+ solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]
+ > solutionWithDocumentChanged.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+ else
+ {
+ Assert.Equal(
+ solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1],
+ solutionWithDocumentChanged.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
+ Assert.True(testLspServer.TestWorkspace.TryApplyChanges(solutionWithDocumentChanged));
+
+ var finalSolution = testLspServer.GetCurrentSolution();
+
+ if (expectVersionChange)
+ {
+ // In balanced (or if we forced regen) mode, the execution version should have been updated to the new value.
+ Assert.NotEqual(initialExecutionMap[projectId1], solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ Assert.NotEqual(initialExecutionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+ else
+ {
+ // In automatic mode, nothing should change wrt to execution versions (unless we specified force-regenerate).
+ Assert.Equal(initialExecutionMap[projectId1], solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ Assert.Equal(initialExecutionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
+ // The final execution version for the project should match the changed execution version, no matter what.
+ // Proving that the content change happened, but didn't drop the execution version change.
+ Assert.Equal(solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
[Theory, CombinatorialData]
public async Task TestReturnsNullForRemovedClosedGeneratedFile(bool mutatingLspWorkspace)
{
@@ -279,7 +376,9 @@ public async Task TestReturnsNullForRemovedOpenedGeneratedFile(bool mutatingLspW
Assert.Null(secondRequest.Text);
}
- private async Task CreateTestLspServerWithGeneratorAsync(bool mutatingLspWorkspace, string generatedDocumentText)
+ private async Task CreateTestLspServerWithGeneratorAsync(
+ bool mutatingLspWorkspace,
+ [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string generatedDocumentText)
{
var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace);
await AddGeneratorAsync(new SingleFileTestGenerator(generatedDocumentText), testLspServer.TestWorkspace);
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 b2875a2986cca..438601629916c 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
@@ -42,7 +42,6 @@
-
@@ -83,7 +82,6 @@
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.EditorFeatures\$(Configuration)\net472\Microsoft.CodeAnalysis.EditorFeatures.dll" TargetDir="" />
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.Apex\$(Configuration)\net472\Microsoft.CodeAnalysis.ExternalAccess.Apex.dll" TargetDir="" />
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.AspNetCore\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.dll" TargetDir="" />
- <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.Copilot\$(Configuration)\net472\Microsoft.CodeAnalysis.ExternalAccess.Copilot.dll" TargetDir="" />
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.Debugger\$(Configuration)\net472\Microsoft.CodeAnalysis.ExternalAccess.Debugger.dll" TargetDir="" />
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator\$(Configuration)\net472\Microsoft.CodeAnalysis.ExternalAccess.EditorConfigGenerator.dll" TargetDir="" />
<_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.Extensions\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.ExternalAccess.Extensions.dll" TargetDir="" />
diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/DiagnosticDescriptorCreationAnalyzer.cs b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/DiagnosticDescriptorCreationAnalyzer.cs
index 3a0e9f12b3246..ba7b738e4e38d 100644
--- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/DiagnosticDescriptorCreationAnalyzer.cs
+++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/DiagnosticDescriptorCreationAnalyzer.cs
@@ -1084,7 +1084,7 @@ arrayCreation.DimensionSizes[0].ConstantValue.Value is int size &&
else if (arrayCreation.Initializer is IArrayInitializerOperation arrayInitializer &&
arrayInitializer.ElementValues.All(element => element.ConstantValue.HasValue && element.ConstantValue.Value is string))
{
- customTags = arrayInitializer.ElementValues.Select(element => (string)element.ConstantValue.Value!).ToImmutableArray();
+ customTags = arrayInitializer.ElementValues.SelectAsArray(element => (string)element.ConstantValue.Value!);
}
}
finally
diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/AnalyzerReleaseTrackingFix.FixAllProvider.cs b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/AnalyzerReleaseTrackingFix.FixAllProvider.cs
index 835d2ca9cbd08..2c90de3c46d05 100644
--- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/AnalyzerReleaseTrackingFix.FixAllProvider.cs
+++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/AnalyzerReleaseTrackingFix.FixAllProvider.cs
@@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.ReleaseTracking;
using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.Fixers
{
@@ -63,7 +64,7 @@ public ReleaseTrackingFixAllProvider() { }
if (fixAllContext.CodeActionEquivalenceKey == CodeAnalysisDiagnosticsResources.EnableAnalyzerReleaseTrackingRuleTitle)
{
- var projectIds = diagnosticsToFix.Select(d => d.Key.Id).ToImmutableArray();
+ var projectIds = diagnosticsToFix.SelectAsArray(d => d.Key.Id);
return new FixAllAddAdditionalDocumentsAction(projectIds, fixAllContext.Solution);
}
diff --git a/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/CSharp/Analyzers/EnumeratorAllocationAnalyzer.cs b/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/CSharp/Analyzers/EnumeratorAllocationAnalyzer.cs
index b17213b7ae247..33f2d1abd45ad 100644
--- a/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/CSharp/Analyzers/EnumeratorAllocationAnalyzer.cs
+++ b/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/CSharp/Analyzers/EnumeratorAllocationAnalyzer.cs
@@ -64,7 +64,7 @@ protected override void AnalyzeNode(SyntaxNodeAnalysisContext context, in Perfor
if ((enumerator == null || enumerator.IsEmpty) && typeInfo.Type.Interfaces != null)
{
// 2nd fallback, now we try and find the IEnumerable Interface explicitly
- var iEnumerable = typeInfo.Type.Interfaces.Where(i => i.Name == "IEnumerable").ToImmutableArray();
+ var iEnumerable = typeInfo.Type.Interfaces.WhereAsArray(i => i.Name == "IEnumerable");
if (iEnumerable != null && !iEnumerable.IsEmpty)
{
enumerator = iEnumerable[0].GetMembers("GetEnumerator");
diff --git a/src/RoslynAnalyzers/Text.Analyzers/Core/IdentifiersShouldBeSpelledCorrectly.cs b/src/RoslynAnalyzers/Text.Analyzers/Core/IdentifiersShouldBeSpelledCorrectly.cs
index 32fd7a6e4f58c..426b6bc3bcce2 100644
--- a/src/RoslynAnalyzers/Text.Analyzers/Core/IdentifiersShouldBeSpelledCorrectly.cs
+++ b/src/RoslynAnalyzers/Text.Analyzers/Core/IdentifiersShouldBeSpelledCorrectly.cs
@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
namespace Text.Analyzers
{
@@ -263,8 +264,7 @@ ImmutableArray ReadDictionaries()
var fileProvider = AdditionalFileProvider.FromOptions(context.Options);
return fileProvider.GetMatchingFiles(@"(?:dictionary|custom).*?\.(?:xml|dic)$")
.Select(GetOrCreateDictionaryFromAdditionalText)
- .Where(x => x != null)
- .ToImmutableArray();
+ .WhereAsArray(x => x != null);
}
CodeAnalysisDictionary GetOrCreateDictionaryFromAdditionalText(AdditionalText additionalText)
diff --git a/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Analysis/TaintedDataAnalysis/TaintedDataSymbolMapExtensions.cs b/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Analysis/TaintedDataAnalysis/TaintedDataSymbolMapExtensions.cs
index 460aedb98d80a..56947ae85e0b4 100644
--- a/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Analysis/TaintedDataAnalysis/TaintedDataSymbolMapExtensions.cs
+++ b/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Analysis/TaintedDataAnalysis/TaintedDataSymbolMapExtensions.cs
@@ -66,8 +66,8 @@ public static bool IsSourceMethod(
IEnumerable<(PointsToCheck, string target)> positivePointsToTaintedTargets = pointsToTaintedTargets.Where(s =>
s.pointsToCheck(
- arguments.Select(o =>
- pointsToAnalysisResult[o.Kind, o.Syntax]).ToImmutableArray()));
+ arguments.SelectAsArray(o =>
+ pointsToAnalysisResult[o.Kind, o.Syntax])));
if (positivePointsToTaintedTargets.Any())
{
allTaintedTargets ??= PooledHashSet.GetInstance();
@@ -90,8 +90,8 @@ public static bool IsSourceMethod(
IEnumerable<(ValueContentCheck, string target)> positiveValueContentTaintedTargets = valueContentTaintedTargets.Where(s =>
s.valueContentCheck(
- arguments.Select(o => pointsToAnalysisResult[o.Kind, o.Syntax]).ToImmutableArray(),
- arguments.Select(o => valueContentAnalysisResult[o.Kind, o.Syntax]).ToImmutableArray()));
+ arguments.SelectAsArray(o => pointsToAnalysisResult[o.Kind, o.Syntax]),
+ arguments.SelectAsArray(o => valueContentAnalysisResult[o.Kind, o.Syntax])));
if (positiveValueContentTaintedTargets.Any())
{
allTaintedTargets ??= PooledHashSet.GetInstance();
diff --git a/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/AnalysisEntityFactory.cs b/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/AnalysisEntityFactory.cs
index 6be317bb84fca..f29bec91e0d63 100644
--- a/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/AnalysisEntityFactory.cs
+++ b/src/RoslynAnalyzers/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/AnalysisEntityFactory.cs
@@ -300,7 +300,7 @@ private static void GetSymbolAndIndicesForMemberReference(IMemberReferenceOperat
{
symbol = propertyReference.Property;
indices = !propertyReference.Arguments.IsEmpty ?
- CreateAbstractIndices(propertyReference.Arguments.Select(a => a.Value).ToImmutableArray()) :
+ CreateAbstractIndices(propertyReference.Arguments.SelectAsArray(a => a.Value)) :
ImmutableArray.Empty;
}
diff --git a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj
index 7269a9d22a04a..61bfdc1c685f5 100644
--- a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj
+++ b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj
@@ -100,6 +100,7 @@
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('Microsoft.ServiceHub.'))"/>
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('System.Composition.'))"/>
<_Dependency Remove="@(_Dependency)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('Microsoft.Internal.VisualStudio.'))"/>
+ <_Dependency Remove="Azure.Core"/>
<_Dependency Remove="EnvDTE"/>
<_Dependency Remove="EnvDTE80"/>
<_Dependency Remove="EnvDTE90"/>
@@ -128,6 +129,7 @@
<_Dependency Remove="stdole"/>
<_Dependency Remove="StreamJsonRpc"/>
<_Dependency Remove="System.Buffers" />
+ <_Dependency Remove="System.ClientModel"/>
<_Dependency Remove="System.Collections.Immutable"/>
<_Dependency Remove="System.Configuration.ConfigurationManager"/>
<_Dependency Remove="System.Diagnostics.DiagnosticSource"/>
@@ -136,6 +138,7 @@
<_Dependency Remove="System.IO.Packaging"/>
<_Dependency Remove="System.IO.Pipelines"/>
<_Dependency Remove="System.Memory"/>
+ <_Dependency Remove="System.Memory.Data"/>
<_Dependency Remove="System.Numerics.Vectors"/>
<_Dependency Remove="System.Reflection.Metadata"/>
<_Dependency Remove="System.Reflection.MetadataLoadContext"/>
diff --git a/src/Tools/AnalyzerRunner/CodeRefactoringRunner.cs b/src/Tools/AnalyzerRunner/CodeRefactoringRunner.cs
index 8997850368363..de3e30d3e5d36 100644
--- a/src/Tools/AnalyzerRunner/CodeRefactoringRunner.cs
+++ b/src/Tools/AnalyzerRunner/CodeRefactoringRunner.cs
@@ -18,6 +18,7 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Composition;
+using Roslyn.Utilities;
using static AnalyzerRunner.Program;
namespace AnalyzerRunner
@@ -231,7 +232,7 @@ private static ImmutableDictionary refactoring.Metadata.Languages).Distinct();
return languages.ToImmutableDictionary(
language => language,
- language => refactorings.Where(refactoring => refactoring.Metadata.Languages.Contains(language)).ToImmutableArray());
+ language => refactorings.WhereAsArray(refactoring => refactoring.Metadata.Languages.Contains(language)));
}
private class CodeRefactoringProviderMetadata
diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs
index 3a3c437a1ae7d..a83e074b0217a 100644
--- a/src/Tools/BuildValidator/Program.cs
+++ b/src/Tools/BuildValidator/Program.cs
@@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.Rebuild;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
+using Roslyn.Utilities;
namespace BuildValidator
{
@@ -373,7 +374,7 @@ private static ImmutableArray ResolveSourceLinks(CompilationOpt
var documents = JsonConvert.DeserializeAnonymousType(Encoding.UTF8.GetString(sourceLinkUtf8), new { documents = (Dictionary?)null })?.documents
?? throw new InvalidOperationException("Failed to deserialize source links.");
- var sourceLinks = documents.Select(makeSourceLink).ToImmutableArray();
+ var sourceLinks = documents.SelectAsArray(makeSourceLink);
if (sourceLinks.IsDefault)
{
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/AbstractRazorCohostLifecycleService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/AbstractRazorCohostLifecycleService.cs
new file mode 100644
index 0000000000000..7e1720526fa0a
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/AbstractRazorCohostLifecycleService.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Roslyn.LanguageServer.Protocol;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
+
+internal abstract class AbstractRazorCohostLifecycleService : IDisposable
+{
+ public abstract Task LspServerIntializedAsync(CancellationToken cancellationToken);
+ public abstract Task RazorActivatedAsync(ClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken);
+ public abstract void Dispose();
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CodeLens.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CodeLens.cs
new file mode 100644
index 0000000000000..da21ca75c5c53
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CodeLens.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.LanguageServer.Handler.CodeLens;
+using Microsoft.CodeAnalysis.Options;
+using LSP = Roslyn.LanguageServer.Protocol;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
+
+internal static class CodeLens
+{
+ public static Task GetCodeLensAsync(LSP.TextDocumentIdentifier textDocumentIdentifier, Document document, CancellationToken cancellationToken)
+ {
+ var globalOptions = document.Project.Solution.Services.ExportProvider.GetService();
+
+ return CodeLensHandler.GetCodeLensAsync(textDocumentIdentifier, document, globalOptions, cancellationToken);
+ }
+
+ public static Task ResolveCodeLensAsync(LSP.CodeLens codeLens, Document document, CancellationToken cancellationToken)
+ {
+ return CodeLensResolveHandler.ResolveCodeLensAsync(codeLens, document, cancellationToken);
+ }
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Completion.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Completion.cs
index 6e1ee9308e5a3..4368b83fe220c 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Completion.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Completion.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServer;
@@ -18,12 +19,15 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
internal static class Completion
{
+ [Obsolete("Use GetCompletionListAsync with CompletionListCacheWrapper instead.")]
private static CompletionListCache? s_completionListCache;
+ [Obsolete("Use GetCompletionListAsync with CompletionListCacheWrapper instead.")]
private static CompletionListCache GetCache()
=> s_completionListCache ??= InterlockedOperations.Initialize(ref s_completionListCache, () => new());
- public static async Task GetCompletionListAsync(
+ [Obsolete("Use GetCompletionListAsync with CompletionListCacheWrapper instead.")]
+ public static Task GetCompletionListAsync(
Document document,
LinePosition linePosition,
LSP.CompletionContext? completionContext,
@@ -33,6 +37,44 @@ private static CompletionListCache GetCache()
{
var cache = GetCache();
+ return GetCompletionListAsync(
+ document,
+ linePosition,
+ completionContext,
+ supportsVSExtensions,
+ completionCapabilities,
+ GetCache(),
+ cancellationToken);
+ }
+
+ public static Task GetCompletionListAsync(
+ Document document,
+ LinePosition linePosition,
+ LSP.CompletionContext? completionContext,
+ bool supportsVSExtensions,
+ LSP.CompletionSetting completionCapabilities,
+ CompletionListCacheWrapper cacheWrapper,
+ CancellationToken cancellationToken)
+ {
+ return GetCompletionListAsync(
+ document,
+ linePosition,
+ completionContext,
+ supportsVSExtensions,
+ completionCapabilities,
+ cacheWrapper.GetCache(),
+ cancellationToken);
+ }
+
+ private static async Task GetCompletionListAsync(
+ Document document,
+ LinePosition linePosition,
+ LSP.CompletionContext? completionContext,
+ bool supportsVSExtensions,
+ LSP.CompletionSetting completionCapabilities,
+ CompletionListCache cache,
+ CancellationToken cancellationToken)
+ {
var position = await document
.GetPositionFromLinePositionAsync(linePosition, cancellationToken)
.ConfigureAwait(false);
@@ -50,6 +92,7 @@ private static CompletionListCache GetCache()
cancellationToken).ConfigureAwait(false);
}
+ [Obsolete("Use GetCompletionListAsync with CompletionListCacheWrapper instead.")]
public static Task ResolveCompletionItemAsync(
LSP.CompletionItem completionItem,
Document document,
@@ -57,8 +100,30 @@ private static CompletionListCache GetCache()
LSP.CompletionSetting completionCapabilities,
CancellationToken cancellationToken)
{
- var cache = GetCache();
+ return ResolveCompletionItemAsync(
+ completionItem, document, supportsVSExtensions, completionCapabilities, GetCache(), cancellationToken);
+ }
+ public static Task ResolveCompletionItemAsync(
+ LSP.CompletionItem completionItem,
+ Document document,
+ bool supportsVSExtensions,
+ LSP.CompletionSetting completionCapabilities,
+ CompletionListCacheWrapper cacheWrapper,
+ CancellationToken cancellationToken)
+ {
+ return ResolveCompletionItemAsync(
+ completionItem, document, supportsVSExtensions, completionCapabilities, cacheWrapper.GetCache(), cancellationToken);
+ }
+
+ private static Task ResolveCompletionItemAsync(
+ LSP.CompletionItem completionItem,
+ Document document,
+ bool supportsVSExtensions,
+ LSP.CompletionSetting completionCapabilities,
+ CompletionListCache cache,
+ CancellationToken cancellationToken)
+ {
var globalOptions = document.Project.Solution.Services.ExportProvider.GetService();
var capabilityHelper = new CompletionCapabilityHelper(supportsVSExtensions, completionCapabilities);
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CompletionListCacheWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CompletionListCacheWrapper.cs
new file mode 100644
index 0000000000000..575c1ea618aae
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/CompletionListCacheWrapper.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
+
+///
+/// Provides a wrapper around the so that Razor can control the lifecycle.
+///
+internal sealed class CompletionListCacheWrapper
+{
+ private readonly CompletionListCache _cache = new();
+
+ public CompletionListCache GetCache() => _cache;
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs
index 6e39c496e756b..ddb3c019a66bd 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs
@@ -25,8 +25,6 @@ internal static class Diagnostics
document, range: null, DiagnosticKind.All, cancellationToken).ConfigureAwait(false);
var project = document.Project;
- // isLiveSource means build might override a diagnostics, but this method is only used by tooling, so builds aren't relevant
- const bool IsLiveSource = false;
// Potential duplicate is only set for workspace diagnostics
const bool PotentialDuplicate = false;
@@ -34,7 +32,7 @@ internal static class Diagnostics
foreach (var diagnostic in diagnostics)
{
if (!diagnostic.IsSuppressed)
- result.AddRange(ProtocolConversions.ConvertDiagnostic(diagnostic, supportsVisualStudioExtensions, project, IsLiveSource, PotentialDuplicate, globalOptionsService));
+ result.AddRange(ProtocolConversions.ConvertDiagnostic(diagnostic, supportsVisualStudioExtensions, project, PotentialDuplicate, globalOptionsService));
}
return result.ToImmutableAndFree();
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHintCacheWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHintCacheWrapper.cs
new file mode 100644
index 0000000000000..5d28d9f98d44e
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHintCacheWrapper.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
+
+///
+/// Provides a wrapper around the so that Razor can control the lifecycle.
+///
+internal sealed class InlayHintCacheWrapper
+{
+ private readonly InlayHintCache _cache = new();
+
+ public InlayHintCache GetCache() => _cache;
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHints.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHints.cs
index 90b90fa6cd63e..8b81992d87d4c 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHints.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/InlayHints.cs
@@ -2,11 +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.
+using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.InlineHints;
using Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint;
-using Roslyn.LanguageServer.Protocol;
+using LSP = Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers
{
@@ -15,27 +16,49 @@ internal static class InlayHints
// In the Roslyn LSP server this cache has the same lifetime as the LSP server. For Razor, running OOP, we don't have
// that same lifetime anywhere, everything is just static. This is likely not ideal, but the inlay hint cache has a
// max size of 3 items, so it's not a huge deal.
+ [Obsolete("Use GetInlayHintsAsync with InlayHintCacheWrapper instead.")]
private static InlayHintCache? s_resolveCache;
- public static Task GetInlayHintsAsync(Document document, TextDocumentIdentifier textDocumentIdentifier, Range range, bool displayAllOverride, CancellationToken cancellationToken)
+ [Obsolete("Use GetInlayHintsAsync with InlayHintCacheWrapper instead.")]
+ public static Task GetInlayHintsAsync(Document document, LSP.TextDocumentIdentifier textDocumentIdentifier, LSP.Range range, bool displayAllOverride, CancellationToken cancellationToken)
{
s_resolveCache ??= new();
+ return GetInlayHintsAsync(document, textDocumentIdentifier, range, displayAllOverride, s_resolveCache, cancellationToken);
+ }
+
+ public static Task GetInlayHintsAsync(Document document, LSP.TextDocumentIdentifier textDocumentIdentifier, LSP.Range range, bool displayAllOverride, InlayHintCacheWrapper cacheWrapper, CancellationToken cancellationToken)
+ {
+ return GetInlayHintsAsync(document, textDocumentIdentifier, range, displayAllOverride, cacheWrapper.GetCache(), cancellationToken);
+ }
+ private static Task GetInlayHintsAsync(Document document, LSP.TextDocumentIdentifier textDocumentIdentifier, LSP.Range range, bool displayAllOverride, InlayHintCache cache, CancellationToken cancellationToken)
+ {
// Currently Roslyn options don't sync to OOP so trying to get the real options out of IGlobalOptionsService will
// always just result in the defaults, which for inline hints are to not show anything. However, the editor has a
// setting for LSP inlay hints, so we can assume that if we get a request from the client, the user wants hints.
// When overriding however, Roslyn does a nicer job if type hints are off.
var options = GetOptions(displayAllOverride);
- return InlayHintHandler.GetInlayHintsAsync(document, textDocumentIdentifier, range, options, displayAllOverride, s_resolveCache, cancellationToken);
+ return InlayHintHandler.GetInlayHintsAsync(document, textDocumentIdentifier, range, options, displayAllOverride, cache, cancellationToken);
}
- public static Task ResolveInlayHintAsync(Document document, InlayHint request, CancellationToken cancellationToken)
+ [Obsolete("Use GetInlayHintsAsync with InlayHintCacheWrapper instead.")]
+ public static Task ResolveInlayHintAsync(Document document, LSP.InlayHint request, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(s_resolveCache, "Cache should never be null for resolve, since it should have been created by the original request");
+ return ResolveInlayHintAsync(document, request, s_resolveCache, cancellationToken);
+ }
+
+ public static Task ResolveInlayHintAsync(Document document, LSP.InlayHint request, InlayHintCacheWrapper cacheWrapper, CancellationToken cancellationToken)
+ {
+ return ResolveInlayHintAsync(document, request, cacheWrapper.GetCache(), cancellationToken);
+ }
+
+ private static Task ResolveInlayHintAsync(Document document, LSP.InlayHint request, InlayHintCache cache, CancellationToken cancellationToken)
+ {
var data = InlayHintResolveHandler.GetInlayHintResolveData(request);
var options = GetOptions(data.DisplayAllOverride);
- return InlayHintResolveHandler.ResolveInlayHintAsync(document, request, s_resolveCache, data, options, cancellationToken);
+ return InlayHintResolveHandler.ResolveInlayHintAsync(document, request, cache, data, options, cancellationToken);
}
private static InlineHintsOptions GetOptions(bool displayAllOverride)
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/OnAutoInsert.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/OnAutoInsert.cs
index c4388d526027d..e688a3759f38c 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/OnAutoInsert.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/OnAutoInsert.cs
@@ -20,7 +20,11 @@ internal static class OnAutoInsert
public static Task GetOnAutoInsertResponseAsync(Document document, LinePosition linePosition, string character, FormattingOptions formattingOptions, CancellationToken cancellationToken)
{
var globalOptions = document.Project.Solution.Services.ExportProvider.GetService();
- var services = document.Project.Solution.Services.ExportProvider.GetExports().Where(s => s.Metadata.Language == LanguageNames.CSharp).SelectAsArray(s => s.Value);
+ var services = document.Project.Solution.Services.ExportProvider
+ .GetExports()
+ .SelectAsArray(
+ predicate: s => s.Metadata.Language == LanguageNames.CSharp,
+ selector: s => s.Value);
return OnAutoInsertHandler.GetOnAutoInsertResponseAsync(globalOptions, services, document, linePosition, character, formattingOptions, isRazorRequest: true, cancellationToken);
}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Rename.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Rename.cs
index 2565e735d6406..c6feae0bab8e9 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Rename.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Rename.cs
@@ -16,5 +16,5 @@ internal static class Rename
=> PrepareRenameHandler.GetRenameRangeAsync(document, linePosition, cancellationToken);
public static Task GetRenameEditAsync(Document document, LinePosition linePosition, string newName, CancellationToken cancellationToken)
- => RenameHandler.GetRenameEditAsync(document, linePosition, newName, includeSourceGenerated: true, cancellationToken);
+ => RenameHandler.GetRenameEditAsync(document, linePosition, newName, cancellationToken);
}
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs
index d8e01a448a05c..87b350649cd91 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs
@@ -2,11 +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.
+using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
+[Obsolete("Please move to AbstractRazorCohostLifecycleService. This will be removed in a future release.")]
internal interface ICohostStartupService
{
Task StartupAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken);
diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs
index 5209e33afb7e6..a582960931a92 100644
--- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs
@@ -22,38 +22,51 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class RazorStartupServiceFactory(
[Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService,
- [Import(AllowDefault = true)] Lazy? cohostStartupService) : ILspServiceFactory
+ [Import(AllowDefault = true)] Lazy? cohostStartupService,
+ [Import(AllowDefault = true)] AbstractRazorCohostLifecycleService? razorCohostLifecycleService) : ILspServiceFactory
{
public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind)
{
- return new RazorStartupService(uIContextActivationService, cohostStartupService);
+ return new RazorStartupService(uIContextActivationService, cohostStartupService, razorCohostLifecycleService);
}
private class RazorStartupService(
IUIContextActivationService? uIContextActivationService,
- Lazy? cohostStartupService) : ILspService, IOnInitialized, IDisposable
+#pragma warning disable CS0618 // Type or member is obsolete
+ Lazy? cohostStartupService,
+#pragma warning restore CS0618 // Type or member is obsolete
+ AbstractRazorCohostLifecycleService? razorCohostLifecycleService) : ILspService, IOnInitialized, IDisposable
{
private readonly CancellationTokenSource _disposalTokenSource = new();
private IDisposable? _activation;
public void Dispose()
{
+ razorCohostLifecycleService?.Dispose();
+
_activation?.Dispose();
_activation = null;
_disposalTokenSource.Cancel();
}
- public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken)
+ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken)
{
if (context.ServerKind is not (WellKnownLspServerKinds.AlwaysActiveVSLspServer or WellKnownLspServerKinds.CSharpVisualBasicLspServer))
{
// We have to register this class for Any server, but only want to run in the C# server in VS or VS Code
- return Task.CompletedTask;
+ return;
+ }
+
+ if (cohostStartupService is null && razorCohostLifecycleService is null)
+ {
+ return;
}
- if (cohostStartupService is null)
+ if (razorCohostLifecycleService is not null)
{
- return Task.CompletedTask;
+ // If we have a cohost lifecycle service, fire pre-initialization, which happens when the LSP server starts up, but before
+ // the UIContext is activated.
+ await razorCohostLifecycleService.LspServerIntializedAsync(cancellationToken).ConfigureAwait(false);
}
if (uIContextActivationService is null)
@@ -66,7 +79,7 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon
_activation = uIContextActivationService.ExecuteWhenActivated(Constants.RazorCohostingUIContext, InitializeRazor);
}
- return Task.CompletedTask;
+ return;
void InitializeRazor()
{
@@ -85,13 +98,18 @@ private async Task InitializeRazorAsync(ClientCapabilities clientCapabilities, R
using var languageScope = context.Logger.CreateLanguageContext(Constants.RazorLanguageName);
- // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL
- var serializedClientCapabilities = JsonSerializer.Serialize(clientCapabilities, ProtocolConversions.LspJsonSerializerOptions);
-
var requestContext = new RazorCohostRequestContext(context);
+ if (razorCohostLifecycleService is not null)
+ {
+ // If we have a cohost lifecycle service, fire post-initialization, which happens when the UIContext is activated.
+ await razorCohostLifecycleService.RazorActivatedAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
+ }
+
if (cohostStartupService is not null)
{
+ // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL
+ var serializedClientCapabilities = JsonSerializer.Serialize(clientCapabilities, ProtocolConversions.LspJsonSerializerOptions);
await cohostStartupService.Value.StartupAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratedDocumentSpanMappingService.cs b/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratedDocumentSpanMappingService.cs
new file mode 100644
index 0000000000000..51b91a0ad1694
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratedDocumentSpanMappingService.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor
+{
+ internal interface IRazorSourceGeneratedDocumentSpanMappingService
+ {
+ Task> GetMappedTextChangesAsync(SourceGeneratedDocument oldDocument, SourceGeneratedDocument newDocument, CancellationToken cancellationToken);
+ Task> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray spans, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorGeneratedDocumentIdentity.cs b/src/Tools/ExternalAccess/Razor/Features/RazorGeneratedDocumentIdentity.cs
index 7c2290c9154ea..4e50c9137a69e 100644
--- a/src/Tools/ExternalAccess/Razor/Features/RazorGeneratedDocumentIdentity.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/RazorGeneratedDocumentIdentity.cs
@@ -9,4 +9,17 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor;
///
/// Wrapper for and
///
-internal record struct RazorGeneratedDocumentIdentity(DocumentId DocumentId, string HintName, string FilePath, string GeneratorAssemblyName, string? GeneratorAssemblyPath, Version GeneratorAssemblyVersion, string GeneratorTypeName);
+internal record struct RazorGeneratedDocumentIdentity(DocumentId DocumentId, string HintName, string FilePath, string GeneratorAssemblyName, string? GeneratorAssemblyPath, Version GeneratorAssemblyVersion, string GeneratorTypeName)
+{
+ internal static RazorGeneratedDocumentIdentity Create(SourceGeneratedDocument document)
+ => Create(document.Identity);
+
+ internal static RazorGeneratedDocumentIdentity Create(SourceGeneratedDocumentIdentity identity)
+ => new(identity.DocumentId,
+ identity.HintName,
+ identity.FilePath,
+ identity.Generator.AssemblyName,
+ identity.Generator.AssemblyPath,
+ identity.Generator.AssemblyVersion,
+ identity.Generator.TypeName);
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorMappedSpanResult.cs b/src/Tools/ExternalAccess/Razor/Features/RazorMappedSpanResult.cs
index 96adfdc8fb60b..4d4e375b6b847 100644
--- a/src/Tools/ExternalAccess/Razor/Features/RazorMappedSpanResult.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/RazorMappedSpanResult.cs
@@ -3,16 +3,21 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor;
+[DataContract]
internal readonly struct RazorMappedSpanResult
{
+ [DataMember(Order = 0)]
public readonly string FilePath;
+ [DataMember(Order = 1)]
public readonly LinePositionSpan LinePositionSpan;
+ [DataMember(Order = 2)]
public readonly TextSpan Span;
public RazorMappedSpanResult(string filePath, LinePositionSpan linePositionSpan, TextSpan span)
@@ -30,7 +35,10 @@ public RazorMappedSpanResult(string filePath, LinePositionSpan linePositionSpan,
public bool IsDefault => FilePath == null;
}
-internal readonly record struct RazorMappedEditResult(string FilePath, TextChange[] TextChanges)
+[DataContract]
+internal readonly record struct RazorMappedEditResult(
+ [property: DataMember(Order = 0)] string FilePath,
+ [property: DataMember(Order = 1)] TextChange[] TextChanges)
{
public bool IsDefault => FilePath == null || TextChanges == null;
}
diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorSourceGeneratedDocumentSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/RazorSourceGeneratedDocumentSpanMappingServiceWrapper.cs
new file mode 100644
index 0000000000000..1b02fc896051c
--- /dev/null
+++ b/src/Tools/ExternalAccess/Razor/Features/RazorSourceGeneratedDocumentSpanMappingServiceWrapper.cs
@@ -0,0 +1,87 @@
+
+// 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.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ExternalAccess.Razor;
+
+[ExportWorkspaceService(typeof(ISourceGeneratedDocumentSpanMappingService)), Shared]
+[method: ImportingConstructor]
+[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+internal sealed class RazorSourceGeneratedDocumentSpanMappingServiceWrapper(
+ [Import(AllowDefault = true)] IRazorSourceGeneratedDocumentSpanMappingService? implementation) : ISourceGeneratedDocumentSpanMappingService
+{
+ private readonly IRazorSourceGeneratedDocumentSpanMappingService? _implementation = implementation;
+
+ public bool CanMapSpans(SourceGeneratedDocument document)
+ {
+ return _implementation is not null && document.IsRazorSourceGeneratedDocument();
+ }
+
+ public async Task> GetMappedTextChangesAsync(SourceGeneratedDocument oldDocument, SourceGeneratedDocument newDocument, CancellationToken cancellationToken)
+ {
+ if (_implementation is null ||
+ !oldDocument.IsRazorSourceGeneratedDocument() ||
+ !newDocument.IsRazorSourceGeneratedDocument())
+ {
+ return [];
+ }
+
+ var mappedChanges = await _implementation.GetMappedTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false);
+ if (mappedChanges.IsDefaultOrEmpty)
+ {
+ return [];
+ }
+
+ using var _ = ArrayBuilder.GetInstance(out var changesBuilder);
+ foreach (var change in mappedChanges)
+ {
+ if (change.IsDefault)
+ {
+ continue;
+ }
+
+ foreach (var textChange in change.TextChanges)
+ {
+ changesBuilder.Add(new MappedTextChange(change.FilePath, textChange));
+ }
+ }
+
+ return changesBuilder.ToImmutableAndClear();
+ }
+
+ public async Task> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray spans, CancellationToken cancellationToken)
+ {
+ if (_implementation is null ||
+ !document.IsRazorSourceGeneratedDocument())
+ {
+ return [];
+ }
+
+ var mappedSpans = await _implementation.MapSpansAsync(document, spans, cancellationToken).ConfigureAwait(false);
+ if (mappedSpans.Length != spans.Length)
+ {
+ return [];
+ }
+
+ using var _ = ArrayBuilder.GetInstance(out var spansBuilder);
+ foreach (var span in mappedSpans)
+ {
+ spansBuilder.Add(span.IsDefault
+ ? default
+ : new MappedSpanResult(span.FilePath, span.LinePositionSpan, span.Span));
+ }
+
+ return spansBuilder.ToImmutableAndClear();
+ }
+}
diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorUri.cs b/src/Tools/ExternalAccess/Razor/Features/RazorUri.cs
index 8eb2e6972e94c..6841ab7a9a4bc 100644
--- a/src/Tools/ExternalAccess/Razor/Features/RazorUri.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/RazorUri.cs
@@ -35,13 +35,6 @@ public static RazorGeneratedDocumentIdentity GetIdentityOfGeneratedDocument(Solu
// Razor only cares about documents from its own generator, but it's better to just send them back the info they
// need to check on their side, so we can avoid dual insertions if anything changes.
- return new RazorGeneratedDocumentIdentity(
- identity.DocumentId,
- identity.HintName,
- identity.FilePath,
- identity.Generator.AssemblyName,
- identity.Generator.AssemblyPath,
- identity.Generator.AssemblyVersion,
- identity.Generator.TypeName);
+ return RazorGeneratedDocumentIdentity.Create(identity);
}
}
diff --git a/src/Tools/ExternalAccess/Razor/Features/SolutionExtensions.cs b/src/Tools/ExternalAccess/Razor/Features/SolutionExtensions.cs
index 4cfa3dbc0d745..55c5de663ba90 100644
--- a/src/Tools/ExternalAccess/Razor/Features/SolutionExtensions.cs
+++ b/src/Tools/ExternalAccess/Razor/Features/SolutionExtensions.cs
@@ -16,5 +16,5 @@ public static ImmutableArray GetDocumentIds(this Solution solution,
=> LanguageServer.Extensions.GetDocumentIds(solution, new(documentUri));
public static int GetWorkspaceVersion(this Solution solution)
- => solution.WorkspaceVersion;
+ => solution.SolutionStateContentVersion;
}
diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs
index 2da252a3ab76a..029d70823dd36 100644
--- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs
+++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs
@@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml;
internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource
{
- bool IDiagnosticSource.IsLiveSource() => true;
Project IDiagnosticSource.GetProject() => document.Project;
ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id);
TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { DocumentUri = document.GetURI() };
@@ -26,7 +25,7 @@ async Task> IDiagnosticSource.GetDiagnosticsAsync
{
var xamlRequestContext = XamlRequestContext.FromRequestContext(context);
var diagnostics = await xamlDiagnosticSource.GetDiagnosticsAsync(xamlRequestContext, cancellationToken).ConfigureAwait(false);
- var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray();
+ var result = diagnostics.SelectAsArray(e => DiagnosticData.Create(e, document));
return result;
}
}
diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs
index 436ce4c1188cb..c4775718fa51f 100644
--- a/src/Tools/Replay/Replay.cs
+++ b/src/Tools/Replay/Replay.cs
@@ -105,7 +105,7 @@ static async Task RunAsync(ReplayOptions options)
var stopwatch = new Stopwatch();
stopwatch.Start();
- await foreach (var buildData in BuildAllAsync(options, compilerCalls, compilerServerLogger, CancellationToken.None))
+ await foreach (var buildData in BuildAllAsync(options, compilerCalls, compilerServerLogger, CancellationToken.None).ConfigureAwait(false))
{
Console.WriteLine($"{buildData.CompilerCall.GetDiagnosticName()} ... {buildData.BuildResponse.Type}");
}
diff --git a/src/Tools/SemanticSearch/Extensions/Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj b/src/Tools/SemanticSearch/Extensions/Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj
new file mode 100644
index 0000000000000..01db54595ee6b
--- /dev/null
+++ b/src/Tools/SemanticSearch/Extensions/Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+ Library
+ $(NetVSShared)
+
+
+
+
+
+
+
+
+
diff --git a/src/Tools/SemanticSearch/Extensions/ProjectModel.cs b/src/Tools/SemanticSearch/Extensions/ProjectModel.cs
new file mode 100644
index 0000000000000..1b94e2e3ccd35
--- /dev/null
+++ b/src/Tools/SemanticSearch/Extensions/ProjectModel.cs
@@ -0,0 +1,147 @@
+// 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 NET
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using System.Xml;
+using System.Text;
+
+namespace Microsoft.CodeAnalysis.SemanticSearch.Extensions;
+
+///
+/// Models project information not tracked by Compilation.
+///
+public sealed class ProjectModel
+{
+ private readonly Lazy> _lazyResxFiles;
+
+ public string FilePath { get; }
+
+ internal ProjectModel(string filePath)
+ {
+ FilePath = filePath;
+ _lazyResxFiles = new(LoadResxFiles, isThreadSafe: true);
+ }
+
+ internal ProjectModel(string filePath, ImmutableDictionary resxFiles)
+ {
+ FilePath = filePath;
+ _lazyResxFiles = new(() => resxFiles, isThreadSafe: true);
+ }
+
+ public ImmutableDictionary ResxFiles
+ => _lazyResxFiles.Value;
+
+ public ProjectModel ReplaceResxFile(ResxFile file)
+ => new(FilePath, ResxFiles.SetItem(file.FilePath, file));
+
+ internal ImmutableDictionary LoadResxFiles()
+ {
+ var resxFiles = ImmutableDictionary.CreateBuilder();
+ var projectDirectory = Path.GetDirectoryName(FilePath)!;
+
+ // TODO: get EmbeddedResources items from msbuild instead
+ foreach (var filePath in Directory.EnumerateFileSystemEntries(projectDirectory, "*.resx", SearchOption.AllDirectories))
+ {
+ resxFiles.Add(filePath, ResxFile.ReadFromFile(filePath));
+ }
+
+ return resxFiles.ToImmutable();
+ }
+
+ internal static IEnumerable<(string filePath, string? newContent)> GetChanges(ProjectModel oldModel, ProjectModel newModel)
+ {
+ if (!oldModel._lazyResxFiles.IsValueCreated && !newModel._lazyResxFiles.IsValueCreated)
+ {
+ yield break;
+ }
+
+ foreach (var (filePath, newResx) in newModel.ResxFiles)
+ {
+ var newContent = newResx.GetContent();
+
+ if (oldModel.ResxFiles.TryGetValue(filePath, out var oldResx) && newContent == oldResx.GetContent())
+ {
+ continue;
+ }
+
+ // new or updated resx file:
+ yield return (filePath, newContent);
+ }
+
+ foreach (var (filePath, _) in oldModel.ResxFiles)
+ {
+ if (!newModel.ResxFiles.ContainsKey(filePath))
+ {
+ // deleted resx file:
+ yield return (filePath, null);
+ }
+ }
+ }
+}
+
+public sealed class ResxFile
+{
+ public string FilePath { get; }
+
+ private readonly ImmutableDictionary _changes;
+
+ internal ResxFile(string filePath, ImmutableDictionary changes)
+ {
+ FilePath = filePath;
+ _changes = changes;
+ }
+
+ internal static ResxFile ReadFromFile(string filePath)
+ {
+ return new ResxFile(filePath, changes: ImmutableDictionary.Empty);
+ }
+
+ public ResxFile AddString(string name, string value)
+ => new(FilePath, _changes.SetItem(name, value));
+
+ internal string GetContent()
+ {
+ if (_changes.Count == 0)
+ {
+ return File.ReadAllText(FilePath, Encoding.UTF8);
+ }
+
+ var newDocument = XDocument.Load(FilePath, LoadOptions.None);
+
+ foreach (var (name, value) in _changes)
+ {
+ newDocument.Root!.Add(new XElement("data",
+ new XAttribute("name", name),
+ new XAttribute(XNamespace.Xml + "space", "preserve"),
+ new XElement("value", value)
+ ));
+ }
+
+ using var stream = new MemoryStream();
+
+ using var xmlWriter = XmlWriter.Create(stream, new()
+ {
+ Indent = true,
+ Encoding = Encoding.UTF8,
+ IndentChars = "\t",
+ NewLineChars = "\r\n",
+ NewLineOnAttributes = false,
+ });
+
+ newDocument.Save(xmlWriter);
+ xmlWriter.Close();
+
+ stream.Position = 0;
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+ return reader.ReadToEnd();
+ }
+}
+#endif
diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj b/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj
index 111b378b6692b..aaaef777c9c04 100644
--- a/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj
+++ b/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj
@@ -19,6 +19,7 @@
+
@@ -39,12 +40,13 @@
- <_InputReference Include="@(ReferencePath)"
+ <_InputReference Include="@(ReferencePath)"
Condition="'%(ReferencePath.FrameworkReferenceName)' == 'Microsoft.NETCore.App' or
'%(ReferencePath.FileName)' == 'System.Collections.Immutable' or
'%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis' or
'%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis.CSharp' or
- '%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis.VisualBasic'" />
+ '%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis.VisualBasic' or
+ '%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis.SemanticSearch.Extensions'" />
@@ -52,7 +54,9 @@
<_InputFile Include="@(ApiSet)" />
<_InputFile Include="@(_InputReference)" />
- <_OutputFile Include="@(ApiSet->'$(_OutputDir)%(FileName).dll')" />
+ <_OutputRefAssembly Include="@(ApiSet->'$(_OutputDir)%(FileName).dll')" />
+
+ <_OutputFile Include="@(_OutputRefAssembly)" />
<_OutputFile Include="@(Apis)" />
@@ -77,5 +81,5 @@
-
+
diff --git a/src/Tools/TestDiscoveryWorker/Program.cs b/src/Tools/TestDiscoveryWorker/Program.cs
index c22744f8c250f..96b8d7591d066 100644
--- a/src/Tools/TestDiscoveryWorker/Program.cs
+++ b/src/Tools/TestDiscoveryWorker/Program.cs
@@ -9,6 +9,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
+using System.Threading.Tasks;
using Mono.Options;
using Xunit;
using Xunit.Abstractions;
@@ -77,7 +78,7 @@
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
var testsToWrite = new HashSet();
- await foreach (var fullyQualifiedName in sink.GetTestCaseNamesAsync())
+ await foreach (var fullyQualifiedName in sink.GetTestCaseNamesAsync().ConfigureAwait(false))
{
testsToWrite.Add(fullyQualifiedName);
}
diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs
index fb0cfa542c6fb..44adda02940ba 100644
--- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs
+++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs
@@ -28,7 +28,7 @@ public override Task CanNavigateToSpanAsync(Workspace workspace, DocumentI
public override Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken)
{
Debug.Assert(workspace is SemanticSearchWorkspace);
- Debug.Assert(documentId == SemanticSearchUtilities.GetQueryDocumentId(workspace.CurrentSolution));
+ Debug.Assert(documentId == window.SemanticSearchService.GetQueryDocumentId(workspace.CurrentSolution));
return Task.FromResult(window.GetNavigableLocation(textSpan));
}
diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchPresenterController.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchPresenterController.cs
index bba469dc2de29..a2093478938ba 100644
--- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchPresenterController.cs
+++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchPresenterController.cs
@@ -7,9 +7,11 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Host;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SemanticSearch;
+using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LanguageServices.CSharp;
@@ -22,14 +24,21 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp;
internal sealed class SemanticSearchPresenterController(
IStreamingFindUsagesPresenter resultsPresenter,
VisualStudioWorkspace workspace,
- IGlobalOptionService globalOptions) : ISemanticSearchPresenterController
+ IGlobalOptionService globalOptions,
+ IThreadingContext threadingContext) : ISemanticSearchPresenterController
{
public async Task ExecuteQueryAsync(string query, CancellationToken cancellationToken)
{
+ await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+
var (presenterContext, presenterCancellationToken) = resultsPresenter.StartSearch(ServicesVSResources.Semantic_search_results, StreamingFindUsagesPresenterOptions.Default);
+
+ await TaskScheduler.Default;
+
using var queryCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(presenterCancellationToken, cancellationToken);
- var executor = new SemanticSearchQueryExecutor(presenterContext, globalOptions);
+ // TODO: logger
+ var executor = new SemanticSearchQueryExecutor(presenterContext, logMessage: static _ => { }, globalOptions);
await executor.ExecuteAsync(query, queryDocument: null, workspace.CurrentSolution, queryCancellationSource.Token).ConfigureAwait(false);
}
}
diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchQueryExecutor.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchQueryExecutor.cs
index cf452757d6c10..3a26ad85af2c7 100644
--- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchQueryExecutor.cs
+++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchQueryExecutor.cs
@@ -3,26 +3,35 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Concurrent;
+using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.ErrorReporting;
-using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.OrganizeImports;
using Microsoft.CodeAnalysis.SemanticSearch;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.CSharp;
internal sealed class SemanticSearchQueryExecutor(
FindUsagesContext presenterContext,
+ Action logMessage,
IOptionsReader options)
{
- private sealed class ResultsObserver(IFindUsagesContext presenterContext, IOptionsReader options, Document? queryDocument) : ISemanticSearchResultsDefinitionObserver
+ private sealed class ResultsObserver(IFindUsagesContext presenterContext, IOptionsReader options, Action logMessage, Document? queryDocument) : ISemanticSearchResultsDefinitionObserver
{
+ private readonly Lazy changes)>> _lazyDocumentUpdates = new();
+ private readonly Lazy> _lazyTextFileUpdates = new();
+
public ValueTask GetClassificationOptionsAsync(Microsoft.CodeAnalysis.Host.LanguageServices language, CancellationToken cancellationToken)
=> new(options.GetClassificationOptions(language.Language));
@@ -38,9 +47,61 @@ public ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellati
public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken)
=> presenterContext.OnDefinitionFoundAsync(
new SearchExceptionDefinitionItem(exception.Message, exception.TypeName, exception.StackTrace, (queryDocument != null) ? new DocumentSpan(queryDocument, exception.Span) : default), cancellationToken);
+
+ public ValueTask OnLogMessageAsync(string message, CancellationToken cancellationToken)
+ {
+ logMessage(message);
+ return ValueTask.CompletedTask;
+ }
+
+ public ValueTask OnDocumentUpdatedAsync(DocumentId documentId, ImmutableArray changes, CancellationToken cancellationToken)
+ {
+ _lazyDocumentUpdates.Value.Push((documentId, changes));
+ return ValueTask.CompletedTask;
+ }
+
+ private ImmutableArray<(DocumentId documentId, ImmutableArray changes)> GetDocumentUpdates()
+ => _lazyDocumentUpdates.IsValueCreated ? [.. _lazyDocumentUpdates.Value] : [];
+
+ public async ValueTask GetUpdatedSolutionAsync(Solution oldSolution, CancellationToken cancellationToken)
+ {
+ var newSolution = oldSolution;
+
+ foreach (var (documentId, changes) in GetDocumentUpdates())
+ {
+ var oldText = await newSolution.GetRequiredDocument(documentId).GetTextAsync(cancellationToken).ConfigureAwait(false);
+ if (changes.IsEmpty)
+ {
+ newSolution = newSolution.RemoveDocument(documentId);
+ }
+ else
+ {
+ // TODO: auto-format/clean up changed spans
+ newSolution = newSolution.WithDocumentText(documentId, oldText.WithChanges(changes));
+
+ var newDocument = newSolution.GetRequiredDocument(documentId);
+ var organizeImportsService = newDocument.GetRequiredLanguageService();
+ var options = await newDocument.GetOrganizeImportsOptionsAsync(cancellationToken).ConfigureAwait(false);
+ newDocument = await organizeImportsService.OrganizeImportsAsync(newDocument, options, cancellationToken).ConfigureAwait(false);
+ var updatedText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ newSolution = newSolution.WithDocumentText(newDocument.Id, updatedText);
+ }
+ }
+
+ return newSolution;
+ }
+
+ public ImmutableArray<(string filePath, string? newContent)> GetFileUpdates()
+ => _lazyTextFileUpdates.IsValueCreated ? _lazyTextFileUpdates.Value.SelectAsArray(static entry => (entry.Key, entry.Value)) : [];
+
+ public ValueTask OnTextFileUpdatedAsync(string filePath, string? newContent, CancellationToken cancellationToken)
+ {
+ _lazyTextFileUpdates.Value.TryAdd(filePath, newContent);
+ return ValueTask.CompletedTask;
+ }
}
- public async Task ExecuteAsync(string? query, Document? queryDocument, Solution solution, CancellationToken cancellationToken)
+ public async Task<(Solution solution, ImmutableArray<(string filePath, string? newContent)> fileUpdates)> ExecuteAsync(string? query, Document? queryDocument, Solution solution, CancellationToken cancellationToken)
{
Contract.ThrowIfFalse(query is null ^ queryDocument is null);
@@ -56,10 +117,10 @@ public async Task ExecuteAsync(string? query, Document? queryDocument, Solution
await presenterContext.OnCompletedAsync(CancellationToken.None).ConfigureAwait(false);
}
- return;
+ return (solution, []);
}
- var resultsObserver = new ResultsObserver(presenterContext, options, queryDocument);
+ var resultsObserver = new ResultsObserver(presenterContext, options, logMessage, queryDocument);
query ??= (await queryDocument!.GetTextAsync(cancellationToken).ConfigureAwait(false)).ToString();
ExecuteQueryResult result = default;
@@ -71,14 +132,13 @@ public async Task ExecuteAsync(string? query, Document? queryDocument, Solution
var compileResult = await RemoteSemanticSearchServiceProxy.CompileQueryAsync(
solution.Services,
query,
- language: LanguageNames.CSharp,
- SemanticSearchUtilities.ReferenceAssembliesDirectory,
+ targetLanguage: null,
cancellationToken).ConfigureAwait(false);
if (compileResult == null)
{
result = new ExecuteQueryResult(FeaturesResources.Semantic_search_only_supported_on_net_core);
- return;
+ return (solution, []);
}
emitTime = compileResult.Value.EmitTime;
@@ -90,14 +150,20 @@ public async Task ExecuteAsync(string? query, Document? queryDocument, Solution
await presenterContext.OnDefinitionFoundAsync(new SearchCompilationFailureDefinitionItem(error, queryDocument), cancellationToken).ConfigureAwait(false);
}
- return;
+ return (solution, []);
}
result = await RemoteSemanticSearchServiceProxy.ExecuteQueryAsync(
solution,
compileResult.Value.QueryId,
resultsObserver,
+ new QueryExecutionOptions(),
cancellationToken).ConfigureAwait(false);
+
+ // apply document changes:
+ var newSolution = await resultsObserver.GetUpdatedSolutionAsync(solution, cancellationToken).ConfigureAwait(false);
+
+ return (newSolution, resultsObserver.GetFileUpdates());
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical))
{
@@ -131,6 +197,8 @@ await presenterContext.ReportMessageAsync(
ReportTelemetry(query, result, emitTime, canceled);
}
+
+ return (solution, []);
}
private static void ReportTelemetry(string queryString, ExecuteQueryResult result, TimeSpan emitTime, bool canceled)
diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs
index 60824cc3e6055..0be2cd65ea7fe 100644
--- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs
+++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs
@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.IO;
using System.Composition;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -13,6 +15,7 @@
using System.Windows.Markup;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
@@ -21,7 +24,9 @@
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SemanticSearch;
+using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
+using Microsoft.CodeAnalysis.Threading;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility;
using Microsoft.VisualStudio.Imaging;
@@ -54,14 +59,19 @@ internal sealed partial class SemanticSearchToolWindowImpl(
IVsEditorAdaptersFactoryService vsEditorAdaptersFactoryService,
IAsynchronousOperationListenerProvider listenerProvider,
IGlobalOptionService globalOptions,
+ Lazy semanticSearchService,
VisualStudioWorkspace workspace,
IStreamingFindUsagesPresenter resultsPresenter,
ITextUndoHistoryRegistry undoHistoryRegistry,
- IVsService vsUIShellProvider) : IDisposable
+ IVsService vsUIShellProvider,
+ IPreviewFactoryService previewFactory) : IDisposable
{
private const int ToolBarHeight = 26;
private const int ToolBarButtonSize = 20;
+ private static readonly Guid s_logOutputPainGuid = new("{4C4F1810-C865-493E-98A7-8E1120A9FDE4}");
+ private const string LogOutputPaneName = "Semantic Search Log";
+
private static readonly Lazy s_buttonTemplate = new(CreateButtonTemplate);
private readonly IContentType _contentType = contentTypeRegistry.GetContentType(CSharpSemanticSearchContentType.Name);
@@ -70,13 +80,16 @@ internal sealed partial class SemanticSearchToolWindowImpl(
private readonly Lazy _semanticSearchWorkspace
= new(() => new SemanticSearchEditorWorkspace(
hostWorkspaceProvider.Workspace.Services.HostServices,
- CSharpSemanticSearchUtilities.Configuration,
+ semanticSearchService.Value,
threadingContext,
listenerProvider));
// access interlocked:
private volatile CancellationTokenSource? _pendingExecutionCancellationSource;
+ // Create on UI thread only, access on any thread:
+ private AsyncBatchingWorkQueue? _lazyLogQueue;
+
// Access on UI thread only:
private Button? _executeButton;
private Button? _cancelButton;
@@ -84,12 +97,16 @@ private readonly Lazy _semanticSearchWorkspace
private ITextBuffer? _textBuffer;
private IRemoteUserControl? _lazyContent;
+ private IVsOutputWindowPane? _lazyLogOutputPane;
public void Dispose()
{
_lazyContent?.Dispose();
}
+ public ISemanticSearchSolutionService SemanticSearchService
+ => semanticSearchService.Value;
+
public async Task InitializeAsync(CancellationToken cancellationToken)
{
var content = _lazyContent;
@@ -123,7 +140,7 @@ private async Task CreateContentAsync(CancellationToken cancel
// enable LSP:
Contract.ThrowIfFalse(textDocumentFactory.TryGetTextDocument(_textBuffer, out var textDocument));
- textDocument.Rename(SemanticSearchUtilities.GetDocumentFilePath(LanguageNames.CSharp));
+ textDocument.Rename(SemanticSearchService.GetQueryDocumentFilePath());
var toolWindowGrid = new Grid();
toolWindowGrid.ColumnDefinitions.Add(new ColumnDefinition());
@@ -381,11 +398,27 @@ public void RunQuery()
UpdateUIState();
+ _lazyLogQueue ??= new(
+ delay: TimeSpan.Zero,
+ async (messages, cancellationToken) =>
+ {
+ await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None);
+
+ var pane = GetOrCreateLogOutputPane();
+
+ foreach (var message in messages)
+ {
+ pane.OutputStringThreadSafe(message + Environment.NewLine);
+ }
+ },
+ _asyncListener,
+ cancellationToken: CancellationToken.None);
+
var (presenterContext, presenterCancellationToken) = resultsPresenter.StartSearch(ServicesVSResources.Semantic_search_results, StreamingFindUsagesPresenterOptions.Default);
presenterCancellationToken.Register(() => cancellationSource?.Cancel());
var querySolution = _semanticSearchWorkspace.Value.CurrentSolution;
- var queryDocument = SemanticSearchUtilities.GetQueryDocument(querySolution);
+ var queryDocument = querySolution.GetRequiredDocument(SemanticSearchService.GetQueryDocumentId(querySolution));
var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchToolWindow) + ".Execute");
_ = ExecuteAsync(cancellationSource.Token).ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken);
@@ -396,8 +429,57 @@ async Task ExecuteAsync(CancellationToken cancellationToken)
try
{
- var executor = new SemanticSearchQueryExecutor(presenterContext, globalOptions);
- await executor.ExecuteAsync(query: null, queryDocument, workspace.CurrentSolution, cancellationToken).ConfigureAwait(false);
+ var executor = new SemanticSearchQueryExecutor(presenterContext, message => _lazyLogQueue.AddWork(message), globalOptions);
+ var oldSolution = workspace.CurrentSolution;
+ var (newSolution, fileUpdates) = await executor.ExecuteAsync(query: null, queryDocument, oldSolution, cancellationToken).ConfigureAwait(false);
+
+ var success = true;
+ if (newSolution != oldSolution)
+ {
+ var changedSolution = newSolution;
+
+ var previewDialogService = workspace.Services.GetService();
+ if (previewDialogService != null &&
+ previewFactory.GetSolutionPreviews(oldSolution, newSolution, cancellationToken)?.ChangeSummary is { } changeSummary)
+ {
+ await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None);
+
+ changedSolution = previewDialogService.PreviewChanges(
+ EditorFeaturesResources.Preview_Changes,
+ "vs.codefix.previewchanges",
+ "Updates",
+ EditorFeaturesResources.Changes,
+ Glyph.OpenFolder,
+ changeSummary.NewSolution,
+ changeSummary.OldSolution,
+ showCheckBoxes: false);
+
+ // TODO: report error
+ success = changedSolution != null && workspace.TryApplyChanges(changedSolution);
+ }
+ else
+ {
+ await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None);
+
+ success = workspace.TryApplyChanges(changedSolution);
+ }
+ }
+
+ if (success)
+ {
+ // TODO: parallelize, exceptions
+ foreach (var (filePath, newContent) in fileUpdates)
+ {
+ if (newContent == null)
+ {
+ File.Delete(filePath);
+ }
+ else
+ {
+ File.WriteAllText(filePath, newContent, Encoding.UTF8);
+ }
+ }
+ }
}
finally
{
@@ -440,6 +522,26 @@ public NavigableLocation GetNavigableLocation(TextSpan textSpan)
return true;
});
+ private IVsOutputWindowPane GetOrCreateLogOutputPane()
+ {
+ if (_lazyLogOutputPane != null)
+ return _lazyLogOutputPane;
+
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ var outputWindow = ServiceProvider.GlobalProvider.GetServiceOnMainThread();
+
+ // Try to get the pane, create if it doesn't exist
+ var guid = s_logOutputPainGuid;
+ if (outputWindow.GetPane(ref guid, out _lazyLogOutputPane) != VSConstants.S_OK || _lazyLogOutputPane == null)
+ {
+ outputWindow.CreatePane(ref guid, LogOutputPaneName, fInitVisible: 1, fClearWithSolution: 1);
+ outputWindow.GetPane(ref guid, out _lazyLogOutputPane);
+ }
+
+ return _lazyLogOutputPane;
+ }
+
private sealed class CommandFilter : IOleCommandTarget
{
private readonly SemanticSearchToolWindowImpl _window;
diff --git a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs
index d96ce859173e6..86926f690d611 100644
--- a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs
+++ b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs
@@ -551,4 +551,73 @@ public void M()
testState.VerifyRoot(root, "Class1.Class1(string)", [string.Format(EditorFeaturesResources.Calls_To_0, ".ctor")]);
testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, ".ctor"), ["D.M()"]);
}
+
+ [WpfFact]
+ [WorkItem("https://github.com/dotnet/roslyn/issues/71068")]
+ public async Task Method_ExcludeNameofReferencesWithoutMemberAccess()
+ {
+ var text = """
+ namespace N
+ {
+ class G
+ {
+ void B$$oo()
+ {
+ }
+
+ void Main()
+ {
+ var g = new G();
+ g.Boo(); // This should appear in call hierarchy
+ }
+
+ void TestNameof()
+ {
+ var methodName = nameof(Boo); // This should NOT appear
+ }
+ }
+ }
+ """;
+ using var testState = CallHierarchyTestState.Create(text);
+ var root = await testState.GetRootAsync();
+ testState.VerifyRoot(root, "N.G.Boo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Boo")]);
+ // Only the actual method call should appear, not the nameof reference
+ testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Boo"), ["N.G.Main()"]);
+ }
+
+ [WpfFact]
+ [WorkItem("https://github.com/dotnet/roslyn/issues/71068")]
+ public async Task Method_ExcludeNameofReferences()
+ {
+ var text = """
+ namespace N
+ {
+ class C
+ {
+ void G$$oo()
+ {
+ }
+ }
+
+ class G
+ {
+ void Main()
+ {
+ var c = new C();
+ c.Goo(); // This should appear in call hierarchy
+ }
+
+ void TestNameof()
+ {
+ var methodName = nameof(C.Goo); // This should NOT appear
+ }
+ }
+ }
+ """;
+ using var testState = CallHierarchyTestState.Create(text);
+ var root = await testState.GetRootAsync();
+ testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo")]);
+ // Only the actual method call should appear, not the nameof reference
+ testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main()"]);
+ }
}
diff --git a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs
index 222a13c2d8a2b..144857ca03451 100644
--- a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs
+++ b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs
@@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
@@ -149,22 +150,22 @@ private async Task> FixUpDescriptors
continue;
}
- var spanMapper = document.DocumentServiceProvider.GetService();
- if (spanMapper == null)
+ var span = new TextSpan(descriptor.SpanStart, descriptor.SpanLength);
+ var results = await SpanMappingHelper.TryGetMappedSpanResultAsync(document, [span], cancellationToken).ConfigureAwait(false);
+ if (results is null)
{
// for normal document, just add one as they are
list.Add(descriptor);
continue;
}
- var span = new TextSpan(descriptor.SpanStart, descriptor.SpanLength);
- var results = await spanMapper.MapSpansAsync(document, [span], cancellationToken).ConfigureAwait(false);
+ var mappedSpans = results.GetValueOrDefault();
// external component violated contracts. the mapper should preserve input order/count.
// since we gave in 1 span, it should return 1 span back
- Contract.ThrowIfTrue(results.IsDefaultOrEmpty);
+ Contract.ThrowIfTrue(mappedSpans.IsDefaultOrEmpty);
- var result = results[0];
+ var result = mappedSpans[0];
if (result.IsDefault)
{
// it is allowed for mapper to return default
@@ -175,6 +176,30 @@ private async Task> FixUpDescriptors
var excerpter = document.DocumentServiceProvider.GetService();
if (excerpter == null)
{
+ if (document.IsRazorSourceGeneratedDocument())
+ {
+ // HACK: Razor doesn't have has a workspace level excerpt service, but if we just return a simple descriptor here,
+ // the user at least sees something, can navigate, and Razor can improve this later if necessary. Until
+ // https://github.com/dotnet/roslyn/issues/79699 is fixed this won't get hit anyway.
+ list.Add(new ReferenceLocationDescriptor(
+ descriptor.LongDescription,
+ descriptor.Language,
+ descriptor.Glyph,
+ result.Span.Start,
+ result.Span.Length,
+ result.LinePositionSpan.Start.Line,
+ result.LinePositionSpan.Start.Character,
+ descriptor.ProjectGuid,
+ descriptor.DocumentGuid,
+ result.FilePath,
+ descriptor.ReferenceLineText,
+ descriptor.ReferenceStart,
+ descriptor.ReferenceLength,
+ "",
+ "",
+ "",
+ ""));
+ }
continue;
}
diff --git a/src/VisualStudio/Core/Def/Commands.vsct b/src/VisualStudio/Core/Def/Commands.vsct
index 252b01bc7f9ec..0ecb421ea8d60 100644
--- a/src/VisualStudio/Core/Def/Commands.vsct
+++ b/src/VisualStudio/Core/Def/Commands.vsct
@@ -478,7 +478,6 @@
diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest
index 748a713906083..49bbcac6d86e3 100644
--- a/src/VisualStudio/Setup/source.extension.vsixmanifest
+++ b/src/VisualStudio/Setup/source.extension.vsixmanifest
@@ -70,6 +70,7 @@
+
diff --git a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb
index 626916dfee1c3..c50bb16f04b14 100644
--- a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb
+++ b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb
@@ -16,6 +16,7 @@ Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Externa
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Interop
Imports Microsoft.VisualStudio.LanguageServices.Implementation.Interop
+Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks
Imports Microsoft.VisualStudio.Shell.Interop
Imports Roslyn.Test.Utilities
@@ -28,7 +29,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
GetType(StubVsServiceExporter(Of )),
GetType(StubVsServiceExporter(Of ,)),
GetType(MockVisualStudioWorkspace),
- GetType(ProjectCodeModelFactory))
+ GetType(ProjectCodeModelFactory),
+ GetType(ExternalErrorDiagnosticUpdateSource),
+ GetType(MockServiceBroker))
Public SystemWindowsFormsPath As String
Public SystemDrawingPath As String
diff --git a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb
index b5b5db66e8ee7..5aa8317153fe7 100644
--- a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb
+++ b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb
@@ -84,6 +84,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks
Public Overrides Sub EnsureEditableDocuments(documents As IEnumerable(Of DocumentId))
' Nothing to do here
End Sub
+
+ Protected Overrides Sub SubscribeToSourceGeneratorImpactingEvents()
+ ' HACK: We override this method in unit tests to do nothing. The base type uses WhenActivated which can cause a leak if those handlers don't actually
+ ' run, and that API gives us no way to unsubscribe. Further; right now raising events to update the source generator versions
+ ' causes TryApplyChanges to also fail in unit tests because of https://github.com/dotnet/roslyn/issues/79587.
+ End Sub
End Class
Public Class MockInvisibleEditor
diff --git a/src/VisualStudio/TestUtilities2/MockServiceBroker.vb b/src/VisualStudio/TestUtilities2/MockServiceBroker.vb
new file mode 100644
index 0000000000000..63c166f635e03
--- /dev/null
+++ b/src/VisualStudio/TestUtilities2/MockServiceBroker.vb
@@ -0,0 +1,48 @@
+' 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.Collections.Concurrent
+Imports System.ComponentModel.Composition
+Imports System.IO.Pipelines
+Imports System.Threading
+Imports System.Threading.Tasks
+Imports Microsoft.CodeAnalysis.Host.Mef
+Imports Microsoft.ServiceHub.Framework
+Imports Microsoft.VisualStudio.Shell.ServiceBroker
+Imports Roslyn.Utilities
+
+Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
+
+
+
+ Friend Class MockServiceBroker
+ Implements IServiceBroker
+
+ Private ReadOnly _services As New ConcurrentDictionary(Of Type, Object)()
+
+
+
+ Public Sub New()
+ End Sub
+
+ Public Sub RegisterService(Of T)(service As T)
+ _services.Add(GetType(T), service)
+ End Sub
+
+ Public Event AvailabilityChanged As EventHandler(Of BrokeredServicesChangedEventArgs) Implements IServiceBroker.AvailabilityChanged
+
+ Public Function GetProxyAsync(Of T As Class)(serviceDescriptor As ServiceRpcDescriptor, Optional options As ServiceActivationOptions = Nothing, Optional cancellationToken As CancellationToken = Nothing) As ValueTask(Of T) Implements IServiceBroker.GetProxyAsync
+ Dim service As Object = Nothing
+ If _services.TryGetValue(GetType(T), service) Then
+ Return New ValueTask(Of T)(CType(service, T))
+ End If
+
+ Throw New InvalidOperationException("The MockServiceBroker does not have a registered service for " + GetType(T).FullName)
+ End Function
+
+ Public Function GetPipeAsync(serviceMoniker As ServiceMoniker, Optional options As ServiceActivationOptions = Nothing, Optional cancellationToken As CancellationToken = Nothing) As ValueTask(Of IDuplexPipe) Implements IServiceBroker.GetPipeAsync
+ Throw New NotImplementedException()
+ End Function
+ End Class
+End Namespace
diff --git a/src/VisualStudio/TestUtilities2/MockServiceProvider.vb b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb
index 9584002c56ba2..68dde06460c24 100644
--- a/src/VisualStudio/TestUtilities2/MockServiceProvider.vb
+++ b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb
@@ -26,6 +26,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
Private ReadOnly _fileChangeEx As New MockVsFileChangeEx
Public MockMonitorSelection As IVsMonitorSelection
+ Public MockRunningDocumentTable As New MockVsRunningDocumentTable
@@ -56,11 +57,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
Return _fileChangeEx
Case GetType(SVsRunningDocumentTable)
- Dim mock = New Mock(Of IVsRunningDocumentTable)
- mock.As(Of IVsRunningDocumentTable4)()
-
- mock.Setup(Function(m) m.AdviseRunningDocTableEvents(It.IsAny(Of IVsRunningDocTableEvents), It.IsAny(Of UInteger))).Returns(VSConstants.S_OK)
- Return mock.Object
+ Return MockRunningDocumentTable
Case Else
Throw New Exception($"{NameOf(MockServiceProvider)} does not implement {serviceType.FullName}.")
diff --git a/src/VisualStudio/TestUtilities2/MockVsRunningDocumentTable.vb b/src/VisualStudio/TestUtilities2/MockVsRunningDocumentTable.vb
new file mode 100644
index 0000000000000..b6bbf3a590357
--- /dev/null
+++ b/src/VisualStudio/TestUtilities2/MockVsRunningDocumentTable.vb
@@ -0,0 +1,194 @@
+' Licensed to the .NET Foundation under one or more agreements.
+' The .NET Foundation licenses this file to you under the MIT license.
+' See the LICENSE file in the project root for more information.
+Imports Microsoft.VisualStudio.ProjectSystem.VS
+Imports Microsoft.VisualStudio.Shell.Interop
+Imports Roslyn.Test.Utilities
+
+Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
+
+ Public Class MockVsRunningDocumentTable
+ Implements IVsRunningDocumentTable
+ Implements IVsRunningDocumentTable4
+ Implements SVsRunningDocumentTable
+
+ Private _lastDocTableEventsCooke As UInteger = 0
+
+ Public Function RegisterAndLockDocument(grfRDTLockType As UInteger, pszMkDocument As String, pHier As IVsHierarchy, itemid As UInteger, punkDocData As IntPtr, ByRef pdwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.RegisterAndLockDocument
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function LockDocument(grfRDTLockType As UInteger, dwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.LockDocument
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function UnlockDocument(grfRDTLockType As UInteger, dwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.UnlockDocument
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function FindAndLockDocument(dwRDTLockType As UInteger, pszMkDocument As String, ByRef ppHier As IVsHierarchy, ByRef pitemid As UInteger, ByRef ppunkDocData As IntPtr, ByRef pdwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.FindAndLockDocument
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function RenameDocument(pszMkDocumentOld As String, pszMkDocumentNew As String, pHier As IntPtr, itemidNew As UInteger) As Integer Implements IVsRunningDocumentTable.RenameDocument
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function AdviseRunningDocTableEvents(pSink As IVsRunningDocTableEvents, ByRef pdwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.AdviseRunningDocTableEvents
+ _lastDocTableEventsCooke = _lastDocTableEventsCooke + CType(1, UInteger)
+ pdwCookie = _lastDocTableEventsCooke
+ Return VSConstants.S_OK
+ End Function
+
+ Public Function UnadviseRunningDocTableEvents(dwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.UnadviseRunningDocTableEvents
+ Return VSConstants.S_OK
+ End Function
+
+ Public Function GetDocumentInfo(docCookie As UInteger, ByRef pgrfRDTFlags As UInteger, ByRef pdwReadLocks As UInteger, ByRef pdwEditLocks As UInteger, ByRef pbstrMkDocument As String, ByRef ppHier As IVsHierarchy, ByRef pitemid As UInteger, ByRef ppunkDocData As IntPtr) As Integer Implements IVsRunningDocumentTable.GetDocumentInfo
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function NotifyDocumentChanged(dwCookie As UInteger, grfDocChanged As UInteger) As Integer Implements IVsRunningDocumentTable.NotifyDocumentChanged
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function NotifyOnAfterSave(dwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.NotifyOnAfterSave
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetRunningDocumentsEnum(ByRef ppenum As IEnumRunningDocuments) As Integer Implements IVsRunningDocumentTable.GetRunningDocumentsEnum
+ ppenum = New MockEnumRunningDocuments()
+ Return VSConstants.S_OK
+ End Function
+
+ Public Function SaveDocuments(grfSaveOpts As UInteger, pHier As IVsHierarchy, itemid As UInteger, docCookie As UInteger) As Integer Implements IVsRunningDocumentTable.SaveDocuments
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function NotifyOnBeforeSave(dwCookie As UInteger) As Integer Implements IVsRunningDocumentTable.NotifyOnBeforeSave
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function RegisterDocumentLockHolder(grfRDLH As UInteger, dwCookie As UInteger, pLockHolder As IVsDocumentLockHolder, ByRef pdwLHCookie As UInteger) As Integer Implements IVsRunningDocumentTable.RegisterDocumentLockHolder
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function UnregisterDocumentLockHolder(dwLHCookie As UInteger) As Integer Implements IVsRunningDocumentTable.UnregisterDocumentLockHolder
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function ModifyDocumentFlags(docCookie As UInteger, grfFlags As UInteger, fSet As Integer) As Integer Implements IVsRunningDocumentTable.ModifyDocumentFlags
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetRelatedSaveTreeItems(cookie As UInteger, grfSave As UInteger, celt As UInteger, rgSaveTreeItems() As VSSAVETREEITEM) As UInteger Implements IVsRunningDocumentTable4.GetRelatedSaveTreeItems
+ Throw New NotImplementedException()
+ End Function
+
+ Public Sub NotifyDocumentChangedEx(cookie As UInteger, attributes As UInteger) Implements IVsRunningDocumentTable4.NotifyDocumentChangedEx
+ Throw New NotImplementedException()
+ End Sub
+
+ Public Function IsDocumentDirty(cookie As UInteger) As Boolean Implements IVsRunningDocumentTable4.IsDocumentDirty
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function IsDocumentReadOnly(cookie As UInteger) As Boolean Implements IVsRunningDocumentTable4.IsDocumentReadOnly
+ Throw New NotImplementedException()
+ End Function
+
+ Public Sub UpdateDirtyState(cookie As UInteger) Implements IVsRunningDocumentTable4.UpdateDirtyState
+ Throw New NotImplementedException()
+ End Sub
+
+ Public Sub UpdateReadOnlyState(cookie As UInteger) Implements IVsRunningDocumentTable4.UpdateReadOnlyState
+ Throw New NotImplementedException()
+ End Sub
+
+ Public Function IsMonikerValid(moniker As String) As Boolean Implements IVsRunningDocumentTable4.IsMonikerValid
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function IsCookieValid(cookie As UInteger) As Boolean Implements IVsRunningDocumentTable4.IsCookieValid
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentCookie(moniker As String) As UInteger Implements IVsRunningDocumentTable4.GetDocumentCookie
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentFlags(cookie As UInteger) As UInteger Implements IVsRunningDocumentTable4.GetDocumentFlags
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentReadLockCount(cookie As UInteger) As UInteger Implements IVsRunningDocumentTable4.GetDocumentReadLockCount
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentEditLockCount(cookie As UInteger) As UInteger Implements IVsRunningDocumentTable4.GetDocumentEditLockCount
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentMoniker(cookie As UInteger) As String Implements IVsRunningDocumentTable4.GetDocumentMoniker
+ Throw New NotImplementedException()
+ End Function
+
+ Public Sub GetDocumentHierarchyItem(cookie As UInteger, ByRef hierarchy As IVsHierarchy, ByRef itemID As UInteger) Implements IVsRunningDocumentTable4.GetDocumentHierarchyItem
+ Throw New NotImplementedException()
+ End Sub
+
+ Public Function GetDocumentData(cookie As UInteger) As Object Implements IVsRunningDocumentTable4.GetDocumentData
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetDocumentProjectGuid(cookie As UInteger) As Guid Implements IVsRunningDocumentTable4.GetDocumentProjectGuid
+ Throw New NotImplementedException()
+ End Function
+
+ Private Function IVsRunningDocumentTable3_GetRelatedSaveTreeItems(cookie As UInteger, grfSave As UInteger, celt As UInteger, rgSaveTreeItems() As VSSAVETREEITEM) As UInteger Implements IVsRunningDocumentTable3.GetRelatedSaveTreeItems
+ Return GetRelatedSaveTreeItems(cookie, grfSave, celt, rgSaveTreeItems)
+ End Function
+
+ Private Sub IVsRunningDocumentTable3_NotifyDocumentChangedEx(cookie As UInteger, attributes As UInteger) Implements IVsRunningDocumentTable3.NotifyDocumentChangedEx
+ NotifyDocumentChangedEx(cookie, attributes)
+ End Sub
+
+ Private Function IVsRunningDocumentTable3_IsDocumentDirty(cookie As UInteger) As Boolean Implements IVsRunningDocumentTable3.IsDocumentDirty
+ Return IsDocumentDirty(cookie)
+ End Function
+
+ Private Function IVsRunningDocumentTable3_IsDocumentReadOnly(cookie As UInteger) As Boolean Implements IVsRunningDocumentTable3.IsDocumentReadOnly
+ Return IsDocumentReadOnly(cookie)
+ End Function
+
+ Private Sub IVsRunningDocumentTable3_UpdateDirtyState(cookie As UInteger) Implements IVsRunningDocumentTable3.UpdateDirtyState
+ UpdateDirtyState(cookie)
+ End Sub
+
+ Private Sub IVsRunningDocumentTable3_UpdateReadOnlyState(cookie As UInteger) Implements IVsRunningDocumentTable3.UpdateReadOnlyState
+ UpdateReadOnlyState(cookie)
+ End Sub
+ End Class
+
+ Public Class MockEnumRunningDocuments
+ Implements IEnumRunningDocuments
+
+ Public Function [Next](celt As UInteger, rgelt() As UInteger, ByRef pceltFetched As UInteger) As Integer Implements IEnumRunningDocuments.Next
+ pceltFetched = 0
+ Return VSConstants.S_FALSE
+ End Function
+
+ Public Function Skip(celt As UInteger) As Integer Implements IEnumRunningDocuments.Skip
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function Reset() As Integer Implements IEnumRunningDocuments.Reset
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function Clone(ByRef ppenum As IEnumRunningDocuments) As Integer Implements IEnumRunningDocuments.Clone
+ Throw New NotImplementedException()
+ End Function
+ End Class
+
+End Namespace
diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb
index 4b1bbf1f9a740..e2fcf746a962c 100644
--- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb
+++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb
@@ -22,6 +22,7 @@ Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBro
Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS
Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy
+Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
Imports Microsoft.VisualStudio.LanguageServices.Telemetry
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
@@ -69,7 +70,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
GetType(DiagnosticAnalyzerService),
GetType(VisualStudioWorkspaceTelemetryService),
GetType(OpenTextBufferProvider),
- GetType(StubVsEditorAdaptersFactoryService))
+ GetType(StubVsEditorAdaptersFactoryService),
+ GetType(ExternalErrorDiagnosticUpdateSource),
+ GetType(MockServiceBroker))
Private ReadOnly _workspace As VisualStudioWorkspaceImpl
Private ReadOnly _projectFilePaths As New List(Of String)
diff --git a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb
index 373374a5d3ba1..6c8e3a64cb032 100644
--- a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb
+++ b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb
@@ -6,6 +6,7 @@ Imports Microsoft.CodeAnalysis.Editor.Host
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.VisualStudio.LanguageServices.CSharp
+Imports Microsoft.VisualStudio.LanguageServices.Implementation
Imports Microsoft.VisualStudio.LanguageServices.Remote
Imports Microsoft.VisualStudio.LanguageServices.VisualBasic
@@ -25,7 +26,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests
GetType(VisualStudioRemoteHostClientProvider.Factory), ' Do not use ServiceHub in VS unit tests, run services locally.
GetType(IStreamingFindUsagesPresenter), ' TODO: should we be using the actual implementation (https://github.com/dotnet/roslyn/issues/46380)?
GetType(HACK_ThemeColorFixer),
- GetType(Implementation.Notification.VSNotificationServiceFactory),
- GetType(Options.VisualStudioOptionPersisterProvider))
+ GetType(Notification.VSNotificationServiceFactory),
+ GetType(Options.VisualStudioOptionPersisterProvider),
+ GetType(VisualStudioWorkspaceStatusServiceFactory), ' Depends on other packages being loaded, and it's not really clear how it would work in unit tests anyways
+ GetType(VisualStudioDocumentTrackingServiceFactory)) ' Depends on IVsMonitorSelection, and removing it falls back to the default no-op implementation.
End Class
End Namespace
diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs
index e7eaa68086541..63e45dbf7ed5e 100644
--- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs
+++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs
@@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
@@ -19,6 +18,7 @@
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.Options;
+using Roslyn.Utilities;
namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages;
@@ -67,7 +67,7 @@ public static void SetLoggers(IGlobalOptionService globalOptions, IThreadingCont
var client = threadingContext.JoinableTaskFactory.Run(() => RemoteHostClient.TryGetClientAsync(workspaceServices, CancellationToken.None));
if (client != null)
{
- var functionIds = Enum.GetValues().Where(isEnabled).ToImmutableArray();
+ var functionIds = Enum.GetValues().WhereAsArray(isEnabled);
threadingContext.JoinableTaskFactory.Run(async () => _ = await client.TryInvokeAsync(
(service, cancellationToken) => service.EnableLoggingAsync(loggerTypeNames, functionIds, cancellationToken),
diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs
index 58aba76096271..3222e5fd6979c 100644
--- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs
+++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs
@@ -139,7 +139,7 @@ private static SumType GetCommitCharacter
var xamlCommitCharacters = completionItem.XamlCommitCharacters.Value;
- var commitCharacters = xamlCommitCharacters.Characters.Select(c => new VSInternalCommitCharacter { Character = c.ToString(), Insert = !xamlCommitCharacters.NonInsertCharacters.Contains(c) }).ToImmutableArray();
+ var commitCharacters = xamlCommitCharacters.Characters.SelectAsArray(c => new VSInternalCommitCharacter { Character = c.ToString(), Insert = !xamlCommitCharacters.NonInsertCharacters.Contains(c) });
commitCharactersCache.Add(completionItem.Kind, commitCharacters);
return commitCharacters.ToArray();
}
diff --git a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj
index c77015a1b03d7..3da02601991d4 100644
--- a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj
+++ b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj
@@ -48,7 +48,7 @@
-
+
@@ -62,6 +62,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs
index 80b1c93c982d1..a4b9d1b06135b 100644
--- a/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs
+++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs
@@ -68,17 +68,8 @@ public abstract partial class CodeAction
s_cleanupSyntaxPass,
];
- internal static async Task CleanupSyntaxAsync(Document document, CodeCleanupOptions options, CancellationToken cancellationToken)
- {
- Contract.ThrowIfFalse(document.SupportsSyntaxTree);
-
- // format any node with explicit formatter annotation
- var document1 = await Formatter.FormatAsync(document, Formatter.Annotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false);
-
- // format any elastic whitespace
- var document2 = await Formatter.FormatAsync(document1, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false);
- return document2;
- }
+ internal static Task CleanupSyntaxAsync(Document document, CodeCleanupOptions options, CancellationToken cancellationToken)
+ => CodeCleanupHelpers.CleanupSyntaxAsync(document, options, cancellationToken);
internal static ImmutableArray GetAllChangedOrAddedDocumentIds(
Solution originalSolution,
diff --git a/src/Workspaces/Core/Portable/CodeActions/Operations/ApplyChangesOperation.cs b/src/Workspaces/Core/Portable/CodeActions/Operations/ApplyChangesOperation.cs
index 4f4fabfc8db86..472c72ec61d23 100644
--- a/src/Workspaces/Core/Portable/CodeActions/Operations/ApplyChangesOperation.cs
+++ b/src/Workspaces/Core/Portable/CodeActions/Operations/ApplyChangesOperation.cs
@@ -53,7 +53,7 @@ internal static bool ApplyOrMergeChanges(
var currentSolution = workspace.CurrentSolution;
// if there was no intermediary edit, just apply the change fully.
- if (changedSolution.WorkspaceVersion == currentSolution.WorkspaceVersion)
+ if (changedSolution.SolutionStateContentVersion == currentSolution.SolutionStateContentVersion)
{
var result = workspace.TryApplyChanges(changedSolution, progressTracker);
diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs
index 0c75d33aa929e..39e606596519a 100644
--- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs
+++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs
@@ -93,7 +93,7 @@ internal static async Task>();
- await foreach (var (project, diagnostics) in results)
+ await foreach (var (project, diagnostics) in results.ConfigureAwait(false))
{
if (diagnostics.Any())
projectsAndDiagnostics.Add(project, diagnostics);
diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/FixAllProviderInfo.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/FixAllProviderInfo.cs
index c14ea9c150d24..0029db84f0fbb 100644
--- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/FixAllProviderInfo.cs
+++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/FixAllProviderInfo.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Linq;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Roslyn.Utilities;
@@ -52,21 +51,15 @@ private FixAllProviderInfo(
{
var fixAllProvider = provider.GetFixAllProvider();
if (fixAllProvider == null)
- {
return null;
- }
- var diagnosticIds = fixAllProvider.GetSupportedFixAllDiagnosticIds(provider);
- if (diagnosticIds == null || diagnosticIds.IsEmpty())
- {
+ var diagnosticIds = fixAllProvider.GetSupportedFixAllDiagnosticIds(provider).ToImmutableArrayOrEmpty();
+ if (diagnosticIds.IsEmpty)
return null;
- }
var scopes = fixAllProvider.GetSupportedFixAllScopes().ToImmutableArrayOrEmpty();
if (scopes.IsEmpty)
- {
return null;
- }
return new CodeFixerFixAllProviderInfo(fixAllProvider, diagnosticIds, scopes);
}
@@ -115,7 +108,7 @@ private FixAllProviderInfo(
private sealed class CodeFixerFixAllProviderInfo(
IFixAllProvider fixAllProvider,
- IEnumerable supportedDiagnosticIds,
+ ImmutableArray supportedDiagnosticIds,
ImmutableArray supportedScopes) : FixAllProviderInfo(fixAllProvider, supportedScopes)
{
public override bool CanBeFixed(Diagnostic diagnostic)
diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs
index c358e23471480..00233f4dadee1 100644
--- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs
+++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs
@@ -94,14 +94,19 @@ internal sealed class DiagnosticData(
///
/// Properties for a diagnostic generated by an explicit build.
///
- internal static ImmutableDictionary PropertiesForBuildDiagnostic { get; }
- = ImmutableDictionary.Empty.Add(WellKnownDiagnosticPropertyNames.Origin, WellKnownDiagnosticTags.Build);
+ internal static ImmutableDictionary PropertiesForBuildDiagnostic { get; }
+ = ImmutableDictionary.Empty.Add(WellKnownDiagnosticPropertyNames.Origin, WellKnownDiagnosticTags.Build);
public DiagnosticData WithLocations(DiagnosticDataLocation location, ImmutableArray additionalLocations)
=> new(Id, Category, Message, Severity, DefaultSeverity, IsEnabledByDefault,
WarningLevel, CustomTags, Properties, ProjectId, location, additionalLocations,
Language, Title, Description, HelpLink, IsSuppressed);
+ public DiagnosticData WithCustomTags(ImmutableArray customTags)
+ => new(Id, Category, Message, Severity, DefaultSeverity, IsEnabledByDefault,
+ WarningLevel, customTags, Properties, ProjectId, DataLocation, AdditionalLocations,
+ Language, Title, Description, HelpLink, IsSuppressed);
+
public DocumentId? DocumentId => DataLocation.DocumentId;
public override bool Equals(object? obj)
diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDescriptorData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDescriptorData.cs
new file mode 100644
index 0000000000000..a82150452de08
--- /dev/null
+++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDescriptorData.cs
@@ -0,0 +1,70 @@
+// 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.Globalization;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace Microsoft.CodeAnalysis.Diagnostics;
+
+[DataContract]
+internal sealed class DiagnosticDescriptorData(
+ string id,
+ string title,
+ string messageFormat,
+ string category,
+ DiagnosticSeverity defaultSeverity,
+ bool isEnabledByDefault,
+ string? description,
+ string? helpLinkUri,
+ ImmutableArray customTags)
+{
+ [DataMember(Order = 0)]
+ public readonly string Id = id;
+ [DataMember(Order = 1)]
+ public readonly string Title = title;
+ [DataMember(Order = 2)]
+ public readonly string MessageFormat = messageFormat;
+ [DataMember(Order = 3)]
+ public readonly string Category = category;
+ [DataMember(Order = 4)]
+ public readonly DiagnosticSeverity DefaultSeverity = defaultSeverity;
+ [DataMember(Order = 5)]
+ public readonly bool IsEnabledByDefault = isEnabledByDefault;
+ [DataMember(Order = 6)]
+ public readonly string? Description = description;
+ [DataMember(Order = 7)]
+ public readonly string? HelpLinkUri = helpLinkUri;
+ [DataMember(Order = 8)]
+ public readonly ImmutableArray CustomTags = customTags;
+
+ public static DiagnosticDescriptorData Create(DiagnosticDescriptor descriptor)
+ {
+ return new DiagnosticDescriptorData(
+ descriptor.Id,
+ descriptor.Title.ToString(CultureInfo.CurrentUICulture),
+ descriptor.MessageFormat.ToString(CultureInfo.CurrentUICulture),
+ descriptor.Category,
+ descriptor.DefaultSeverity,
+ descriptor.IsEnabledByDefault,
+ descriptor.Description?.ToString(CultureInfo.CurrentUICulture),
+ descriptor.HelpLinkUri,
+ customTags: descriptor.CustomTags.AsImmutableOrEmpty());
+ }
+
+ public DiagnosticDescriptor ToDiagnosticDescriptor()
+ {
+ return new DiagnosticDescriptor(
+ Id,
+ Title,
+ MessageFormat,
+ Category,
+ DefaultSeverity,
+ IsEnabledByDefault,
+ Description,
+ HelpLinkUri,
+ ImmutableCollectionsMarshal.AsArray(CustomTags)!);
+ }
+}
diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs
index aed55c8038c56..81d1a58b3b3bb 100644
--- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs
+++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs
@@ -409,7 +409,7 @@ await AnalyzeDocumentAsync(
else
{
using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder);
- await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken))
+ await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false))
{
await AnalyzeDocumentAsync(
compilationWithAnalyzers.HostCompilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer,
diff --git a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
index d212ebc32a8a1..9ac10bf9a965b 100644
--- a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
+++ b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
@@ -15,6 +15,8 @@ internal interface IRemoteDiagnosticAnalyzerService
ValueTask CalculateDiagnosticsAsync(Checksum solutionChecksum, DiagnosticArguments arguments, CancellationToken cancellationToken);
ValueTask> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken);
ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray snapshot, int unitCount, bool forSpanAnalysis, CancellationToken cancellationToken);
+ ValueTask> GetDiagnosticDescriptorsAsync(
+ Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, CancellationToken cancellationToken);
}
[DataContract]
diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs
index f2c656ad43b82..5360dd63afb69 100644
--- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs
+++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs
@@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api;
internal static class UnitTestingSolutionExtensions
{
public static int GetWorkspaceVersion(this Solution solution)
- => solution.WorkspaceVersion;
+ => solution.SolutionStateContentVersion;
public static async Task GetChecksumAsync(this Solution solution, CancellationToken cancellationToken)
=> new UnitTestingChecksumWrapper(await solution.CompilationState.GetChecksumAsync(cancellationToken).ConfigureAwait(false));
diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs
index 15a252b26edbc..e282315732f08 100644
--- a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs
+++ b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs
@@ -96,7 +96,7 @@ static async IAsyncEnumerable SelectManyAsync(IEnumer
{
foreach (var item in source)
{
- await foreach (var result in selector(item))
+ await foreach (var result in selector(item).ConfigureAwait(false))
yield return result;
}
}
diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs
index 44e770fcb56ef..28bdfeecea888 100644
--- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs
+++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs
@@ -167,7 +167,7 @@ await ProducerConsumer.RunAsync(
using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(out var result);
// Transform the individual finder-location objects to "group/symbol/location" tuples.
- await foreach (var location in locations)
+ await foreach (var location in locations.ConfigureAwait(false))
result.Add((group, symbol, location.Location));
return result.ToImmutableAndClear();
diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs
index cfb3f83439906..fcc624121fa30 100644
--- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs
+++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs
@@ -100,7 +100,7 @@ protected static async Task FindDocumentsAsync(
return;
}
- await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken))
+ await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false))
{
if (scope != null && !scope.Contains(document))
continue;
@@ -600,156 +600,8 @@ protected static SymbolUsageInfo GetSymbolUsageInfo(
FindReferencesDocumentState state,
CancellationToken cancellationToken)
{
- var syntaxFacts = state.SyntaxFacts;
- var semanticFacts = state.SemanticFacts;
- var semanticModel = state.SemanticModel;
-
- var topNameNode = node;
- while (syntaxFacts.IsQualifiedName(topNameNode.Parent))
- topNameNode = topNameNode.Parent;
-
- var parent = topNameNode?.Parent;
-
- // typeof/sizeof are a special case where we don't want to return a TypeOrNamespaceUsageInfo, but rather a ValueUsageInfo.Name.
- // This brings it in line with nameof(...), making all those operators appear in a similar fashion.
- if (parent?.RawKind == syntaxFacts.SyntaxKinds.TypeOfExpression ||
- parent?.RawKind == syntaxFacts.SyntaxKinds.SizeOfExpression)
- {
- return new(ValueUsageInfo.Name, typeOrNamespaceUsageInfoOpt: null);
- }
-
- var isInNamespaceNameContext = syntaxFacts.IsBaseNamespaceDeclaration(parent);
- return syntaxFacts.IsInNamespaceOrTypeContext(topNameNode)
- ? SymbolUsageInfo.Create(GetTypeOrNamespaceUsageInfo())
- : GetSymbolUsageInfoCommon();
-
- // Local functions.
- TypeOrNamespaceUsageInfo GetTypeOrNamespaceUsageInfo()
- {
- var usageInfo = IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts) || syntaxFacts.IsLeftSideOfExplicitInterfaceSpecifier(node)
- ? TypeOrNamespaceUsageInfo.Qualified
- : TypeOrNamespaceUsageInfo.None;
-
- if (isInNamespaceNameContext)
- {
- usageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
- }
- else if (node.FirstAncestorOrSelf((node, syntaxFacts) => syntaxFacts.IsUsingOrExternOrImport(node), syntaxFacts) != null)
- {
- usageInfo |= TypeOrNamespaceUsageInfo.Import;
- }
-
- while (syntaxFacts.IsQualifiedName(node.Parent))
- node = node.Parent;
-
- if (syntaxFacts.IsTypeArgumentList(node.Parent))
- {
- usageInfo |= TypeOrNamespaceUsageInfo.TypeArgument;
- }
- else if (syntaxFacts.IsTypeConstraint(node.Parent))
- {
- usageInfo |= TypeOrNamespaceUsageInfo.TypeConstraint;
- }
- else if (syntaxFacts.IsBaseTypeList(node.Parent) ||
- syntaxFacts.IsBaseTypeList(node.Parent?.Parent))
- {
- usageInfo |= TypeOrNamespaceUsageInfo.Base;
- }
- else if (syntaxFacts.IsTypeOfObjectCreationExpression(node))
- {
- usageInfo |= TypeOrNamespaceUsageInfo.ObjectCreation;
- }
-
- return usageInfo;
- }
-
- SymbolUsageInfo GetSymbolUsageInfoCommon()
- {
- if (semanticFacts.IsInOutContext(semanticModel, node, cancellationToken))
- {
- return SymbolUsageInfo.Create(ValueUsageInfo.WritableReference);
- }
- else if (semanticFacts.IsInRefContext(semanticModel, node, cancellationToken))
- {
- return SymbolUsageInfo.Create(ValueUsageInfo.ReadableWritableReference);
- }
- else if (semanticFacts.IsInInContext(semanticModel, node, cancellationToken))
- {
- return SymbolUsageInfo.Create(ValueUsageInfo.ReadableReference);
- }
- else if (semanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken))
- {
- return SymbolUsageInfo.Create(ValueUsageInfo.Write);
- }
- else
- {
- var operation = semanticModel.GetOperation(node, cancellationToken);
- if (operation is IObjectCreationOperation)
- return SymbolUsageInfo.Create(TypeOrNamespaceUsageInfo.ObjectCreation);
-
- // Note: sizeof/typeof also return 'name', but are handled above in GetSymbolUsageInfo.
- if (operation?.Parent is INameOfOperation)
- return SymbolUsageInfo.Create(ValueUsageInfo.Name);
-
- if (node.IsPartOfStructuredTrivia())
- return SymbolUsageInfo.Create(ValueUsageInfo.Name);
-
- var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken);
- if (symbolInfo.Symbol != null)
- {
- switch (symbolInfo.Symbol.Kind)
- {
- case SymbolKind.Namespace:
- var namespaceUsageInfo = TypeOrNamespaceUsageInfo.None;
- if (isInNamespaceNameContext)
- namespaceUsageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
-
- if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
- namespaceUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
-
- return SymbolUsageInfo.Create(namespaceUsageInfo);
-
- case SymbolKind.NamedType:
- var typeUsageInfo = TypeOrNamespaceUsageInfo.None;
- if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
- typeUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
-
- return SymbolUsageInfo.Create(typeUsageInfo);
-
- case SymbolKind.Method:
- case SymbolKind.Property:
- case SymbolKind.Field:
- case SymbolKind.Event:
- case SymbolKind.Parameter:
- case SymbolKind.Local:
- var valueUsageInfo = ValueUsageInfo.Read;
- if (semanticFacts.IsWrittenTo(semanticModel, node, cancellationToken))
- valueUsageInfo |= ValueUsageInfo.Write;
-
- return SymbolUsageInfo.Create(valueUsageInfo);
- }
- }
-
- return SymbolUsageInfo.None;
- }
- }
- }
-
- private static bool IsNodeOrAnyAncestorLeftSideOfDot(SyntaxNode node, ISyntaxFactsService syntaxFacts)
- {
- if (syntaxFacts.IsLeftSideOfDot(node))
- {
- return true;
- }
-
- if (syntaxFacts.IsRightOfQualifiedName(node) ||
- syntaxFacts.IsNameOfSimpleMemberAccessExpression(node) ||
- syntaxFacts.IsNameOfMemberBindingExpression(node))
- {
- return syntaxFacts.IsLeftSideOfDot(node.Parent);
- }
-
- return false;
+ return SymbolUsageInfo.GetSymbolUsageInfo(
+ state.SemanticFacts, state.SemanticModel, node, cancellationToken);
}
internal static ImmutableArray<(string key, string value)> GetAdditionalFindUsagesProperties(
@@ -877,7 +729,7 @@ protected static async Task> GetAllMatchingGlobalAliasNam
{
using var result = TemporaryArray.Empty;
- await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken))
+ await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false))
{
var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false);
foreach (var alias in index.GetGlobalAliases(name, arity))
diff --git a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs
index 0d77405921e13..e705af0844e38 100644
--- a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs
+++ b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs
@@ -52,6 +52,11 @@ private static void AddSymbols(
{
foreach (var reference in references)
{
+ // Filter out name-only references (e.g. nameof expressions, typeof expressions)
+ // This fixes the most common Call Hierarchy false positives
+ if (reference.SymbolUsageInfo.ValueUsageInfoOpt?.IsNameOnly() == true)
+ continue;
+
var containingSymbol = GetEnclosingMethodOrPropertyOrField(semanticModel, reference);
if (containingSymbol != null)
{
diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs
index a53c694513162..9d3bd2aed15fa 100644
--- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs
+++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs
@@ -107,15 +107,17 @@ internal static async Task FormatAsync(Document document, IEnumerable<
public static Task FormatAsync(Document document, SyntaxAnnotation annotation, OptionSet? options = null, CancellationToken cancellationToken = default)
=> FormatAsync(document, annotation, options, rules: default, cancellationToken: cancellationToken);
+ private static ISyntaxFormatting GetSyntaxFormatting(Document document)
+ => document.GetRequiredLanguageService();
+
+ private static ISyntaxFormatting GetSyntaxFormatting(SolutionServices services, string language)
+ => services.GetExtendedLanguageServices(language).GetRequiredService();
+
internal static Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, CancellationToken cancellationToken)
- => FormatAsync(document, annotation, options, rules: default, cancellationToken);
+ => GetSyntaxFormatting(document).FormatAsync(document, annotation, options, cancellationToken);
- internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
- {
- var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var services = document.Project.Solution.Services;
- return document.WithSyntaxRoot(Format(root, annotation, services, options, rules, cancellationToken));
- }
+ internal static Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
+ => GetSyntaxFormatting(document).FormatAsync(document, annotation, options, rules, cancellationToken);
internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, OptionSet? optionSet, ImmutableArray rules, CancellationToken cancellationToken)
{
@@ -175,7 +177,7 @@ private static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, W
}
internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
- => Format(node, GetAnnotatedSpans(node, annotation), services, options, rules, cancellationToken);
+ => GetSyntaxFormatting(services, node.Language).Format(node, annotation, options, rules, cancellationToken);
///
/// Formats the whitespace of a syntax tree.
@@ -225,7 +227,7 @@ private static SyntaxNode Format(SyntaxNode node, IEnumerable? spans,
}
internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
- => GetFormattingResult(node, spans, services, options, rules, cancellationToken).GetFormattedRoot(cancellationToken);
+ => GetSyntaxFormatting(services, node.Language).Format(node, spans, options, rules, cancellationToken);
private static IFormattingResult? GetFormattingResult(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, ImmutableArray rules, CancellationToken cancellationToken)
{
@@ -252,10 +254,7 @@ internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans,
}
internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
- {
- var formatter = services.GetRequiredLanguageService(node.Language);
- return formatter.GetFormattingResult(node, spans, options, rules, cancellationToken);
- }
+ => GetSyntaxFormatting(services, node.Language).GetFormattingResult(node, spans, options, rules, cancellationToken);
///
/// Determines the changes necessary to format the whitespace of a syntax tree.
diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj
index 086d2cf25dbdd..6ef5edb3aa3de 100644
--- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj
+++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj
@@ -48,6 +48,7 @@
+
diff --git a/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs
index 4401c0f3efdb9..cac4e2af02cc1 100644
--- a/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs
+++ b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs
@@ -78,8 +78,12 @@ internal override OptionSet WithChangedOptionInternal(OptionKey optionKey, objec
internal (ImmutableArray> internallyDefined, ImmutableArray> externallyDefined) GetChangedOptions()
{
- var internallyDefined = _changedOptionKeys.Where(key => key.Option is IOption2).SelectAsArray(key => KeyValuePair.Create(new OptionKey2((IOption2)key.Option, key.Language), _values[key]));
- var externallyDefined = _changedOptionKeys.Where(key => key.Option is not IOption2).SelectAsArray(key => KeyValuePair.Create(key, _values[key]));
+ var internallyDefined = _changedOptionKeys.SelectAsArray(
+ predicate: key => key.Option is IOption2,
+ selector: key => KeyValuePair.Create(new OptionKey2((IOption2)key.Option, key.Language), _values[key]));
+ var externallyDefined = _changedOptionKeys.SelectAsArray(
+ predicate: key => key.Option is not IOption2,
+ selector: key => KeyValuePair.Create(key, _values[key]));
return (internallyDefined, externallyDefined);
}
}
diff --git a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs
index b13513e6023f9..be820f0303624 100644
--- a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs
+++ b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs
@@ -7,7 +7,9 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Remote;
@@ -38,6 +40,14 @@ internal static class RemoteUtilities
}
}
+ foreach (var docId in solutionChanges.GetExplicitlyChangedSourceGeneratedDocuments())
+ {
+ var oldDoc = oldSolution.GetRequiredSourceGeneratedDocumentForAlreadyGeneratedId(docId);
+ var newDoc = newSolution.GetRequiredSourceGeneratedDocumentForAlreadyGeneratedId(docId);
+ var textChanges = await newDoc.GetTextChangesAsync(oldDoc, cancellationToken).ConfigureAwait(false);
+ builder.Add((docId, textChanges.ToImmutableArray()));
+ }
+
return builder.ToImmutableAndClear();
}
@@ -55,7 +65,10 @@ public static async Task UpdateSolutionAsync(
var documentIdsAndTexts = await documentTextChanges
.SelectAsArrayAsync(async (tuple, cancellationToken) =>
{
- var oldText = await oldSolution.GetDocument(tuple.documentId).GetValueTextAsync(cancellationToken).ConfigureAwait(false);
+ var document = await oldSolution.GetDocumentAsync(tuple.documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
+ Contract.ThrowIfTrue(tuple.documentId.IsSourceGenerated && !document.IsRazorSourceGeneratedDocument());
+
+ var oldText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var newText = oldText.WithChanges(tuple.textChanges);
return (tuple.documentId, newText);
}, cancellationToken)
diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs
index f294e1215bf39..1043b1ae944d8 100644
--- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs
+++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs
@@ -104,7 +104,7 @@ public async Task ResolveConflictsAsync()
_replacementTextValid = IsIdentifierValid_Worker(baseSolution, _replacementText, documentsGroupedByTopologicallySortedProjectId.Select(g => g.Key));
var renamedSpansTracker = new RenamedSpansTracker();
var conflictResolution = new MutableConflictResolution(
- baseSolution, renamedSpansTracker, _replacementText, _replacementTextValid, this.RenameOptions);
+ baseSolution, renamedSpansTracker, _replacementText, _replacementTextValid);
var intermediateSolution = conflictResolution.OldSolution;
foreach (var documentsByProject in documentsGroupedByTopologicallySortedProjectId)
@@ -198,7 +198,7 @@ .. conflictResolution.RelatedLocations
// Step 3: Simplify the project
conflictResolution.UpdateCurrentSolution(await renamedSpansTracker.SimplifyAsync(
- conflictResolution.CurrentSolution, documentsByProject, _replacementTextValid, _renameAnnotations, this.RenameOptions, _cancellationToken).ConfigureAwait(false));
+ conflictResolution.CurrentSolution, documentsByProject, _replacementTextValid, _renameAnnotations, _cancellationToken).ConfigureAwait(false));
intermediateSolution = await conflictResolution.RemoveAllRenameAnnotationsAsync(
intermediateSolution, documentsByProject, _renameAnnotations, _cancellationToken).ConfigureAwait(false);
conflictResolution.UpdateCurrentSolution(intermediateSolution);
@@ -211,7 +211,7 @@ .. conflictResolution.RelatedLocations
if (IsRenameValid(conflictResolution, renamedSymbolInNewSolution))
{
var declarationDocument = await conflictResolution.CurrentSolution.GetRequiredDocumentAsync(
- _documentIdOfRenameSymbolDeclaration, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ _documentIdOfRenameSymbolDeclaration, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
var semanticModel = await declarationDocument.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
await AddImplicitConflictsAsync(
renamedSymbolInNewSolution,
@@ -269,7 +269,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
{
// remember if there were issues in the document prior to renaming it.
var originalDoc = await conflictResolution.OldSolution.GetRequiredDocumentAsync(
- documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
documentIdErrorStateLookup.Add(documentId, await originalDoc.HasAnyErrorsAsync(_cancellationToken).ConfigureAwait(false));
}
@@ -292,7 +292,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
if (!documentIdErrorStateLookup[documentId])
{
var changeDoc = await conflictResolution.CurrentSolution.GetRequiredDocumentAsync(
- documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
await changeDoc.VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).ConfigureAwait(false);
}
}
@@ -350,10 +350,10 @@ private async Task IdentifyConflictsAsync(
foreach (var documentId in documentIdsForConflictResolution)
{
var newDocument = await conflictResolution.CurrentSolution.GetRequiredDocumentAsync(
- documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var baseDocument = await conflictResolution.OldSolution.GetRequiredDocumentAsync(
- documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
var baseSyntaxTree = await baseDocument.GetRequiredSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
var baseRoot = await baseDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
SemanticModel? newDocumentSemanticModel = null;
@@ -661,7 +661,7 @@ private async Task GetRenamedSymbolInCurrentSolutionAsync(MutableConfli
: _renameSymbolDeclarationLocation.SourceSpan.Start;
var document = await conflictResolution.CurrentSolution.GetRequiredDocumentAsync(
- _documentIdOfRenameSymbolDeclaration, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ _documentIdOfRenameSymbolDeclaration, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
var newSymbol = await SymbolFinder.FindSymbolAtPositionAsync(document, start, cancellationToken: _cancellationToken).ConfigureAwait(false);
return newSymbol;
}
@@ -770,7 +770,7 @@ private async Task AnnotateAndRename_WorkerAsync(
_cancellationToken.ThrowIfCancellationRequested();
var document = await originalSolution.GetRequiredDocumentAsync(
- documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, _cancellationToken).ConfigureAwait(false);
var semanticModel = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
var originalSyntaxRoot = await semanticModel.SyntaxTree.GetRootAsync(_cancellationToken).ConfigureAwait(false);
@@ -825,7 +825,7 @@ private async Task AnnotateAndRename_WorkerAsync(
}
else
{
- partiallyRenamedSolution = await conflictResolution.WithDocumentSyntaxRootAsync(partiallyRenamedSolution, documentId, newRoot, _cancellationToken).ConfigureAwait(false);
+ partiallyRenamedSolution = await MutableConflictResolution.WithDocumentSyntaxRootAsync(partiallyRenamedSolution, documentId, newRoot, _cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs
index f2abbbc1e9613..c609aa832ce34 100644
--- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs
+++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs
@@ -22,8 +22,7 @@ internal sealed class MutableConflictResolution(
Solution oldSolution,
RenamedSpansTracker renamedSpansTracker,
string replacementText,
- bool replacementTextValid,
- SymbolRenameOptions options)
+ bool replacementTextValid)
{
// List of All the Locations that were renamed and conflict-complexified
public readonly List RelatedLocations = [];
@@ -49,8 +48,6 @@ internal sealed class MutableConflictResolution(
///
public Solution CurrentSolution { get; private set; } = oldSolution;
- private readonly SymbolRenameOptions _options = options;
-
private (DocumentId documentId, string newName) _renamedDocument;
internal void ClearDocuments(IEnumerable conflictLocationDocumentIds)
@@ -73,7 +70,7 @@ internal async Task RemoveAllRenameAnnotationsAsync(
if (renamedSpansTracker.IsDocumentChanged(documentId))
{
var document = await CurrentSolution.GetRequiredDocumentAsync(
- documentId, _options.RenameInSourceGeneratedDocuments, cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
// For the computeReplacementToken and computeReplacementNode functions, use
@@ -176,16 +173,14 @@ public ConflictResolution ToConflictResolution()
/// work before calling the normal WithDocumentSyntaxRoot method. If the option is not set, there should be no source generated
/// documents, and the method will complete synchronously.
///
- internal async ValueTask WithDocumentSyntaxRootAsync(Solution solution, DocumentId documentId, SyntaxNode newRoot, CancellationToken cancellationToken)
+ internal static async ValueTask WithDocumentSyntaxRootAsync(Solution solution, DocumentId documentId, SyntaxNode newRoot, CancellationToken cancellationToken)
{
// In a source generated document, we have to ensure we've realized the "old" tree in the modified solution or WithDocumentSyntaxRoot
// won't work. Performing a rename in a source generated document is opt-in, so we can assume that we only hit this condition in
// scenarios that wanted it.
if (documentId.IsSourceGenerated)
{
- Contract.ThrowIfFalse(_options.RenameInSourceGeneratedDocuments);
-
- _ = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: _options.RenameInSourceGeneratedDocuments, cancellationToken).ConfigureAwait(false);
+ _ = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
}
return solution.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveIdentity);
diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs
index 93df9e4b58822..09b02800aa066 100644
--- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs
+++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs
@@ -148,7 +148,6 @@ internal async Task SimplifyAsync(
IEnumerable documentIds,
bool replacementTextValid,
AnnotationTable renameAnnotations,
- SymbolRenameOptions options,
CancellationToken cancellationToken)
{
foreach (var documentId in documentIds)
@@ -158,7 +157,7 @@ internal async Task SimplifyAsync(
// It's possible that rename will have found locations in generated documents, so we have to make sure to allow that. We assume that the
// entry point to rename will only process source generated documents if it needed to.
var document = await solution.GetRequiredDocumentAsync(
- documentId, options.RenameInSourceGeneratedDocuments, cancellationToken).ConfigureAwait(false);
+ documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
if (replacementTextValid)
{
diff --git a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs
index c07b54c6bc07c..1b4348b70c324 100644
--- a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs
+++ b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs
@@ -7,6 +7,7 @@
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Shared.Extensions;
@@ -85,7 +86,9 @@ public static SerializableRenameLocation Dehydrate(RenameLocation location)
public async ValueTask RehydrateAsync(Solution solution, CancellationToken cancellation)
{
- var document = solution.GetRequiredDocument(DocumentId);
+ var document = await solution.GetRequiredDocumentAsync(DocumentId, includeSourceGenerated: true, cancellation).ConfigureAwait(false);
+ Contract.ThrowIfTrue(DocumentId.IsSourceGenerated && !document.IsRazorSourceGeneratedDocument());
+
var tree = await document.GetRequiredSyntaxTreeAsync(cancellation).ConfigureAwait(false);
return new RenameLocation(
diff --git a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs
index 5e4c859135b23..8ed3222cdbfa2 100644
--- a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs
+++ b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs
@@ -34,22 +34,7 @@ public readonly record struct SymbolRenameOptions(
[property: DataMember(Order = 0)] bool RenameOverloads = false,
[property: DataMember(Order = 1)] bool RenameInStrings = false,
[property: DataMember(Order = 2)] bool RenameInComments = false,
- [property: DataMember(Order = 3)] bool RenameFile = false)
-{
- internal SymbolRenameOptions(
- bool renameOverloads,
- bool renameInStrings,
- bool renameInComments,
- bool renameFile,
- bool renameInSourceGeneratedDocuments)
- : this(renameOverloads, renameInStrings, renameInComments, renameFile)
- {
- RenameInSourceGeneratedDocuments = renameInSourceGeneratedDocuments;
- }
-
- [DataMember(Order = 4)]
- internal bool RenameInSourceGeneratedDocuments { get; init; } = false;
-}
+ [property: DataMember(Order = 3)] bool RenameFile = false);
///
/// Options for renaming a document.
diff --git a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs
index e7a6295685706..0b3bba6d026d1 100644
--- a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs
+++ b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs
@@ -11,6 +11,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
+using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
@@ -158,7 +159,7 @@ private static string TrimNameToAfterLastDot(string name)
/// Given a ISymbol, returns the renameable locations for a given symbol.
///
public static async Task> GetRenamableDefinitionLocationsAsync(
- ISymbol referencedSymbol, ISymbol originalSymbol, Solution solution, SymbolRenameOptions options, CancellationToken cancellationToken)
+ ISymbol referencedSymbol, ISymbol originalSymbol, Solution solution, CancellationToken cancellationToken)
{
var shouldIncludeSymbol = await ShouldIncludeSymbolAsync(referencedSymbol, originalSymbol, solution, false, cancellationToken).ConfigureAwait(false);
if (!shouldIncludeSymbol)
@@ -236,17 +237,17 @@ void AddRenameLocationIfNotGenerated(Location location, bool isRenamableAccessor
// If the location is in a source generated file, we won't rename it. Our assumption in this case is we
// have cascaded to this symbol from our original source symbol, and the generator will update this file
// based on the renamed symbol.
- if (options.RenameInSourceGeneratedDocuments || document is not SourceGeneratedDocument)
+ if (document is not SourceGeneratedDocument || document.IsRazorSourceGeneratedDocument())
results.Add(new RenameLocation(location, document.Id, isRenamableAccessor: isRenamableAccessor));
}
}
internal static async Task> GetRenamableReferenceLocationsAsync(
- ISymbol referencedSymbol, ISymbol originalSymbol, ReferenceLocation location, Solution solution, SymbolRenameOptions options, CancellationToken cancellationToken)
+ ISymbol referencedSymbol, ISymbol originalSymbol, ReferenceLocation location, Solution solution, CancellationToken cancellationToken)
{
// We won't try to update references in source generated files; we'll assume the generator will rerun
// and produce an updated document with the new name.
- if (!options.RenameInSourceGeneratedDocuments && location.Document is SourceGeneratedDocument)
+ if (location.Document is SourceGeneratedDocument && !location.Document.IsRazorSourceGeneratedDocument())
return [];
var shouldIncludeSymbol = await ShouldIncludeSymbolAsync(referencedSymbol, originalSymbol, solution, true, cancellationToken).ConfigureAwait(false);
diff --git a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs
index 44feed9b4d376..42d58a4cabae1 100644
--- a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs
+++ b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs
@@ -63,10 +63,10 @@ public static async Task FindLocationsInCurrentProcessA
symbol = await RenameUtilities.FindDefinitionSymbolAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
// First, find the direct references just to the symbol being renamed.
- var originalSymbolResult = await AddLocationsReferenceSymbolsAsync(symbol, solution, options, cancellationToken).ConfigureAwait(false);
+ var originalSymbolResult = await AddLocationsReferenceSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
// Next, find references to overloads, if the user has asked to rename those as well.
- var overloadsResult = options.RenameOverloads ? await GetOverloadsAsync(symbol, solution, options, cancellationToken).ConfigureAwait(false) :
+ var overloadsResult = options.RenameOverloads ? await GetOverloadsAsync(symbol, solution, cancellationToken).ConfigureAwait(false) :
[];
// Finally, include strings/comments if that's what the user wants.
@@ -111,12 +111,12 @@ public static async Task FindLocationsInCurrentProcessA
}
private static async Task> GetOverloadsAsync(
- ISymbol symbol, Solution solution, SymbolRenameOptions options, CancellationToken cancellationToken)
+ ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder.GetInstance(out var overloadsResult);
foreach (var overloadedSymbol in RenameUtilities.GetOverloadedSymbols(symbol))
- overloadsResult.Add(await AddLocationsReferenceSymbolsAsync(overloadedSymbol, solution, options, cancellationToken).ConfigureAwait(false));
+ overloadsResult.Add(await AddLocationsReferenceSymbolsAsync(overloadedSymbol, solution, cancellationToken).ConfigureAwait(false));
return overloadsResult.ToImmutableAndClear();
}
@@ -124,7 +124,6 @@ private static async Task> GetOverloadsAsync(
private static async Task AddLocationsReferenceSymbolsAsync(
ISymbol symbol,
Solution solution,
- SymbolRenameOptions options,
CancellationToken cancellationToken)
{
var locations = ImmutableHashSet.CreateBuilder();
@@ -134,17 +133,17 @@ private static async Task AddLocationsReferenceSymbolsAsync(
foreach (var referencedSymbol in referenceSymbols)
{
locations.AddAll(
- await ReferenceProcessing.GetRenamableDefinitionLocationsAsync(referencedSymbol.Definition, symbol, solution, options, cancellationToken).ConfigureAwait(false));
+ await ReferenceProcessing.GetRenamableDefinitionLocationsAsync(referencedSymbol.Definition, symbol, solution, cancellationToken).ConfigureAwait(false));
locations.AddAll(
await referencedSymbol.Locations.SelectManyInParallelAsync(
(l, c) => ReferenceProcessing.GetRenamableReferenceLocationsAsync(
- referencedSymbol.Definition, symbol, l, solution, options, c),
+ referencedSymbol.Definition, symbol, l, solution, c),
cancellationToken).ConfigureAwait(false));
}
- var implicitLocations = referenceSymbols.SelectMany(refSym => refSym.Locations).Where(loc => loc.IsImplicit).ToImmutableArray();
- var referencedSymbols = referenceSymbols.Select(r => r.Definition).Where(r => !r.Equals(symbol)).ToImmutableArray();
+ var implicitLocations = referenceSymbols.SelectMany(refSym => refSym.Locations).WhereAsArray(loc => loc.IsImplicit);
+ var referencedSymbols = referenceSymbols.Select(r => r.Definition).WhereAsArray(r => !r.Equals(symbol));
return new SearchResult(locations.ToImmutable(), implicitLocations, referencedSymbols);
}
diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs
index 8dd58484de661..bab850e635e40 100644
--- a/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs
+++ b/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs
@@ -103,7 +103,7 @@ public static ImmutableArray FilterToAliasMatches(
}
var q = from r in result
- let aliasLocations = r.Locations.Where(loc => SymbolEquivalenceComparer.Instance.Equals(loc.Alias, aliasSymbol)).ToImmutableArray()
+ let aliasLocations = r.Locations.WhereAsArray(loc => SymbolEquivalenceComparer.Instance.Equals(loc.Alias, aliasSymbol))
where aliasLocations.Any()
select new ReferencedSymbol(r.Definition, aliasLocations);
diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISolutionExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISolutionExtensions.cs
index 7c3b54acb7c9d..bfa48da6fd274 100644
--- a/src/Workspaces/Core/Portable/Shared/Extensions/ISolutionExtensions.cs
+++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISolutionExtensions.cs
@@ -39,20 +39,6 @@ public static async Task> GetGlobalNamespacesAs
public static TextDocumentKind? GetDocumentKind(this Solution solution, DocumentId documentId)
=> solution.GetTextDocument(documentId)?.Kind;
- internal static TextDocument? GetTextDocumentForLocation(this Solution solution, Location location)
- {
- switch (location.Kind)
- {
- case LocationKind.SourceFile:
- return solution.GetDocument(location.SourceTree);
- case LocationKind.ExternalFile:
- var documentId = solution.GetDocumentIdsWithFilePath(location.GetLineSpan().Path).FirstOrDefault();
- return solution.GetTextDocument(documentId);
- default:
- return null;
- }
- }
-
public static Solution WithTextDocumentText(this Solution solution, DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveIdentity)
{
var documentKind = solution.GetDocumentKind(documentId);
diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ExtensionOrderer.Graph.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ExtensionOrderer.Graph.cs
index f902c29113e50..71cb698f2fbcb 100644
--- a/src/Workspaces/Core/Portable/Shared/Utilities/ExtensionOrderer.Graph.cs
+++ b/src/Workspaces/Core/Portable/Shared/Utilities/ExtensionOrderer.Graph.cs
@@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
+using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.Shared.Utilities;
@@ -24,35 +26,29 @@ public IEnumerable> FindExtensions(string name)
public void CheckForCycles()
{
foreach (var node in this.Nodes.Values)
- {
node.CheckForCycles();
- }
}
- public IList> TopologicalSort()
+ public ImmutableArray> TopologicalSort()
{
- var result = new List>();
+ using var _ = ArrayBuilder>.GetInstance(out var result);
var seenNodes = new HashSet>();
foreach (var node in this.Nodes.Values)
- {
Visit(node, result, seenNodes);
- }
- return result;
+ return result.ToImmutableAndClear();
}
private static void Visit(
Node node,
- List> result,
+ ArrayBuilder> result,
HashSet> seenNodes)
{
if (seenNodes.Add(node))
{
foreach (var before in node.ExtensionsBeforeMeSet)
- {
Visit(before, result, seenNodes);
- }
result.Add(node.Extension);
}
diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs
index 61dade9c396f8..cc0f48a32e732 100644
--- a/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs
+++ b/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs
@@ -21,6 +21,7 @@ private TrivialTemporaryStorageService()
public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken)
{
+ stream.Position = 0;
var newStream = new MemoryStream();
stream.CopyTo(newStream);
return new StreamStorage(newStream);
diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/Extensions.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/Extensions.cs
index 93159ed29e049..465b27ed2568d 100644
--- a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/Extensions.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/Extensions.cs
@@ -27,4 +27,7 @@ public static bool IsRazorDocument(this TextDocument document)
public static bool IsRazorDocument(this TextDocumentState documentState)
=> documentState.DocumentServiceProvider.GetService()?.DiagnosticsLspClientName == RazorCSharpLspClientName;
+
+ public static bool IsRazorSourceGeneratedDocument(this Document document)
+ => document is SourceGeneratedDocument { Identity.Generator.TypeName: "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator" };
}
diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISourceGeneratedDocumentSpanMappingService.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISourceGeneratedDocumentSpanMappingService.cs
new file mode 100644
index 0000000000000..3208c28a69315
--- /dev/null
+++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/ISourceGeneratedDocumentSpanMappingService.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Host;
+
+internal interface ISourceGeneratedDocumentSpanMappingService : IWorkspaceService
+{
+ bool CanMapSpans(SourceGeneratedDocument sourceGeneratedDocument);
+
+ Task> GetMappedTextChangesAsync(SourceGeneratedDocument oldDocument, SourceGeneratedDocument newDocument, CancellationToken cancellationToken);
+
+ Task> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray spans, CancellationToken cancellationToken);
+}
+
+internal record struct MappedTextChange(string MappedFilePath, TextChange TextChange);
diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/SpanMappingHelper.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/SpanMappingHelper.cs
new file mode 100644
index 0000000000000..7be0c0e109113
--- /dev/null
+++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/SpanMappingHelper.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Host;
+
+internal static class SpanMappingHelper
+{
+ public static bool CanMapSpans(Document document)
+ {
+ if (document is SourceGeneratedDocument sourceGeneratedDocument &&
+ document.Project.Solution.Services.GetService() is { } sourceGeneratedSpanMappingService)
+ {
+ return sourceGeneratedSpanMappingService.CanMapSpans(sourceGeneratedDocument);
+ }
+
+ return document.DocumentServiceProvider.GetService() is not null;
+ }
+
+ public static async Task?> TryGetMappedSpanResultAsync(Document document, ImmutableArray textSpans, CancellationToken cancellationToken)
+ {
+ if (document is SourceGeneratedDocument sourceGeneratedDocument &&
+ document.Project.Solution.Services.GetService() is { } sourceGeneratedSpanMappingService)
+ {
+ var result = await sourceGeneratedSpanMappingService.MapSpansAsync(sourceGeneratedDocument, textSpans, cancellationToken).ConfigureAwait(false);
+ if (result.IsDefaultOrEmpty)
+ {
+ return null;
+ }
+
+ Contract.ThrowIfFalse(textSpans.Length == result.Length,
+ $"The number of input spans {textSpans.Length} should match the number of mapped spans returned {result.Length}");
+ return result;
+ }
+
+ var spanMappingService = document.DocumentServiceProvider.GetService();
+ if (spanMappingService == null)
+ {
+ return null;
+ }
+
+ var mappedSpanResult = await spanMappingService.MapSpansAsync(document, textSpans, cancellationToken).ConfigureAwait(false);
+ Contract.ThrowIfFalse(textSpans.Length == mappedSpanResult.Length,
+ $"The number of input spans {textSpans.Length} should match the number of mapped spans returned {mappedSpanResult.Length}");
+ return mappedSpanResult;
+ }
+}
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
index e219806aa4dc4..5bb97fa0325ec 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
@@ -15,7 +15,6 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
-using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
@@ -62,8 +61,8 @@ internal Solution(
IReadOnlyList analyzerReferences,
ImmutableDictionary fallbackAnalyzerOptions)
: this(new SolutionCompilationState(
- new SolutionState(workspace.Kind, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences, fallbackAnalyzerOptions),
- workspace.PartialSemanticsEnabled))
+ new SolutionState(workspace.Kind, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences, fallbackAnalyzerOptions),
+ workspace.PartialSemanticsEnabled))
{
}
@@ -71,7 +70,7 @@ internal Solution(
internal SolutionCompilationState CompilationState { get; }
- internal int WorkspaceVersion => this.SolutionState.WorkspaceVersion;
+ internal int SolutionStateContentVersion => this.SolutionState.ContentVersion;
internal bool PartialSemanticsEnabled => CompilationState.PartialSemanticsEnabled;
@@ -1588,8 +1587,8 @@ internal ImmutableArray GetRelatedDocumentIds(DocumentId documentId,
internal DocumentId? GetFirstRelatedDocumentId(DocumentId documentId, ProjectId? relatedProjectIdHint)
=> this.SolutionState.GetFirstRelatedDocumentId(documentId, relatedProjectIdHint);
- internal Solution WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services)
- => WithCompilationState(CompilationState.WithNewWorkspace(workspaceKind, workspaceVersion, services));
+ internal Solution WithNewWorkspaceFrom(Solution oldSolution)
+ => WithCompilationState(CompilationState.WithNewWorkspaceFrom(oldSolution));
///
/// Formerly, returned a copy of the solution isolated from the original so that they do not share computed state. It now does nothing.
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionChanges.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionChanges.cs
index 575a40f0b106a..a0bf9f3eaafa6 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionChanges.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionChanges.cs
@@ -13,57 +13,57 @@ namespace Microsoft.CodeAnalysis;
public readonly struct SolutionChanges
{
- private readonly Solution _newSolution;
- private readonly Solution _oldSolution;
+ internal Solution OldSolution { get; }
+ internal Solution NewSolution { get; }
internal SolutionChanges(Solution newSolution, Solution oldSolution)
{
- _newSolution = newSolution;
- _oldSolution = oldSolution;
+ NewSolution = newSolution;
+ OldSolution = oldSolution;
}
public IEnumerable GetAddedProjects()
{
- foreach (var id in _newSolution.ProjectIds)
+ foreach (var id in NewSolution.ProjectIds)
{
- if (!_oldSolution.ContainsProject(id))
+ if (!OldSolution.ContainsProject(id))
{
- yield return _newSolution.GetRequiredProject(id);
+ yield return NewSolution.GetRequiredProject(id);
}
}
}
public IEnumerable GetProjectChanges()
{
- var old = _oldSolution;
+ var old = OldSolution;
// if the project states are different then there is a change.
- foreach (var id in _newSolution.ProjectIds)
+ foreach (var id in NewSolution.ProjectIds)
{
- var newState = _newSolution.GetProjectState(id);
+ var newState = NewSolution.GetProjectState(id);
var oldState = old.GetProjectState(id);
if (oldState != null && newState != null && newState != oldState)
{
- yield return _newSolution.GetRequiredProject(id).GetChanges(_oldSolution.GetRequiredProject(id));
+ yield return NewSolution.GetRequiredProject(id).GetChanges(OldSolution.GetRequiredProject(id));
}
}
}
public IEnumerable GetRemovedProjects()
{
- foreach (var id in _oldSolution.ProjectIds)
+ foreach (var id in OldSolution.ProjectIds)
{
- if (!_newSolution.ContainsProject(id))
+ if (!NewSolution.ContainsProject(id))
{
- yield return _oldSolution.GetRequiredProject(id);
+ yield return OldSolution.GetRequiredProject(id);
}
}
}
public IEnumerable GetAddedAnalyzerReferences()
{
- var oldAnalyzerReferences = new HashSet(_oldSolution.AnalyzerReferences);
- foreach (var analyzerReference in _newSolution.AnalyzerReferences)
+ var oldAnalyzerReferences = new HashSet(OldSolution.AnalyzerReferences);
+ foreach (var analyzerReference in NewSolution.AnalyzerReferences)
{
if (!oldAnalyzerReferences.Contains(analyzerReference))
{
@@ -74,8 +74,8 @@ public IEnumerable GetAddedAnalyzerReferences()
public IEnumerable GetRemovedAnalyzerReferences()
{
- var newAnalyzerReferences = new HashSet(_newSolution.AnalyzerReferences);
- foreach (var analyzerReference in _oldSolution.AnalyzerReferences)
+ var newAnalyzerReferences = new HashSet(NewSolution.AnalyzerReferences);
+ foreach (var analyzerReference in OldSolution.AnalyzerReferences)
{
if (!newAnalyzerReferences.Contains(analyzerReference))
{
@@ -94,18 +94,18 @@ public IEnumerable GetRemovedAnalyzerReferences()
///
internal IEnumerable GetExplicitlyChangedSourceGeneratedDocuments()
{
- if (_newSolution.CompilationState.FrozenSourceGeneratedDocumentStates.IsEmpty)
+ if (NewSolution.CompilationState.FrozenSourceGeneratedDocumentStates.IsEmpty)
return [];
using var _ = ArrayBuilder.GetInstance(out var oldStateBuilder);
- foreach (var (id, _) in _newSolution.CompilationState.FrozenSourceGeneratedDocumentStates.States)
+ foreach (var (id, _) in NewSolution.CompilationState.FrozenSourceGeneratedDocumentStates.States)
{
- var oldState = _oldSolution.CompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(id);
+ var oldState = OldSolution.CompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(id);
oldStateBuilder.AddIfNotNull(oldState);
}
var oldStates = new TextDocumentStates(oldStateBuilder);
- return _newSolution.CompilationState.FrozenSourceGeneratedDocumentStates.GetChangedStateIds(
+ return NewSolution.CompilationState.FrozenSourceGeneratedDocumentStates.GetChangedStateIds(
oldStates,
ignoreUnchangedContent: true,
ignoreUnchangeableDocuments: false);
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs
index 49918b82a5f71..b396c1574c22e 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs
@@ -1482,17 +1482,11 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments(
frozenSourceGeneratedDocumentStates: new TextDocumentStates(documentStates.Values));
}
- public SolutionCompilationState WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services)
- {
- return this.Branch(
- this.SolutionState.WithNewWorkspace(workspaceKind, workspaceVersion, services));
- }
+ public SolutionCompilationState WithNewWorkspaceFrom(Solution oldSolution)
+ => this.Branch(this.SolutionState.WithNewWorkspaceFrom(oldSolution));
public SolutionCompilationState WithOptions(SolutionOptionSet options)
- {
- return this.Branch(
- this.SolutionState.WithOptions(options));
- }
+ => this.Branch(this.SolutionState.WithOptions(options));
///
/// Updates entries in our to the corresponding values in the
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
index 60ad44dd0eb0d..1f9ca11562d9d 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
@@ -37,8 +37,15 @@ internal sealed partial class SolutionState
{
public static readonly IEqualityComparer FilePathComparer = CachingFilePathComparer.Instance;
- // the version of the workspace this solution is from
- public int WorkspaceVersion { get; }
+ ///
+ /// The content version of the workspace this solution is from. This monotonically increases in the
+ /// workspace whenever the content of its snapshot changes. Importantly,
+ /// this does not change when the SolutionState stays the same, but the workspace's 's
+ /// changes. That ensures that requests from the host
+ /// to rerun source generators do not block subsequent requests to update the solution's content in
+ /// .
+ ///
+ public int ContentVersion { get; }
public string? WorkspaceKind { get; }
public SolutionServices Services { get; }
public SolutionOptionSet Options { get; }
@@ -65,7 +72,7 @@ internal sealed partial class SolutionState
private SolutionState(
string? workspaceKind,
- int workspaceVersion,
+ int solutionStateContentVersion,
SolutionServices services,
SolutionInfo.SolutionAttributes solutionAttributes,
IReadOnlyList projectIds,
@@ -78,7 +85,7 @@ private SolutionState(
Lazy? lazyAnalyzers)
{
WorkspaceKind = workspaceKind;
- WorkspaceVersion = workspaceVersion;
+ ContentVersion = solutionStateContentVersion;
SolutionAttributes = solutionAttributes;
Services = services;
ProjectIds = projectIds;
@@ -111,7 +118,7 @@ public SolutionState(
ImmutableDictionary fallbackAnalyzerOptions)
: this(
workspaceKind,
- workspaceVersion: 0,
+ solutionStateContentVersion: 0,
services,
solutionAttributes,
projectIds: SpecializedCollections.EmptyBoxedImmutableArray(),
@@ -208,7 +215,9 @@ internal SolutionState Branch(
return new SolutionState(
WorkspaceKind,
- WorkspaceVersion,
+ // Note: we pass along this version for now. The workspace will actually fork us once more
+ // when it determines the content version it is moving to.
+ ContentVersion,
Services,
solutionAttributes,
projectIds,
@@ -226,13 +235,17 @@ internal SolutionState Branch(
/// This implicitly also changes the value of for this solution,
/// since that is extracted from for backwards compatibility.
///
- public SolutionState WithNewWorkspace(
- string? workspaceKind,
- int workspaceVersion,
- SolutionServices services)
+ public SolutionState WithNewWorkspaceFrom(Solution oldSolution)
{
+ var workspaceKind = oldSolution.WorkspaceKind;
+ var services = oldSolution.Services;
+
+ var solutionStateContentVersion = oldSolution.SolutionState == this
+ ? oldSolution.SolutionStateContentVersion // If the solution state is the same, we can keep the same version.
+ : oldSolution.SolutionStateContentVersion + 1; // Otherwise, increment the version.
+
if (workspaceKind == WorkspaceKind &&
- workspaceVersion == WorkspaceVersion &&
+ solutionStateContentVersion == ContentVersion &&
services == Services)
{
return this;
@@ -242,7 +255,7 @@ public SolutionState WithNewWorkspace(
// get locked-in by document states and project states when first constructed.
return new SolutionState(
workspaceKind,
- workspaceVersion,
+ solutionStateContentVersion,
services,
SolutionAttributes,
ProjectIds,
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs
index a8eabc3e626c2..d5e36c996a282 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs
@@ -48,6 +48,13 @@ public override string ToString()
=> $"{MajorVersion}.{MinorVersion}";
public Checksum Checksum => new(MajorVersion, MinorVersion);
+
+ public static bool operator >(SourceGeneratorExecutionVersion left, SourceGeneratorExecutionVersion right)
+ => left.MajorVersion > right.MajorVersion ||
+ (left.MajorVersion == right.MajorVersion && left.MinorVersion > right.MinorVersion);
+
+ public static bool operator <(SourceGeneratorExecutionVersion left, SourceGeneratorExecutionVersion right)
+ => !(left == right || left > right);
}
///
diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs
index 75d7784ecc0c6..77941c15689fd 100644
--- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs
@@ -192,7 +192,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio
return (solution, solution);
}
- _latestSolution = solution.WithNewWorkspace(oldSolution.WorkspaceKind, oldSolution.WorkspaceVersion + 1, oldSolution.Services);
+ _latestSolution = solution.WithNewWorkspaceFrom(oldSolution);
return (oldSolution, _latestSolution);
}
}
@@ -466,13 +466,13 @@ private Solution InitializeAnalyzerFallbackOptions(Solution oldSolution, Solutio
/// Action to perform immediately prior to updating .
/// The action will be passed the old that will be replaced and the exact solution
/// it will be replaced with. The latter may be different than the solution returned by as it will have its updated
- /// accordingly. This will only be run once.
+ /// name="transformation"/> as it may its updated
+ /// (if it's actually changed). This will only be run once.
/// Action to perform once has been updated. The
/// action will be passed the old that was just replaced and the exact solution it
/// was replaced with. The latter may be different than the solution returned by as it will have its updated
- /// accordingly. This will only be run once.
+ /// name="transformation"/> as it may have its updated
+ /// (if it's actually changed). This will only be run once.
private protected (Solution oldSolution, Solution newSolution) SetCurrentSolution(
TData data,
Func transformation,
@@ -536,7 +536,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio
continue;
}
- newSolution = newSolution.WithNewWorkspace(oldSolution.WorkspaceKind, oldSolution.WorkspaceVersion + 1, oldSolution.Services);
+ newSolution = newSolution.WithNewWorkspaceFrom(oldSolution);
// Prior to updating the latest solution, let the caller do any other state updates they want.
onBeforeUpdate?.Invoke(oldSolution, newSolution, data);
@@ -1567,7 +1567,7 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgress loc.Alias != null) == 1);
}
+ [Fact]
+ public async Task MemberAccessReadWriteSemantics_NotAffectedByWalkUp()
+ {
+ var text = """
+
+ class C
+ {
+ public C InnerC;
+ public int Field;
+
+ void M()
+ {
+ // Test the critical case: a.b.c = 5
+ // InnerC (b) should be READ, Field (c) should be WRITTEN
+ InnerC.Field = 5;
+ }
+ }
+
+ """;
+ using var workspace = CreateWorkspace();
+ var solution = GetSingleDocumentSolution(workspace, text);
+ var project = solution.Projects.First();
+ var compilation = await project.GetCompilationAsync();
+
+ var typeC = compilation.GetTypeByMetadataName("C");
+ var innerCSymbol = typeC.GetMembers("InnerC").OfType().First();
+ var fieldSymbol = typeC.GetMembers("Field").OfType().First();
+
+ // Find references to InnerC
+ var innerCReferences = (await SymbolFinder.FindReferencesAsync(innerCSymbol, solution)).ToList();
+ Assert.Equal(1, innerCReferences.Count);
+
+ var innerCLocations = innerCReferences[0].Locations.Where(loc => !loc.IsImplicit).ToList();
+ Assert.Equal(1, innerCLocations.Count); // Should only have the assignment usage, not declaration
+
+ var innerCUsage = innerCLocations.Single();
+
+ // CRITICAL TEST: In "InnerC.Field = 5", InnerC should be READ (not written)
+ // This is the key assertion - in a.b.c = 5, 'b' (InnerC) is read to access 'c' (Field)
+ Assert.True(innerCUsage.SymbolUsageInfo.IsReadFrom(),
+ "InnerC should be READ in 'InnerC.Field = 5'. " +
+ $"The walk-up logic should not affect this. UsageInfo: {innerCUsage.SymbolUsageInfo}");
+
+ Assert.False(innerCUsage.SymbolUsageInfo.IsWrittenTo(),
+ "InnerC should NOT be WRITTEN in 'InnerC.Field = 5'. " +
+ $"Only Field should be written. UsageInfo: {innerCUsage.SymbolUsageInfo}");
+
+ // Verify Field is written as expected
+ var fieldReferences = (await SymbolFinder.FindReferencesAsync(fieldSymbol, solution)).ToList();
+ Assert.Equal(1, fieldReferences.Count);
+
+ var fieldLocations = fieldReferences[0].Locations.Where(loc => !loc.IsImplicit).ToList();
+ Assert.Equal(1, fieldLocations.Count); // Should only have the assignment usage, not declaration
+
+ var fieldUsage = fieldLocations.Single();
+
+ Assert.True(fieldUsage.SymbolUsageInfo.IsWrittenTo(),
+ "Field should be WRITTEN in 'InnerC.Field = 5'. " +
+ $"UsageInfo: {fieldUsage.SymbolUsageInfo}");
+ }
+
private static void Verify(ReferencedSymbol reference, HashSet expectedMatchedLines)
{
void verifier(Location location)
diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
index 04e6245305c75..2a8c29df22b0d 100644
--- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
+++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs
@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Collections.Immutable;
-using System.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -24,7 +22,6 @@
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
-using Roslyn.Utilities;
using Xunit;
using static Microsoft.CodeAnalysis.UnitTests.SolutionTestHelpers;
using static Microsoft.CodeAnalysis.UnitTests.SolutionUtilities;
@@ -1249,7 +1246,7 @@ public async Task MultipleWithTextUnfreezesFully(TestHost testHost)
}
[Theory, CombinatorialData]
- public async Task WithTextWorksOnUnrealisedGeneratedDocument(TestHost testHost)
+ public async Task WithTextWorksOnUnrealizedGeneratedDocument(TestHost testHost)
{
using var workspace = CreateWorkspace(testHost: testHost);
@@ -1284,7 +1281,7 @@ static async Task FreezeAndGetDocument(Project project,
}
[Theory, CombinatorialData]
- public async Task WithSyntaxRootWorksOnUnrealisedGeneratedDocument(TestHost testHost)
+ public async Task WithSyntaxRootWorksOnUnrealizedGeneratedDocument(TestHost testHost)
{
using var workspace = CreateWorkspace(testHost: testHost);
@@ -1400,20 +1397,11 @@ public Assembly LoadFromPath(string fullPath)
=> throw new InvalidOperationException("These tests should not be loading analyzer assemblies in those host workspace, only in the remote one.");
}
- [PartNotDiscoverable]
- [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test), Shared]
- [method: ImportingConstructor]
- [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
- private sealed class TestWorkspaceConfigurationService() : IWorkspaceConfigurationService
- {
- public WorkspaceConfigurationOptions Options { get; set; } = WorkspaceConfigurationOptions.Default;
- }
-
[Theory, CombinatorialData]
internal async Task UpdatingAnalyzerReferenceReloadsGenerators(
SourceGeneratorExecutionPreference executionPreference)
{
- using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)], TestHost.OutOfProcess);
+ using var workspace = CreateWorkspace([], TestHost.OutOfProcess);
var mefServices = (VisualStudioMefHostServices)workspace.Services.HostServices;
// Ensure the local and remote sides agree on how we're executing source generators.
@@ -1494,5 +1482,83 @@ internal async Task UpdatingAnalyzerReferenceReloadsGenerators(
}
}
+ [Theory, CombinatorialData]
+ [WorkItem("https://github.com/dotnet/roslyn/issues/79587")]
+ internal async Task TestChangeToExecutionVersionBeforeTryApplyChanges(
+ SourceGeneratorExecutionPreference executionPreference,
+ bool majorVersionUpdate)
+ {
+ using var workspace = TestWorkspace.CreateCSharp(
+ "// First file",
+ composition: FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess));
+
+ var configService = (TestWorkspaceConfigurationService)workspace.Services.GetRequiredService();
+ configService.Options = configService.Options with { SourceGeneratorExecution = executionPreference };
+
+ // want to access the true workspace solution (which will be a fork of the solution we're producing here).
+ var initialSolution = workspace.CurrentSolution;
+ var initialExecutionMap = initialSolution.CompilationState.SourceGeneratorExecutionVersionMap.Map;
+
+ var projectId1 = initialSolution.Projects.Single().Id;
+ Assert.True(initialExecutionMap.ContainsKey(projectId1));
+
+ // Simulate the host making a change to the sg execution version, to force generators to rerun.
+ var forceRegeneration = majorVersionUpdate;
+ workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration);
+ await WaitForSourceGeneratorsAsync(workspace);
+
+ var solutionWithChangedExecutionVersion = workspace.CurrentSolution;
+
+ // Now, fork the *original* solution and try to apply it back. This should succeed
+ // as the change to execution version should not impact the solution content version.
+ var solutionWithDocumentAdded = initialSolution.AddDocument(
+ DocumentId.CreateNewId(projectId1), "Y.cs", "// Contents");
+
+ var expectVersionChange = executionPreference is SourceGeneratorExecutionPreference.Balanced || forceRegeneration;
+
+ // The content forked solution should have an SG execution version *less than* the one we just changed.
+ // Note: this will be patched up once we call TryApplyChanges.
+ if (expectVersionChange)
+ {
+ Assert.True(
+ solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]
+ > solutionWithDocumentAdded.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+ else
+ {
+ Assert.Equal(
+ solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1],
+ solutionWithDocumentAdded.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
+ Assert.True(workspace.TryApplyChanges(solutionWithDocumentAdded));
+
+ var finalSolution = workspace.CurrentSolution;
+ Assert.Equal(2, finalSolution.Projects.Single().Documents.Count());
+
+ if (expectVersionChange)
+ {
+ // In balanced (or if we forced regen) mode, the execution version should have been updated to the new value.
+ Assert.NotEqual(initialExecutionMap[projectId1], solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ Assert.NotEqual(initialExecutionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+ else
+ {
+ // In automatic mode, nothing should change wrt to execution versions (unless we specified force-regenerate).
+ Assert.Equal(initialExecutionMap[projectId1], solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ Assert.Equal(initialExecutionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
+ // The final execution version for the project should match the changed execution version, no matter what.
+ // Proving that the content change happened, but didn't drop the execution version change.
+ Assert.Equal(solutionWithChangedExecutionVersion.CompilationState.SourceGeneratorExecutionVersionMap[projectId1], finalSolution.CompilationState.SourceGeneratorExecutionVersionMap[projectId1]);
+ }
+
+ private static async Task WaitForSourceGeneratorsAsync(TestWorkspace workspace)
+ {
+ var operations = workspace.ExportProvider.GetExportedValue();
+ await operations.WaitAllAsync(workspace, [FeatureAttribute.Workspace, FeatureAttribute.SourceGenerators]);
+ }
+
#endif
}
diff --git a/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs b/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs
index d6a71b46fb465..f712bd9d74564 100644
--- a/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs
+++ b/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs
@@ -592,4 +592,18 @@ public void TestNotGCRootedOnConstruction()
// rooted within the export provider instance.
GC.KeepAlive(exportProvider);
}
+
+ [Fact]
+ public async Task VbReferencingCSharp()
+ {
+ var workspace = new AdhocWorkspace();
+ var csProj = workspace.AddProject("CsProj", LanguageNames.CSharp);
+ csProj = csProj.WithCompilationOptions(csProj.CompilationOptions.WithOutputKind(OutputKind.DynamicallyLinkedLibrary));
+ var vbProj = csProj.Solution.AddProject("VbProj", "VbProj", LanguageNames.VisualBasic)
+ .AddProjectReference(new ProjectReference(csProj.Id));
+ vbProj = vbProj.WithCompilationOptions(vbProj.CompilationOptions.WithOutputKind(OutputKind.DynamicallyLinkedLibrary));
+ var comp = (await vbProj.GetCompilationAsync())!;
+ var diagnostics = comp.GetDiagnostics();
+ Assert.Empty(diagnostics);
+ }
}
diff --git a/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs b/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs
index 681622f567569..e16cf3cc1f3a3 100644
--- a/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs
+++ b/src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs
@@ -10,12 +10,20 @@ namespace Microsoft.CodeAnalysis.UnitTests;
public sealed class TestOutputLoggerProvider(ITestOutputHelper testOutputHelper) : ILoggerProvider
{
+ ///
+ /// The given to us from xUnit. This is nulled out once we dispose this logger provider;
+ /// xUnit will abort a test run if something writes to this when a test isn't running; that's helpful for debugging,
+ /// but if a test fails we might still have asynchronous work logging in the background that didn't cleanly shut down. We don't want
+ /// an entire test run failing for that.
+ ///
+ private ITestOutputHelper? _testOutputHelper = testOutputHelper;
+
public ILogger CreateLogger(string categoryName)
{
- return new TestOutputLogger(testOutputHelper, categoryName);
+ return new TestOutputLogger(this, categoryName);
}
- private sealed class TestOutputLogger(ITestOutputHelper testOutputHelper, string categoryName) : ILogger
+ private sealed class TestOutputLogger(TestOutputLoggerProvider loggerProvider, string categoryName) : ILogger
{
public IDisposable BeginScope(TState state) where TState : notnull
{
@@ -29,10 +37,10 @@ public bool IsEnabled(LogLevel logLevel)
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)}");
+ loggerProvider._testOutputHelper?.WriteLine($"[{DateTime.UtcNow:hh:mm:ss.fff}] [{logLevel}] [{categoryName}] {formatter(state, exception)}");
if (exception is not null)
- testOutputHelper.WriteLine(exception.ToString());
+ loggerProvider._testOutputHelper?.WriteLine(exception.ToString());
}
private sealed class NoOpDisposable : IDisposable
@@ -45,5 +53,6 @@ public void Dispose()
public void Dispose()
{
+ _testOutputHelper = null;
}
}
diff --git a/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs b/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs
index a929d0b77a49a..96699be6380a8 100644
--- a/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs
+++ b/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs
@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.Testing;
using Microsoft.CodeAnalysis.UnitTests.Remote;
+using Roslyn.Test.Utilities;
namespace Microsoft.CodeAnalysis.Test.Utilities;
@@ -14,6 +15,7 @@ public static class FeaturesTestCompositions
public static readonly TestComposition Features = TestComposition.Empty
.AddAssemblies(MefHostServices.DefaultAssemblies)
.AddParts(
+ typeof(TestWorkspaceConfigurationService), // allows TestWorkspaces to specify custom workspace configuration
typeof(TestSerializerService.Factory),
typeof(MockWorkspaceEventListenerProvider), // by default, avoid running Solution Crawler and other services that start in workspace event listeners
typeof(TestErrorReportingService), // mocks the info-bar error reporting
diff --git a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj
index d24648010f3f3..91d98f244f935 100644
--- a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj
+++ b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj
@@ -46,6 +46,7 @@
+
diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace.cs
index 001839622e629..d24566278f456 100644
--- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace.cs
+++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace.cs
@@ -32,6 +32,22 @@ internal TestWorkspace(
{
}
+ internal TestWorkspace(
+ ExportProvider exportProvider,
+ string? workspaceKind = WorkspaceKind.Host,
+ Guid solutionTelemetryId = default,
+ bool disablePartialSolutions = true,
+ bool ignoreUnchangeableDocumentsWhenApplyingChanges = true,
+ WorkspaceConfigurationOptions? configurationOptions = null)
+ : base(exportProvider,
+ workspaceKind,
+ solutionTelemetryId,
+ disablePartialSolutions,
+ ignoreUnchangeableDocumentsWhenApplyingChanges,
+ configurationOptions)
+ {
+ }
+
private protected override TestHostDocument CreateDocument(
string text = "",
string displayName = "",
diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_Create.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_Create.cs
index 9505dca7cd156..9ace4ef7ab6c4 100644
--- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_Create.cs
+++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_Create.cs
@@ -6,6 +6,7 @@
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.VisualStudio.Composition;
namespace Microsoft.CodeAnalysis.Test.Utilities;
@@ -36,6 +37,19 @@ internal static TestWorkspace Create(
return workspace;
}
+ internal static TestWorkspace Create(
+ XElement workspaceElement,
+ ExportProvider exportProvider,
+ bool openDocuments = true,
+ string workspaceKind = null,
+ IDocumentServiceProvider documentServiceProvider = null,
+ bool ignoreUnchangeableDocumentsWhenApplyingChanges = true)
+ {
+ var workspace = new TestWorkspace(exportProvider, workspaceKind, ignoreUnchangeableDocumentsWhenApplyingChanges: ignoreUnchangeableDocumentsWhenApplyingChanges);
+ workspace.InitializeDocuments(workspaceElement, openDocuments, documentServiceProvider);
+ return workspace;
+ }
+
///
/// Creates a single buffer in a workspace.
///
diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs
index 12fcab3bafc6c..1fb5f99d3a7cd 100644
--- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs
+++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs
@@ -284,7 +284,7 @@ private static ParseOptions GetPreProcessorParseOptions(string language, XAttrib
else if (language == LanguageNames.VisualBasic)
{
return new VisualBasicParseOptions(preprocessorSymbols: preprocessorSymbolsAttribute.Value
- .Split(',').Select(v => KeyValuePair.Create(v.Split('=').ElementAt(0), (object)v.Split('=').ElementAt(1))).ToImmutableArray());
+ .Split(',').SelectAsArray(v => KeyValuePair.Create(v.Split('=').ElementAt(0), (object)v.Split('=').ElementAt(1))));
}
else
{
@@ -731,7 +731,7 @@ protected virtual (MetadataReference reference, ImmutableArray peImage) Cr
var compilation = CreateCompilation(referencedSource);
var aliasElement = referencedSource.Attribute("Aliases")?.Value;
- var aliases = aliasElement != null ? aliasElement.Split(',').Select(s => s.Trim()).ToImmutableArray() : default;
+ var aliases = aliasElement != null ? aliasElement.Split(',').SelectAsArray(s => s.Trim()) : default;
var includeXmlDocComments = false;
var includeXmlDocCommentsAttribute = referencedSource.Attribute(IncludeXmlDocCommentsAttributeName);
diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs
index 765e78aadfee9..aac6d93087643 100644
--- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs
+++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs
@@ -17,6 +17,7 @@
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Serialization;
@@ -36,7 +37,6 @@ public abstract partial class TestWorkspace : Wo
where TSolution : TestHostSolution
{
public ExportProvider ExportProvider { get; }
- public TestComposition? Composition { get; }
public bool CanApplyChangeDocument { get; set; }
@@ -60,10 +60,26 @@ internal TestWorkspace(
bool disablePartialSolutions = true,
bool ignoreUnchangeableDocumentsWhenApplyingChanges = true,
WorkspaceConfigurationOptions? configurationOptions = null)
- : base(GetHostServices(ref composition, configurationOptions != null), workspaceKind ?? WorkspaceKind.Host)
+ : this(
+ (composition ?? FeaturesTestCompositions.Features).ExportProviderFactory.CreateExportProvider(),
+ workspaceKind,
+ solutionTelemetryId,
+ disablePartialSolutions,
+ ignoreUnchangeableDocumentsWhenApplyingChanges,
+ configurationOptions)
{
- this.Composition = composition;
- this.ExportProvider = composition.ExportProviderFactory.CreateExportProvider();
+ }
+
+ internal TestWorkspace(
+ ExportProvider exportProvider,
+ string? workspaceKind = WorkspaceKind.Host,
+ Guid solutionTelemetryId = default,
+ bool disablePartialSolutions = true,
+ bool ignoreUnchangeableDocumentsWhenApplyingChanges = true,
+ WorkspaceConfigurationOptions? configurationOptions = null)
+ : base(VisualStudioMefHostServices.Create(exportProvider), workspaceKind ?? WorkspaceKind.Host)
+ {
+ this.ExportProvider = exportProvider;
var partialSolutionsTestHook = Services.GetRequiredService();
partialSolutionsTestHook.IsPartialSolutionDisabled = disablePartialSolutions;
@@ -155,18 +171,6 @@ internal void SetAnalyzerFallbackAndGlobalOptions(OptionsCollection? options)
options.SetGlobalOptions(GlobalOptions);
}
- private static HostServices GetHostServices([NotNull] ref TestComposition? composition, bool hasWorkspaceConfigurationOptions)
- {
- composition ??= FeaturesTestCompositions.Features;
-
- if (hasWorkspaceConfigurationOptions)
- {
- composition = composition.AddParts(typeof(TestWorkspaceConfigurationService));
- }
-
- return composition.GetHostServices();
- }
-
private protected abstract TDocument CreateDocument(
string text = "",
string displayName = "",
@@ -661,7 +665,7 @@ internal void InitializeDocuments(
var fromProject = projectNameToTestHostProject[fromName];
var toProject = projectNameToTestHostProject[toName];
- var aliases = projectReference.Attributes(AliasAttributeName).Select(a => a.Value).ToImmutableArray();
+ var aliases = projectReference.Attributes(AliasAttributeName).SelectAsArray(a => a.Value);
OnProjectReferenceAdded(fromProject.Id, new ProjectReference(toProject.Id, aliases.Any() ? aliases : default));
}
diff --git a/src/Workspaces/MSBuild/BuildHost/BuildHost.cs b/src/Workspaces/MSBuild/BuildHost/BuildHost.cs
index c121ef1bb646f..bde0afd2c3e64 100644
--- a/src/Workspaces/MSBuild/BuildHost/BuildHost.cs
+++ b/src/Workspaces/MSBuild/BuildHost/BuildHost.cs
@@ -42,62 +42,75 @@ private bool TryEnsureMSBuildLoaded(string projectOrSolutionFilePath)
return true;
}
- if (!PlatformInformation.IsRunningOnMono)
+ var instance = FindMSBuild(projectOrSolutionFilePath, includeUnloadableInstances: false);
+ if (instance is null)
{
+ return false;
+ }
- VisualStudioInstance? instance;
-
-#if NETFRAMEWORK
+ MSBuildLocator.RegisterMSBuildPath(instance.Path);
+ _logger.LogInformation($"Registered MSBuild {instance.Version} instance at {instance.Path}");
+ return true;
+ }
+ }
- // In this case, we're just going to pick the highest VS install on the machine, in case the projects are using some newer
- // MSBuild features. Since we don't have something like a global.json we can't really know what the minimum version is.
+ private MSBuildLocation? FindMSBuild(string projectOrSolutionFilePath, bool includeUnloadableInstances)
+ {
+ if (!PlatformInformation.IsRunningOnMono)
+ {
+ VisualStudioInstance? instance;
- // TODO: we should also check that the managed tools are actually installed
- instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(vs => vs.Version).FirstOrDefault();
+#if NETFRAMEWORK
+ // In this case, we're just going to pick the highest VS install on the machine, in case the projects are using some newer
+ // MSBuild features. Since we don't have something like a global.json we can't really know what the minimum version is.
+ // TODO: we should also check that the managed tools are actually installed
+ instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(vs => vs.Version).FirstOrDefault();
#else
+ // Locate the right SDK for this particular project; MSBuildLocator ensures in this case the first one is the preferred one.
+ // The includeUnloadableInstance parameter additionally locates SDKs from all installations regardless of whether they are
+ // loadable by the BuildHost process.
+ var options = new VisualStudioInstanceQueryOptions
+ {
+ DiscoveryTypes = DiscoveryType.DotNetSdk,
+ WorkingDirectory = Path.GetDirectoryName(projectOrSolutionFilePath),
+ AllowAllDotnetLocations = includeUnloadableInstances,
+ AllowAllRuntimeVersions = includeUnloadableInstances,
+ };
- // Locate the right SDK for this particular project; MSBuildLocator ensures in this case the first one is the preferred one.
- // TODO: we should pick the appropriate instance back in the main process and just use the one chosen here.
- var options = new VisualStudioInstanceQueryOptions { DiscoveryTypes = DiscoveryType.DotNetSdk, WorkingDirectory = Path.GetDirectoryName(projectOrSolutionFilePath) };
- instance = MSBuildLocator.QueryVisualStudioInstances(options).FirstOrDefault();
-
+ instance = MSBuildLocator.QueryVisualStudioInstances(options).FirstOrDefault();
#endif
- if (instance != null)
- {
- MSBuildLocator.RegisterInstance(instance);
- _logger.LogInformation($"Registered MSBuild instance at {instance.MSBuildPath}");
- }
- else
- {
- _logger.LogCritical("No compatible MSBuild instance could be found.");
- }
- }
- else
+ if (instance != null)
{
-#if NETFRAMEWORK
+ return new(instance.MSBuildPath, instance.Version.ToString());
+ }
- // We're running on Mono, but not all Mono installations have a usable MSBuild installation, so let's see if we have one that we can use.
- var monoMSBuildDirectory = MonoMSBuildDiscovery.GetMonoMSBuildDirectory();
+ _logger.LogCritical("No compatible MSBuild instance could be found.");
+ }
+ else
+ {
- if (monoMSBuildDirectory != null)
- {
- MSBuildLocator.RegisterMSBuildPath(monoMSBuildDirectory);
- _logger.LogInformation($"Registered MSBuild instance at {monoMSBuildDirectory}");
- }
- else
+#if NETFRAMEWORK
+ // We're running on Mono, but not all Mono installations have a usable MSBuild installation, so let's see if we have one that we can use.
+ var monoMSBuildDirectory = MonoMSBuildDiscovery.GetMonoMSBuildDirectory();
+ if (monoMSBuildDirectory != null)
+ {
+ var monoMSBuildVersion = MonoMSBuildDiscovery.GetMonoMSBuildVersion();
+ if (monoMSBuildVersion != null)
{
- _logger.LogCritical("No Mono MSBuild installation could be found; see https://www.mono-project.com/ for installation instructions.");
+ return new(monoMSBuildDirectory, monoMSBuildVersion);
}
+ }
+ _logger.LogCritical("No Mono MSBuild installation could be found; see https://www.mono-project.com/ for installation instructions.");
#else
- _logger.LogCritical("Trying to run the .NET Core BuildHost on Mono is unsupported.");
+ _logger.LogCritical("Trying to run the .NET Core BuildHost on Mono is unsupported.");
#endif
- }
- return MSBuildLocator.IsRegistered;
}
+
+ return null;
}
[MemberNotNull(nameof(_buildManager))]
@@ -122,6 +135,11 @@ private void CreateBuildManager()
}
}
+ public MSBuildLocation? FindBestMSBuild(string projectOrSolutionFilePath)
+ {
+ return FindMSBuild(projectOrSolutionFilePath, includeUnloadableInstances: true);
+ }
+
public bool HasUsableMSBuild(string projectOrSolutionFilePath)
{
return TryEnsureMSBuildLoaded(projectOrSolutionFilePath);
diff --git a/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFile.cs
index 100afdab1622f..36817b7b1db01 100644
--- a/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFile.cs
+++ b/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFile.cs
@@ -159,25 +159,22 @@ private ProjectFileInfo CreateProjectFileInfo(MSB.Execution.ProjectInstance proj
var targetFrameworkVersion = project.ReadPropertyString(PropertyNames.TargetFrameworkVersion);
- var docs = project.GetDocuments()
- .Where(IsNotTemporaryGeneratedFile)
- .Select(MakeDocumentFileInfo)
- .ToImmutableArray();
+ var docs = project.GetDocuments().SelectAsArray(
+ predicate: IsNotTemporaryGeneratedFile,
+ selector: MakeDocumentFileInfo);
var additionalDocs = project.GetAdditionalFiles()
- .Select(MakeNonSourceFileDocumentFileInfo)
- .ToImmutableArray();
+ .SelectAsArray(MakeNonSourceFileDocumentFileInfo);
var analyzerConfigDocs = project.GetEditorConfigFiles()
- .Select(MakeNonSourceFileDocumentFileInfo)
- .ToImmutableArray();
+ .SelectAsArray(MakeNonSourceFileDocumentFileInfo);
var packageReferences = project.GetPackageReferences();
var projectCapabilities = project.GetItems(ItemNames.ProjectCapability).SelectAsArray(item => item.ToString());
var contentFileInfo = GetContentFiles(project);
- var fileGlobs = _loadedProject?.GetAllGlobs().Select(GetFileGlobs).ToImmutableArray() ?? [];
+ var fileGlobs = _loadedProject?.GetAllGlobs().SelectAsArray(GetFileGlobs) ?? [];
return new ProjectFileInfo()
{
@@ -223,8 +220,7 @@ private static ImmutableArray GetContentFiles(MSB.Execution.ProjectInsta
private ImmutableArray GetCommandLineArgs(MSB.Execution.ProjectInstance project)
{
var commandLineArgs = GetCompilerCommandLineArgs(project)
- .Select(item => item.ItemSpec)
- .ToImmutableArray();
+ .SelectAsArray(item => item.ItemSpec);
if (commandLineArgs.Length == 0)
{
diff --git a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs
index 8724cc7c991c6..45be2011e2cf1 100644
--- a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs
+++ b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs
@@ -12,10 +12,27 @@ namespace Microsoft.CodeAnalysis.MSBuild;
///
internal interface IBuildHost
{
+ ///
+ /// Finds the best MSBuild instance installed for loading the given project or solution.
+ ///
+ ///
+ /// This may return MSBuild instances that are not loadable by the BuildHost process.
+ ///
+ MSBuildLocation? FindBestMSBuild(string projectOrSolutionFilePath);
+
+ ///
+ /// Determines whether there is a MSBuild instance that is loadable by the BuildHost process.
+ ///
+ ///
+ /// This may return true even if the project or solution require a newer version of MSBuild.
+ ///
bool HasUsableMSBuild(string projectOrSolutionFilePath);
+
Task LoadProjectFileAsync(string projectFilePath, string languageName, CancellationToken cancellationToken);
- /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.
+ ///
+ /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.
+ ///
/// A path to a project file which may or may not exist on disk. Note that an extension that is known by MSBuild, such as .csproj or .vbproj, should be used here.
/// The project file XML content.
int LoadProject(string projectFilePath, string projectContent, string languageName);
diff --git a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MSBuildLocation.cs b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MSBuildLocation.cs
new file mode 100644
index 0000000000000..a1edcde0d32d1
--- /dev/null
+++ b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MSBuildLocation.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.Runtime.Serialization;
+
+namespace Microsoft.CodeAnalysis.MSBuild;
+
+[DataContract]
+internal sealed class MSBuildLocation(string path, string version)
+{
+ ///
+ /// This is the path to the directory containing the MSBuild binaries.
+ ///
+ ///
+ /// When running on .NET this will be the path to the SDK required for loading projects.
+ ///
+ [DataMember(Order = 0)]
+ public string Path { get; } = path;
+
+ ///
+ /// This is the version of MSBuild at this location.
+ ///
+ [DataMember(Order = 1)]
+ public string Version { get; } = version;
+}
diff --git a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs
index 99a376391a654..05228c3bef8b6 100644
--- a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs
+++ b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@@ -17,6 +18,7 @@ internal static class MonoMSBuildDiscovery
private static string? s_monoRuntimeExecutablePath;
private static string? s_monoLibDirPath;
private static string? s_monoMSBuildDirectory;
+ private static string? s_monoVersionString;
private static IEnumerable GetSearchPaths()
{
@@ -143,7 +145,7 @@ private static IEnumerable GetSearchPaths()
if (!monoMSBuildDir.Exists)
return null;
- // Inside this is either a Current directory or a 15.0 directory, so find it; the previous code at
+ // Inside this is either a Current directory or a 15.0 directory, so find it; the previous code at
// https://github.com/OmniSharp/omnisharp-roslyn/blob/dde8119c40f4e3920eb5ea894cbca047033bd9aa/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs#L48-L58
// ensured we had a correctly normalized path in case the underlying file system might have been case insensitive.
var versionDirectory =
@@ -159,4 +161,32 @@ private static IEnumerable GetSearchPaths()
return s_monoMSBuildDirectory;
}
+
+ public static string? GetMonoMSBuildVersion()
+ {
+ Contract.ThrowIfTrue(PlatformInformation.IsWindows);
+
+ if (s_monoVersionString == null)
+ {
+ var monoMSBuildDirectory = GetMonoMSBuildDirectory();
+ if (monoMSBuildDirectory == null)
+ {
+ return null;
+ }
+
+ // Look for Microsoft.Build.dll in the tools path. If it isn't there, this is likely a Mono layout on Linux
+ // where the 'msbuild' package has not been installed.
+ var monoMSBuildPath = Path.Combine(monoMSBuildDirectory, "Microsoft.Build.dll");
+ try
+ {
+ var msbuildVersionInfo = FileVersionInfo.GetVersionInfo(monoMSBuildPath);
+ s_monoVersionString = msbuildVersionInfo.ProductVersion;
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ }
+
+ return s_monoVersionString;
+ }
}
diff --git a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs
index 0d58b27a46171..3a1c1dc95ac5a 100644
--- a/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs
+++ b/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs
@@ -33,6 +33,8 @@ internal sealed class BuildHostProcessManager : IAsyncDisposable
private static string MSBuildWorkspaceDirectory => Path.GetDirectoryName(typeof(BuildHostProcessManager).Assembly.Location)!;
private static bool IsLoadedFromNuGetPackage => File.Exists(Path.Combine(MSBuildWorkspaceDirectory, "..", "..", "microsoft.codeanalysis.workspaces.msbuild.nuspec"));
+ private static readonly string DotnetExecutable = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet";
+
public BuildHostProcessManager(ImmutableDictionary? globalMSBuildProperties = null, IBinLogPathProvider? binaryLogPathProvider = null, ILoggerFactory? loggerFactory = null)
{
_globalMSBuildProperties = globalMSBuildProperties ?? ImmutableDictionary.Empty;
@@ -57,13 +59,13 @@ public async Task GetBuildHostWithFallbackAsync(string projectF
///
public async Task<(RemoteBuildHost buildHost, BuildHostProcessKind actualKind)> GetBuildHostWithFallbackAsync(BuildHostProcessKind buildHostKind, string projectOrSolutionFilePath, CancellationToken cancellationToken)
{
- if (buildHostKind == BuildHostProcessKind.Mono && MonoMSBuildDiscovery.GetMonoMSBuildDirectory() == null)
+ if (buildHostKind == BuildHostProcessKind.Mono && MonoMSBuildDiscovery.GetMonoMSBuildVersion() == null)
{
- _logger?.LogWarning($"An installation of Mono could not be found; {projectOrSolutionFilePath} will be loaded with the .NET Core SDK and may encounter errors.");
+ _logger?.LogWarning($"An installation of Mono MSBuild could not be found; {projectOrSolutionFilePath} will be loaded with the .NET Core SDK and may encounter errors.");
buildHostKind = BuildHostProcessKind.NetCore;
}
- var buildHost = await GetBuildHostAsync(buildHostKind, cancellationToken).ConfigureAwait(false);
+ var buildHost = await GetBuildHostAsync(buildHostKind, projectOrSolutionFilePath, dotnetPath: null, cancellationToken).ConfigureAwait(false);
// If this is a .NET Framework build host, we may not have have build tools installed and thus can't actually use it to build.
// Check if this is the case. Unlike the mono case, we have to actually ask the other process since MSBuildLocator only allows
@@ -74,47 +76,99 @@ public async Task GetBuildHostWithFallbackAsync(string projectF
{
// It's not usable, so we'll fall back to the .NET Core one.
_logger?.LogWarning($"An installation of Visual Studio or the Build Tools for Visual Studio could not be found; {projectOrSolutionFilePath} will be loaded with the .NET Core SDK and may encounter errors.");
- return (await GetBuildHostAsync(BuildHostProcessKind.NetCore, cancellationToken).ConfigureAwait(false), actualKind: BuildHostProcessKind.NetCore);
+ return (await GetBuildHostAsync(BuildHostProcessKind.NetCore, projectOrSolutionFilePath, dotnetPath: null, cancellationToken).ConfigureAwait(false), BuildHostProcessKind.NetCore);
}
}
return (buildHost, buildHostKind);
}
- public async Task GetBuildHostAsync(BuildHostProcessKind buildHostKind, CancellationToken cancellationToken)
+ public Task GetBuildHostAsync(BuildHostProcessKind buildHostKind, CancellationToken cancellationToken)
+ {
+ return GetBuildHostAsync(buildHostKind, projectOrSolutionFilePath: null, dotnetPath: null, cancellationToken);
+ }
+
+ public async Task GetBuildHostAsync(BuildHostProcessKind buildHostKind, string? projectOrSolutionFilePath, string? dotnetPath, CancellationToken cancellationToken)
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (!_processes.TryGetValue(buildHostKind, out var buildHostProcess))
{
- var pipeName = Guid.NewGuid().ToString();
- var processStartInfo = CreateBuildHostStartInfo(buildHostKind, pipeName);
+ buildHostProcess = await NoLock_GetBuildHostAsync(buildHostKind, projectOrSolutionFilePath, dotnetPath, cancellationToken).ConfigureAwait(false);
- var process = Process.Start(processStartInfo);
- Contract.ThrowIfNull(process, "Process.Start failed to launch a process.");
+ _processes.Add(buildHostKind, buildHostProcess);
+ }
- buildHostProcess = new BuildHostProcess(process, pipeName, _loggerFactory);
- buildHostProcess.Disconnected += BuildHostProcess_Disconnected;
+ return buildHostProcess.BuildHost;
+ }
- // We've subscribed to Disconnected, but if the process crashed before that point we might have not seen it
- if (process.HasExited)
- {
- buildHostProcess.LogProcessFailure();
- throw new Exception($"BuildHost process exited immediately with {process.ExitCode}");
- }
+ async Task NoLock_GetBuildHostAsync(BuildHostProcessKind buildHostKind, string? projectOrSolutionFilePath, string? dotnetPath, CancellationToken cancellationToken)
+ {
+ var pipeName = Guid.NewGuid().ToString();
+ var processStartInfo = CreateBuildHostStartInfo(buildHostKind, pipeName, dotnetPath);
- _processes.Add(buildHostKind, buildHostProcess);
+ var process = Process.Start(processStartInfo);
+ Contract.ThrowIfNull(process, "Process.Start failed to launch a process.");
+
+ var buildHostProcess = new BuildHostProcess(process, pipeName, _loggerFactory);
+ buildHostProcess.Disconnected += BuildHostProcess_Disconnected;
+
+ // We've subscribed to Disconnected, but if the process crashed before that point we might have not seen it
+ if (process.HasExited)
+ {
+ buildHostProcess.LogProcessFailure();
+ throw new Exception($"BuildHost process exited immediately with {process.ExitCode}");
}
- return buildHostProcess.BuildHost;
+ if (buildHostKind != BuildHostProcessKind.NetCore
+ || projectOrSolutionFilePath is null
+ || dotnetPath is not null)
+ {
+ return buildHostProcess;
+ }
+
+ // When running on .NET Core, we need to find the right SDK location that can load our project and restart the BuildHost if required.
+ // When dotnetPath is null, the BuildHost is started with the default dotnet executable, which may not be the right one for the project.
+
+ var processPath = GetProcessPath();
+
+ // The running BuildHost will be able to search through all the SDK install locations for a usable MSBuild instance.
+ var msbuildLocation = await buildHostProcess.BuildHost.FindBestMSBuildAsync(projectOrSolutionFilePath, cancellationToken).ConfigureAwait(false);
+ if (msbuildLocation is null)
+ {
+ return buildHostProcess;
+ }
+
+ // The layout of the SDK is such that the dotnet executable is always at the same relative path from the MSBuild location.
+ dotnetPath = Path.GetFullPath(Path.Combine(msbuildLocation.Path, $"../../{DotnetExecutable}"));
+
+ // If the dotnetPath is null or the file doesn't exist, we can't do anything about it; the BuildHost will just use the default dotnet executable.
+ // If the dotnetPath is the same as processPath then we are already running from the right dotnet executable, so we don't need to relaunch.
+ if (dotnetPath is null || processPath == dotnetPath || !File.Exists(dotnetPath))
+ {
+ return buildHostProcess;
+ }
+
+ // We need to relaunch the .NET BuildHost from a different dotnet instance.
+ buildHostProcess.Disconnected -= BuildHostProcess_Disconnected;
+ await buildHostProcess.DisposeAsync().ConfigureAwait(false);
+ _logger?.LogInformation(".NET BuildHost started from {ProcessPath} reloading to start from {DotnetPath} to match necessary SDK location.", processPath, dotnetPath);
+
+ return await NoLock_GetBuildHostAsync(buildHostKind, projectOrSolutionFilePath, dotnetPath, cancellationToken).ConfigureAwait(false);
}
+
+#if NET
+ static string GetProcessPath() => Environment.ProcessPath ?? throw new InvalidOperationException("Unable to determine the path of the current process.");
+#else
+ static string GetProcessPath() => Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException("Unable to determine the path of the current process.");
+#endif
}
- internal ProcessStartInfo CreateBuildHostStartInfo(BuildHostProcessKind buildHostKind, string pipeName)
+ internal ProcessStartInfo CreateBuildHostStartInfo(BuildHostProcessKind buildHostKind, string pipeName, string? dotnetPath)
{
return buildHostKind switch
{
- BuildHostProcessKind.NetCore => CreateDotNetCoreBuildHostStartInfo(pipeName),
+ BuildHostProcessKind.NetCore => CreateDotNetCoreBuildHostStartInfo(pipeName, dotnetPath),
BuildHostProcessKind.NetFramework => CreateDotNetFrameworkBuildHostStartInfo(pipeName),
BuildHostProcessKind.Mono => CreateMonoBuildHostStartInfo(pipeName),
_ => throw ExceptionUtilities.UnexpectedValue(buildHostKind)
@@ -165,11 +219,11 @@ public async ValueTask DisposeAsync()
await process.DisposeAsync().ConfigureAwait(false);
}
- private ProcessStartInfo CreateDotNetCoreBuildHostStartInfo(string pipeName)
+ private ProcessStartInfo CreateDotNetCoreBuildHostStartInfo(string pipeName, string? dotnetPath)
{
var processStartInfo = new ProcessStartInfo()
{
- FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet",
+ FileName = dotnetPath ?? DotnetExecutable,
};
// We need to roll forward to the latest runtime, since the project may be using an SDK (or an SDK required runtime) newer than we ourselves built with.
@@ -231,9 +285,9 @@ private static string GetBuildHostPath(string contentFolderName, string assembly
if (IsLoadedFromNuGetPackage)
{
- // When Workspaces.MSBuild is loaded from the NuGet package (as is the case in .NET Interactive, NCrunch, and possibly other use cases)
+ // When Workspaces.MSBuild is loaded from the NuGet package (as is the case in .NET Interactive, NCrunch, and possibly other use cases)
// the Build host is deployed under the contentFiles folder.
- //
+ //
// Workspaces.MSBuild.dll Path - .nuget/packages/microsoft.codeanalysis.workspaces.msbuild/{version}/lib/{tfm}/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
// MSBuild.BuildHost.dll Path - .nuget/packages/microsoft.codeanalysis.workspaces.msbuild/{version}/contentFiles/any/any/{contentFolderName}/{assemblyName}
diff --git a/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs b/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs
index 76cb09e82a438..6edb429bfab09 100644
--- a/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs
+++ b/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs
@@ -19,6 +19,21 @@ public RemoteBuildHost(RpcClient client)
_client = client;
}
+ ///
+ /// Finds the best MSBuild instance installed for loading the given project or solution.
+ ///
+ ///
+ /// This will return the best MSBuild instance regardless of whether it is loadable by the BuildHost process.
+ ///
+ public Task FindBestMSBuildAsync(string projectOrSolutionFilePath, CancellationToken cancellationToken)
+ => _client.InvokeNullableAsync(BuildHostTargetObject, nameof(IBuildHost.FindBestMSBuild), parameters: [projectOrSolutionFilePath], cancellationToken);
+
+ ///
+ /// Determines whether there is a MSBuild instance that is loadable by the BuildHost process.
+ ///
+ ///
+ /// This may return true even if the project or solution require a newer version of MSBuild.
+ ///
public Task HasUsableMSBuildAsync(string projectOrSolutionFilePath, CancellationToken cancellationToken)
=> _client.InvokeAsync(BuildHostTargetObject, nameof(IBuildHost.HasUsableMSBuild), parameters: [projectOrSolutionFilePath], cancellationToken);
@@ -29,7 +44,9 @@ public async Task LoadProjectFileAsync(string projectFilePath
return new RemoteProjectFile(_client, remoteProjectFileTargetObject);
}
- /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.
+ ///
+ /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.
+ ///
/// A path to a project file which may or may not exist on disk. Note that an extension that is known by MSBuild, such as .csproj or .vbproj, should be used here.
/// The project file XML content.
public async Task LoadProjectAsync(string projectFilePath, string projectContent, string languageName, CancellationToken cancellationToken)
diff --git a/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs b/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs
index c89105505db99..4a3bc61b6e0b6 100644
--- a/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs
+++ b/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs
@@ -18,7 +18,7 @@ public sealed class BuildHostProcessManagerTests
public void ProcessStartInfo_ForNetCore_RollsForwardToLatestPreview()
{
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(BuildHostProcessKind.NetCore, pipeName: "");
+ .CreateBuildHostStartInfo(BuildHostProcessKind.NetCore, pipeName: "", dotnetPath: null);
#if NET
var rollForwardIndex = processStartInfo.ArgumentList.IndexOf("--roll-forward");
@@ -35,7 +35,7 @@ public void ProcessStartInfo_ForNetCore_RollsForwardToLatestPreview()
public void ProcessStartInfo_ForNetCore_LaunchesDotNetCLI()
{
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(BuildHostProcessKind.NetCore, pipeName: "");
+ .CreateBuildHostStartInfo(BuildHostProcessKind.NetCore, pipeName: "", dotnetPath: null);
Assert.StartsWith("dotnet", processStartInfo.FileName);
}
@@ -44,7 +44,7 @@ public void ProcessStartInfo_ForNetCore_LaunchesDotNetCLI()
public void ProcessStartInfo_ForMono_LaunchesMono()
{
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(BuildHostProcessKind.Mono, pipeName: "");
+ .CreateBuildHostStartInfo(BuildHostProcessKind.Mono, pipeName: "", dotnetPath: null);
Assert.Equal("mono", processStartInfo.FileName);
}
@@ -53,7 +53,7 @@ public void ProcessStartInfo_ForMono_LaunchesMono()
public void ProcessStartInfo_ForNetFramework_LaunchesBuildHost()
{
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(BuildHostProcessKind.NetFramework, pipeName: "");
+ .CreateBuildHostStartInfo(BuildHostProcessKind.NetFramework, pipeName: "", dotnetPath: null);
Assert.EndsWith("Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe", processStartInfo.FileName);
}
@@ -70,7 +70,7 @@ internal void ProcessStartInfo_PassesBinLogPath(BuildHostProcessKind buildHostKi
binLogPathProviderMock.Setup(m => m.GetNewLogPath()).Returns(BinaryLogPath);
var processStartInfo = new BuildHostProcessManager(binaryLogPathProvider: binLogPathProviderMock.Object)
- .CreateBuildHostStartInfo(buildHostKind, pipeName: "");
+ .CreateBuildHostStartInfo(buildHostKind, pipeName: "", dotnetPath: null);
#if NET
var binlogIndex = processStartInfo.ArgumentList.IndexOf("--binlog");
@@ -90,7 +90,7 @@ internal void ProcessStartInfo_PassesPipeName(BuildHostProcessKind buildHostKind
const string PipeName = "TestPipe";
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(buildHostKind, PipeName);
+ .CreateBuildHostStartInfo(buildHostKind, PipeName, dotnetPath: null);
#if NET
var binlogIndex = processStartInfo.ArgumentList.IndexOf("--pipe");
@@ -111,7 +111,7 @@ internal void ProcessStartInfo_PassesLocale(BuildHostProcessKind buildHostKind)
const string Locale = "de-DE";
var processStartInfo = new BuildHostProcessManager()
- .CreateBuildHostStartInfo(buildHostKind, pipeName: "");
+ .CreateBuildHostStartInfo(buildHostKind, pipeName: "", dotnetPath: null);
#if NET
var localeIndex = processStartInfo.ArgumentList.IndexOf("--locale");
@@ -136,7 +136,7 @@ internal void ProcessStartInfo_PassesProperties(BuildHostProcessKind buildHostKi
var buildHostProcessManager = new BuildHostProcessManager(globalMSBuildProperties);
- var processStartInfo = buildHostProcessManager.CreateBuildHostStartInfo(buildHostKind, pipeName: "");
+ var processStartInfo = buildHostProcessManager.CreateBuildHostStartInfo(buildHostKind, pipeName: "", dotnetPath: null);
#if NET
foreach (var kvp in globalMSBuildProperties)
diff --git a/src/Workspaces/MSBuild/Test/NewlyCreatedProjectsFromDotNetNew.cs b/src/Workspaces/MSBuild/Test/NewlyCreatedProjectsFromDotNetNew.cs
index aec4036aa929d..3b239de1cc68d 100644
--- a/src/Workspaces/MSBuild/Test/NewlyCreatedProjectsFromDotNetNew.cs
+++ b/src/Workspaces/MSBuild/Test/NewlyCreatedProjectsFromDotNetNew.cs
@@ -221,8 +221,7 @@ async Task AssertProjectLoadsCleanlyAsync(string projectFilePath, string[] ignor
// Unnecessary using directives are reported with a severity of Hidden
var nonHiddenDiagnostics = compilation!.GetDiagnostics()
- .Where(diagnostic => diagnostic.Severity > DiagnosticSeverity.Hidden)
- .ToImmutableArray();
+ .WhereAsArray(diagnostic => diagnostic.Severity > DiagnosticSeverity.Hidden);
// For good test hygiene lets ensure that all ignored diagnostics were actually reported.
var reportedDiagnosticIds = nonHiddenDiagnostics
diff --git a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs
index 70df9645d436f..6c21fb5f82d94 100644
--- a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs
+++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs
@@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
-using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
@@ -23,12 +22,13 @@ namespace Microsoft.CodeAnalysis.EditAndContinue;
[Export(typeof(IManagedHotReloadLanguageService))]
[Export(typeof(IManagedHotReloadLanguageService2))]
+[Export(typeof(IManagedHotReloadLanguageService3))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class ManagedHotReloadLanguageService(
IServiceBrokerProvider serviceBrokerProvider,
IEditAndContinueService encService,
- SolutionSnapshotRegistry solutionSnapshotRegistry) : IManagedHotReloadLanguageService2
+ SolutionSnapshotRegistry solutionSnapshotRegistry) : IManagedHotReloadLanguageService3
{
private sealed class PdbMatchingSourceTextProvider : IPdbMatchingSourceTextProvider
{
@@ -156,34 +156,9 @@ public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken)
return ValueTask.CompletedTask;
}
- public async ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken)
- {
- if (_disabled)
- {
- return;
- }
-
- try
- {
- Contract.ThrowIfNull(_debuggingSession);
-
- var currentDesignTimeSolution = await GetCurrentDesignTimeSolutionAsync(cancellationToken).ConfigureAwait(false);
- var currentCompileTimeSolution = GetCurrentCompileTimeSolution(currentDesignTimeSolution);
-
- _committedDesignTimeSolution = currentDesignTimeSolution;
-
- var projectIds = from path in projectPaths
- let projectId = currentCompileTimeSolution.Projects.FirstOrDefault(project => project.FilePath == path)?.Id
- where projectId != null
- select projectId;
-
- encService.UpdateBaselines(_debuggingSession.Value, currentCompileTimeSolution, [.. projectIds]);
- }
- catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
- {
- Disable();
- }
- }
+ [Obsolete]
+ public ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken)
+ => throw new NotImplementedException();
public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken)
{
@@ -269,10 +244,15 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio
}
}
+ [Obsolete]
public ValueTask GetUpdatesAsync(CancellationToken cancellationToken)
- => GetUpdatesAsync(runningProjects: [], cancellationToken);
+ => throw new NotImplementedException();
+
+ [Obsolete]
+ public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken)
+ => throw new NotImplementedException();
- public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken)
+ public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken)
{
if (_disabled)
{
@@ -285,24 +265,17 @@ public async ValueTask GetUpdatesAsync(ImmutableArray.GetInstance(out var runningProjectPaths);
- runningProjectPaths.AddAll(runningProjects);
-
- // TODO: Update once implemented: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2449700
- var runningProjectInfos = solution.Projects.Where(p => p.FilePath != null && runningProjectPaths.Contains(p.FilePath)).ToImmutableDictionary(
- keySelector: static p => p.Id,
- elementSelector: static p => new RunningProjectInfo { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false });
+ var runningProjectOptions = runningProjects.ToRunningProjectOptions(solution, static info => (info.ProjectInstanceId.ProjectFilePath, info.ProjectInstanceId.TargetFramework, info.RestartAutomatically));
EmitSolutionUpdateResults.Data results;
try
{
- results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectInfos, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate();
+ results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectOptions, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate();
}
catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
{
- results = EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjectInfos);
+ results = EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjectOptions);
}
// Only store the solution if we have any changes to apply, otherwise CommitUpdatesAsync/DiscardUpdatesAsync won't be called.
@@ -314,11 +287,15 @@ public async ValueTask GetUpdatesAsync(ImmutableArray GetProjectPaths(IEnumerable ids)
- => ids.SelectAsArray(id => solution.GetRequiredProject(id).FilePath!);
+ ToProjectIntanceIds(results.ProjectsToRebuild),
+ ToProjectIntanceIds(results.ProjectsToRestart.Keys));
+
+ ImmutableArray ToProjectIntanceIds(IEnumerable ids)
+ => ids.SelectAsArray(id =>
+ {
+ var project = solution.GetRequiredProject(id);
+ return new ProjectInstanceId(project.FilePath!, project.State.NameAndFlavor.flavor ?? "");
+ });
}
catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
{
diff --git a/src/Workspaces/Remote/Core/ExportProviderBuilder.cs b/src/Workspaces/Remote/Core/ExportProviderBuilder.cs
index 1819a38a84958..2b545830f1274 100644
--- a/src/Workspaces/Remote/Core/ExportProviderBuilder.cs
+++ b/src/Workspaces/Remote/Core/ExportProviderBuilder.cs
@@ -89,7 +89,7 @@ private async Task GetCompositionConfigurationAsync(Canc
// Verify we only have expected errors.
- ThrowOnUnexpectedErrors(config, catalog);
+ ReportCompositionErrors(config, catalog);
// Try to cache the composition.
_ = WriteCompositionCacheAsync(compositionCacheFile, config, cancellationToken).ReportNonFatalErrorAsync();
@@ -180,25 +180,28 @@ protected virtual void PerformCacheDirectoryCleanup(DirectoryInfo directoryInfo,
}
}
- protected abstract bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions);
+ protected abstract bool ContainsUnexpectedErrors(IEnumerable erroredParts);
- private void ThrowOnUnexpectedErrors(CompositionConfiguration configuration, ComposableCatalog catalog)
+ private void ReportCompositionErrors(CompositionConfiguration configuration, ComposableCatalog catalog)
{
+ foreach (var exception in catalog.DiscoveredParts.DiscoveryErrors)
+ {
+ LogError($"Encountered exception in the MEF composition: {exception.Message}");
+ }
+
// Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior.
var erroredParts = configuration.CompositionErrors.FirstOrDefault()?.SelectMany(error => error.Parts).Select(part => part.Definition.Type.Name) ?? [];
- if (ContainsUnexpectedErrors(erroredParts, catalog.DiscoveredParts.DiscoveryErrors))
+ if (ContainsUnexpectedErrors(erroredParts))
{
try
{
- catalog.DiscoveredParts.ThrowOnErrors();
configuration.ThrowOnErrors();
}
catch (CompositionFailedException ex)
{
// The ToString for the composition failed exception doesn't output a nice set of errors by default, so log it separately
- LogError($"Encountered errors in the MEF composition:{Environment.NewLine}{ex.ErrorsAsString}");
- throw;
+ LogError($"Encountered errors in the MEF composition: {ex.Message}{Environment.NewLine}{ex.ErrorsAsString}");
}
}
}
diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs
index f3c2787b4a189..1fb3243d6344b 100644
--- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs
+++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs
@@ -101,7 +101,7 @@ private async Task WriteBatchToPipeAsync(
// Keep track of how many checksums we found. We must find all the checksums we were asked to find.
var foundChecksumCount = 0;
- await foreach (var (checksum, asset) in checksumsAndAssets)
+ await foreach (var (checksum, asset) in checksumsAndAssets.ConfigureAwait(false))
{
await WriteSingleAssetToPipeAsync(
pooledStream.Object, objectWriter, checksum, asset, cancellationToken).ConfigureAwait(false);
diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs
index 085b92694ef20..574bcc9ff91a8 100644
--- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs
+++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs
@@ -77,10 +77,12 @@ public static async Task CreateAsync(
(service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, localSettingsDirectory, cancellationToken),
cancellationToken).ConfigureAwait(false);
+ string? errorMessage = null;
+
if (remoteProcessIdAndErrorMessage.HasValue)
{
if (remoteProcessIdAndErrorMessage.Value.errorMessage != null)
- hubClient.Logger.TraceEvent(TraceEventType.Error, 1, $"ServiceHub initialization error: {remoteProcessIdAndErrorMessage.Value.errorMessage}");
+ errorMessage = $"ServiceHub initialization error: {remoteProcessIdAndErrorMessage.Value.errorMessage}";
try
{
@@ -88,12 +90,24 @@ public static async Task CreateAsync(
}
catch (Exception e)
{
- hubClient.Logger.TraceEvent(TraceEventType.Error, 1, $"Unable to find Roslyn ServiceHub process: {e.Message}");
+ errorMessage = $"Unable to find Roslyn ServiceHub process: {e.Message}";
}
}
else
{
- hubClient.Logger.TraceEvent(TraceEventType.Error, 1, "Roslyn ServiceHub process initialization failed.");
+ errorMessage = "Roslyn ServiceHub process initialization failed.";
+ }
+
+ if (errorMessage != null)
+ {
+ hubClient.Logger.TraceEvent(TraceEventType.Error, 1, errorMessage);
+
+ var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(IRemoteInitializationService), client.Configuration);
+
+ services.GetRequiredService().ShowGlobalErrorInfo(
+ errorMessage,
+ TelemetryFeatureName.GetRemoteFeatureName(descriptor.ComponentName, descriptor.SimpleName),
+ exception: null);
}
await client.TryInvokeAsync(
diff --git a/src/Workspaces/Remote/ServiceHub.CoreComponents/CoreComponents.Shared.targets b/src/Workspaces/Remote/ServiceHub.CoreComponents/CoreComponents.Shared.targets
index 558f628f2022c..3c3c1322415ba 100644
--- a/src/Workspaces/Remote/ServiceHub.CoreComponents/CoreComponents.Shared.targets
+++ b/src/Workspaces/Remote/ServiceHub.CoreComponents/CoreComponents.Shared.targets
@@ -35,7 +35,8 @@
"Microsoft.NETCore.App.Runtime.win-{arch}"
"Microsoft.WindowsDesktop.App.Runtime.win-{arch}"
-->
-
+
+
@@ -66,6 +67,7 @@
<_R2RAssemblies Include="Microsoft.VisualStudio.Composition.dll" />
<_R2RAssemblies Include="Microsoft.VisualStudio.Telemetry.dll" />
<_R2RAssemblies Include="Microsoft.VisualStudio.Threading.dll" />
+ <_R2RAssemblies Include="Microsoft.VisualStudio.Copilot.Roslyn.SemanticSearch.dll" />
<_R2RAssemblies Include="MessagePack.dll" />
<_R2RAssemblies Include="Nerdback.Streams.dll" />
<_R2RAssemblies Include="Newtonsoft.Json.dll" />
diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs
index e93bbef2bb1da..5a18bb1c4a4da 100644
--- a/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs
+++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs
@@ -22,11 +22,13 @@ internal sealed class RemoteExportProviderBuilder : ExportProviderBuilder
{
internal static readonly ImmutableArray RemoteHostAssemblyNames =
MefHostServices.DefaultAssemblyNames
- .Add("Microsoft.CodeAnalysis.ExternalAccess.AspNetCore")
.Add("Microsoft.CodeAnalysis.Remote.ServiceHub")
- .Add("Microsoft.CodeAnalysis.ExternalAccess.Razor.Features")
.Add("Microsoft.CodeAnalysis.Remote.Workspaces")
- .Add("Microsoft.CodeAnalysis.ExternalAccess.Extensions");
+ .Add("Microsoft.CodeAnalysis.ExternalAccess.AspNetCore")
+ .Add("Microsoft.CodeAnalysis.ExternalAccess.Razor.Features")
+ .Add("Microsoft.CodeAnalysis.ExternalAccess.Extensions")
+ .Add("Microsoft.CodeAnalysis.ExternalAccess.Copilot")
+ .Add("Microsoft.VisualStudio.Copilot.Roslyn.SemanticSearch");
private static ExportProvider? s_instance;
internal static ExportProvider ExportProvider
@@ -71,16 +73,11 @@ protected override void LogTrace(string message)
{
}
- protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions)
+ protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts)
{
// Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior.
- var expectedErrorPartsSet = new HashSet(["PythiaSignatureHelpProvider", "VSTypeScriptAnalyzerService", "CodeFixService"]);
- var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part));
-
- if (hasUnexpectedErroredParts)
- return true;
-
- return partDiscoveryExceptions.Count > 0;
+ var expectedErrorPartsSet = new HashSet(["PythiaSignatureHelpProvider", "VSTypeScriptAnalyzerService", "CodeFixService", "CSharpMapCodeService", "CopilotSemanticSearchQueryExecutor"]);
+ return erroredParts.Any(part => !expectedErrorPartsSet.Contains(part));
}
private sealed class SimpleAssemblyLoader : IAssemblyLoader
diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj
index c8ed84bdf5b6e..34fc571959613 100644
--- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj
+++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs
index b7d92866bf9b7..4e1a9f8738a39 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs
@@ -78,6 +78,9 @@ public RemoteWorkspace GetWorkspace()
public SolutionServices GetWorkspaceServices()
=> GetWorkspace().Services.SolutionServices;
+ public static TService GetRequiredService()
+ => RemoteExportProviderBuilder.ExportProvider.GetExportedValue();
+
protected void Log(TraceEventType errorType, string message)
=> TraceLogger.TraceEvent(errorType, 0, $"{GetType()}: {message}");
diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
index 37861b2ddac56..fb4138165be25 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
@@ -13,11 +13,13 @@
using Microsoft.CodeAnalysis.Remote.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Telemetry;
+using Roslyn.Utilities;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;
namespace Microsoft.CodeAnalysis.Remote;
-internal sealed class RemoteDiagnosticAnalyzerService : BrokeredServiceBase, IRemoteDiagnosticAnalyzerService
+internal sealed class RemoteDiagnosticAnalyzerService(in BrokeredServiceBase.ServiceConstructionArguments arguments)
+ : BrokeredServiceBase(arguments), IRemoteDiagnosticAnalyzerService
{
internal sealed class Factory : FactoryBase
{
@@ -27,11 +29,6 @@ protected override IRemoteDiagnosticAnalyzerService CreateService(in ServiceCons
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache = new();
- public RemoteDiagnosticAnalyzerService(in ServiceConstructionArguments arguments)
- : base(arguments)
- {
- }
-
///
/// Calculate diagnostics. this works differently than other ones such as todo comments or designer attribute scanner
/// since in proc and out of proc runs quite differently due to concurrency and due to possible amount of data
@@ -79,9 +76,9 @@ public async ValueTask CalculateDiagnosti
}
}
- public async ValueTask> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken)
+ public ValueTask> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken)
{
- return await RunWithSolutionAsync(
+ return RunWithSolutionAsync(
solutionChecksum,
async solution =>
{
@@ -98,7 +95,7 @@ public async ValueTask> GetSourceGeneratorDiagnos
}
return builder.ToImmutableAndClear();
- }, cancellationToken).ConfigureAwait(false);
+ }, cancellationToken);
}
public ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray snapshot, int unitCount, bool forSpanAnalysis, CancellationToken cancellationToken)
@@ -121,4 +118,23 @@ public ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray> GetDiagnosticDescriptorsAsync(Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, CancellationToken cancellationToken)
+ {
+ return RunWithSolutionAsync(
+ solutionChecksum,
+ solution =>
+ {
+ var project = solution.GetRequiredProject(projectId);
+ var analyzerReference = project.AnalyzerReferences
+ .First(r => r.FullPath == analyzerReferenceFullPath);
+
+ var descriptors = project
+ .GetDiagnosticDescriptors(analyzerReference)
+ .SelectAsArray(DiagnosticDescriptorData.Create);
+
+ return ValueTask.FromResult(descriptors);
+ },
+ cancellationToken);
+ }
}
diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs
index 7d5be3e678b67..e9594d527b3cf 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs
@@ -141,7 +141,7 @@ public ValueTask> GetDocumentDiagnosticsAsync(Che
/// Remote API.
///
public ValueTask EmitSolutionUpdateAsync(
- Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken)
+ Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken)
{
return RunServiceAsync(solutionChecksum, async solution =>
{
@@ -182,18 +182,6 @@ public ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, Cancel
}, cancellationToken);
}
- ///
- /// Remote API.
- ///
- public ValueTask UpdateBaselinesAsync(Checksum solutionChecksum, DebuggingSessionId sessionId, ImmutableArray rebuiltProjects, CancellationToken cancellationToken)
- {
- return RunServiceAsync(solutionChecksum, solution =>
- {
- GetService().UpdateBaselines(sessionId, solution, rebuiltProjects);
- return default;
- }, cancellationToken);
- }
-
///
/// Remote API.
///
diff --git a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs
index f262e6e265941..1a28497b02ee6 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs
@@ -131,7 +131,7 @@ private int GetOrAddDefinitionItemId(DefinitionItem item)
public async ValueTask OnReferencesFoundAsync(IAsyncEnumerable references, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder.GetInstance(out var dehydrated);
- await foreach (var reference in references)
+ await foreach (var reference in references.ConfigureAwait(false))
{
var dehydratedReference = SerializableSourceReferenceItem.Dehydrate(
GetOrAddDefinitionItemId(reference.Definition), reference);
diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs
index 962fcb09cf497..cd0bdcc6ec8f3 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs
@@ -4,11 +4,11 @@
using System.Threading;
using System.Threading.Tasks;
+using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Classification;
-using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.SemanticSearch;
-using Roslyn.Utilities;
+using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Remote;
@@ -46,6 +46,15 @@ public async ValueTask OnSymbolFoundAsync(Solution solution, ISymbol symbol, Can
public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken)
=> callback.InvokeAsync((callback, cancellationToken) => callback.OnUserCodeExceptionAsync(callbackId, exception, cancellationToken), cancellationToken);
+
+ public ValueTask OnDocumentUpdatedAsync(DocumentId documentId, ImmutableArray changes, CancellationToken cancellationToken)
+ => callback.InvokeAsync((callback, cancellationToken) => callback.OnDocumentUpdatedAsync(callbackId, documentId, changes, cancellationToken), cancellationToken);
+
+ public ValueTask OnLogMessageAsync(string message, CancellationToken cancellationToken)
+ => callback.InvokeAsync((callback, cancellationToken) => callback.OnLogMessageAsync(callbackId, message, cancellationToken), cancellationToken);
+
+ public ValueTask OnTextFileUpdatedAsync(string filePath, string? newContent, CancellationToken cancellationToken)
+ => callback.InvokeAsync((callback, cancellationToken) => callback.OnTextFileUpdatedAsync(callbackId, filePath, newContent, cancellationToken), cancellationToken);
}
///
@@ -53,15 +62,15 @@ public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, Cance
///
public ValueTask CompileQueryAsync(
string query,
- string language,
+ string? targetLanguage,
string referenceAssembliesDir,
CancellationToken cancellationToken)
{
return RunServiceAsync(cancellationToken =>
{
var services = GetWorkspaceServices();
- var service = services.GetLanguageServices(language).GetRequiredService();
- var result = service.CompileQuery(services, query, referenceAssembliesDir, TraceLogger, cancellationToken);
+ var service = GetRequiredService();
+ var result = service.CompileQuery(services, query, targetLanguage, referenceAssembliesDir, TraceLogger, cancellationToken);
return ValueTask.FromResult(result);
}, cancellationToken);
@@ -74,7 +83,7 @@ public ValueTask DiscardQueryAsync(CompiledQueryId queryId, CancellationToken ca
{
return RunServiceAsync(cancellationToken =>
{
- var service = GetWorkspaceServices().GetLanguageServices(queryId.Language).GetRequiredService();
+ var service = GetRequiredService();
service.DiscardQuery(queryId);
return default;
@@ -88,19 +97,15 @@ public ValueTask ExecuteQueryAsync(
Checksum solutionChecksum,
RemoteServiceCallbackId callbackId,
CompiledQueryId queryId,
+ QueryExecutionOptions options,
CancellationToken cancellationToken)
{
return RunServiceAsync(solutionChecksum, async solution =>
{
- var service = solution.Services.GetLanguageServices(queryId.Language).GetService();
- if (service == null)
- {
- return new ExecuteQueryResult(FeaturesResources.Semantic_search_only_supported_on_net_core);
- }
-
+ var service = GetRequiredService();
var clientCallbacks = new ClientCallbacks(callback, callbackId);
- return await service.ExecuteQueryAsync(solution, queryId, observer: clientCallbacks, TraceLogger, cancellationToken).ConfigureAwait(false);
+ return await service.ExecuteQueryAsync(solution, queryId, observer: clientCallbacks, options, TraceLogger, cancellationToken).ConfigureAwait(false);
}, cancellationToken);
}
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs
index ef1afc430db50..6d01a60b73d11 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs
@@ -245,6 +245,14 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
var startIndexInclusive = startDelimiter.Length;
var endIndexExclusive = tokenText.Length - endDelimiter.Length;
+ var offset = token.SpanStart;
+
+ // Avoid creating and processsing the runes if there are no escapes or surrogates in the string.
+ if (!ContainsEscapeOrSurrogate(tokenText.AsSpan(startIndexInclusive, endIndexExclusive - startIndexInclusive), escapeBraces))
+ {
+ var sequence = VirtualCharSequence.Create(offset, tokenText);
+ return sequence.GetSubSequence(TextSpan.FromBounds(startIndexInclusive, endIndexExclusive));
+ }
// Do things in two passes. First, convert everything in the string to a 16-bit-char+span. Then walk
// again, trying to create Runes from the 16-bit-chars. We do this to simplify complex cases where we may
@@ -253,7 +261,6 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
using var _ = ArrayBuilder<(char ch, TextSpan span)>.GetInstance(out var charResults);
// First pass, just convert everything in the string (i.e. escapes) to plain 16-bit characters.
- var offset = token.SpanStart;
for (var index = startIndexInclusive; index < endIndexExclusive;)
{
var ch = tokenText[index];
@@ -282,6 +289,21 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
return CreateVirtualCharSequence(tokenText, offset, startIndexInclusive, endIndexExclusive, charResults);
}
+ private static bool ContainsEscapeOrSurrogate(ReadOnlySpan tokenText, bool escapeBraces)
+ {
+ foreach (var ch in tokenText)
+ {
+ if (ch == '\\')
+ return true;
+ else if (escapeBraces && IsOpenOrCloseBrace(ch))
+ return true;
+ else if (char.IsSurrogate(ch))
+ return true;
+ }
+
+ return false;
+ }
+
private static VirtualCharSequence CreateVirtualCharSequence(
string tokenText, int offset, int startIndexInclusive, int endIndexExclusive, ArrayBuilder<(char ch, TextSpan span)> charResults)
{
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/BlockSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/BlockSyntaxExtensions.cs
index 227d5accb6a03..86cdc44ca8c35 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/BlockSyntaxExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/BlockSyntaxExtensions.cs
@@ -50,8 +50,8 @@ static bool IsAnyCodeDirective(SyntaxTrivia trivia)
// contains an expression-statement or throw-statement.
bool HasAcceptableDirectiveShape(StatementSyntax statement, SyntaxToken closeBrace)
{
- var leadingDirectives = statement.GetLeadingTrivia().Where(IsAnyCodeDirective).ToImmutableArray();
- var closeBraceLeadingDirectives = block.CloseBraceToken.LeadingTrivia.Where(IsAnyCodeDirective).ToImmutableArray();
+ var leadingDirectives = statement.GetLeadingTrivia().WhereAsArray(IsAnyCodeDirective);
+ var closeBraceLeadingDirectives = block.CloseBraceToken.LeadingTrivia.WhereAsArray(IsAnyCodeDirective);
if (leadingDirectives.Length == 0)
{
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs
index 7381441a9689c..71a13d4419205 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpHeaderFacts.cs
@@ -75,8 +75,9 @@ public override bool IsOnLocalDeclarationHeader(SyntaxNode root, int position, [
{
var node = TryGetAncestorForLocation(root, position, out localDeclaration);
return node != null && IsOnHeader(root, position, node, node, holes: node.Declaration.Variables
- .Where(v => v.Initializer != null)
- .SelectAsArray(initializedV => initializedV.Initializer!.Value));
+ .SelectAsArray(
+ predicate: v => v.Initializer != null,
+ selector: initializedV => initializedV.Initializer!.Value));
}
public override bool IsOnIfStatementHeader(SyntaxNode root, int position, [NotNullWhen(true)] out SyntaxNode? ifStatement)
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
index 9a163c7477c77..75f88b5726c43 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
@@ -1665,6 +1665,13 @@ public void GetPartsOfParenthesizedExpression(
closeParen = parenthesizedExpression.CloseParenToken;
}
+ public void GetPartsOfPostfixUnaryExpression(SyntaxNode node, out SyntaxNode operand, out SyntaxToken operatorToken)
+ {
+ var postfixUnaryExpression = (PostfixUnaryExpressionSyntax)node;
+ operand = postfixUnaryExpression.Operand;
+ operatorToken = postfixUnaryExpression.OperatorToken;
+ }
+
public void GetPartsOfPrefixUnaryExpression(SyntaxNode node, out SyntaxToken operatorToken, out SyntaxNode operand)
{
var prefixUnaryExpression = (PrefixUnaryExpressionSyntax)node;
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/TypeStyle/CSharpUseExplicitTypeHelper.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/TypeStyle/CSharpUseExplicitTypeHelper.cs
index 67bee3e072b04..b93ca40fcf8c1 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/TypeStyle/CSharpUseExplicitTypeHelper.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/TypeStyle/CSharpUseExplicitTypeHelper.cs
@@ -110,6 +110,10 @@ typeName.Parent.Parent is (kind: SyntaxKind.LocalDeclarationStatement or SyntaxK
return false;
}
}
+ else if (typeName.Parent is DeclarationExpressionSyntax)
+ {
+ return !ContainsAnonymousType(typeName, semanticModel, cancellationToken);
+ }
return true;
}
@@ -141,18 +145,21 @@ protected override bool AssignmentSupportsStylePreference(
CSharpSimplifierOptions options,
CancellationToken cancellationToken)
{
- // is or contains an anonymous type
- // cases :
- // var anon = new { Num = 1 };
- // var enumerableOfAnons = from prod in products select new { prod.Color, prod.Price };
- var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type;
- if (declaredType.ContainsAnonymousType())
- {
+ if (ContainsAnonymousType(typeName, semanticModel, cancellationToken))
return false;
- }
// cannot find type if initializer resolves to an ErrorTypeSymbol
var initializerTypeInfo = semanticModel.GetTypeInfo(initializer, cancellationToken);
return !initializerTypeInfo.Type.IsErrorType();
}
+
+ private static bool ContainsAnonymousType(TypeSyntax typeName, SemanticModel semanticModel, CancellationToken cancellationToken)
+ {
+ // is or contains an anonymous type
+ // cases :
+ // var anon = new { Num = 1 };
+ // var enumerableOfAnons = from prod in products select new { prod.Color, prod.Price };
+ var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type;
+ return declaredType.ContainsAnonymousType();
+ }
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SymbolUsageInfo.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SymbolUsageInfo.cs
index 8f485357cbb8b..fe8a466d4328b 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SymbolUsageInfo.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SymbolUsageInfo.cs
@@ -4,6 +4,10 @@
using System.Diagnostics;
using System.Runtime.Serialization;
+using System.Threading;
+using Microsoft.CodeAnalysis.LanguageService;
+using Microsoft.CodeAnalysis.Operations;
+using static System.IO.Hashing.XxHashShared;
namespace Microsoft.CodeAnalysis;
@@ -43,4 +47,180 @@ public bool IsReadFrom()
public bool IsWrittenTo()
=> ValueUsageInfoOpt.HasValue && ValueUsageInfoOpt.Value.IsWrittenTo();
+
+ public static SymbolUsageInfo GetSymbolUsageInfo(
+ ISemanticFacts semanticFacts,
+ SemanticModel semanticModel,
+ SyntaxNode node,
+ CancellationToken cancellationToken)
+ {
+ var syntaxFacts = semanticFacts.SyntaxFacts;
+
+ var topNameNode = node;
+ while (syntaxFacts.IsQualifiedName(topNameNode.Parent))
+ topNameNode = topNameNode.Parent;
+
+ var parent = topNameNode?.Parent;
+
+ // typeof/sizeof are a special case where we don't want to return a TypeOrNamespaceUsageInfo, but rather a ValueUsageInfo.Name.
+ // This brings it in line with nameof(...), making all those operators appear in a similar fashion.
+ if (parent?.RawKind == syntaxFacts.SyntaxKinds.TypeOfExpression ||
+ parent?.RawKind == syntaxFacts.SyntaxKinds.SizeOfExpression)
+ {
+ return new(ValueUsageInfo.Name, typeOrNamespaceUsageInfoOpt: null);
+ }
+
+ var isInNamespaceNameContext = syntaxFacts.IsBaseNamespaceDeclaration(parent);
+ return syntaxFacts.IsInNamespaceOrTypeContext(topNameNode)
+ ? Create(GetTypeOrNamespaceUsageInfo())
+ : GetSymbolUsageInfoCommon();
+
+ // Local functions.
+ TypeOrNamespaceUsageInfo GetTypeOrNamespaceUsageInfo()
+ {
+ var usageInfo = IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts) || syntaxFacts.IsLeftSideOfExplicitInterfaceSpecifier(node)
+ ? TypeOrNamespaceUsageInfo.Qualified
+ : TypeOrNamespaceUsageInfo.None;
+
+ if (isInNamespaceNameContext)
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
+ }
+ else if (node.FirstAncestorOrSelf((node, syntaxFacts) => syntaxFacts.IsUsingOrExternOrImport(node), syntaxFacts) != null)
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.Import;
+ }
+
+ while (syntaxFacts.IsQualifiedName(node.Parent))
+ node = node.Parent;
+
+ if (syntaxFacts.IsTypeArgumentList(node.Parent))
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.TypeArgument;
+ }
+ else if (syntaxFacts.IsTypeConstraint(node.Parent))
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.TypeConstraint;
+ }
+ else if (syntaxFacts.IsBaseTypeList(node.Parent) ||
+ syntaxFacts.IsBaseTypeList(node.Parent?.Parent))
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.Base;
+ }
+ else if (syntaxFacts.IsTypeOfObjectCreationExpression(node))
+ {
+ usageInfo |= TypeOrNamespaceUsageInfo.ObjectCreation;
+ }
+
+ return usageInfo;
+ }
+
+ SymbolUsageInfo GetSymbolUsageInfoCommon()
+ {
+ if (semanticFacts.IsInOutContext(semanticModel, node, cancellationToken))
+ {
+ return Create(ValueUsageInfo.WritableReference);
+ }
+ else if (semanticFacts.IsInRefContext(semanticModel, node, cancellationToken))
+ {
+ return Create(ValueUsageInfo.ReadableWritableReference);
+ }
+ else if (semanticFacts.IsInInContext(semanticModel, node, cancellationToken))
+ {
+ return Create(ValueUsageInfo.ReadableReference);
+ }
+ else if (semanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken))
+ {
+ return Create(ValueUsageInfo.Write);
+ }
+ else
+ {
+ var operation = semanticModel.GetOperation(node, cancellationToken);
+ if (operation is IObjectCreationOperation)
+ return Create(TypeOrNamespaceUsageInfo.ObjectCreation);
+
+ // Note: sizeof/typeof also return 'name', but are handled above in GetSymbolUsageInfo.
+ if (IsInNameOfOperation(node))
+ return Create(ValueUsageInfo.Name);
+
+ if (node.IsPartOfStructuredTrivia())
+ return Create(ValueUsageInfo.Name);
+
+ var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken);
+ if (symbolInfo.Symbol != null)
+ {
+ switch (symbolInfo.Symbol.Kind)
+ {
+ case SymbolKind.Namespace:
+ var namespaceUsageInfo = TypeOrNamespaceUsageInfo.None;
+ if (isInNamespaceNameContext)
+ namespaceUsageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
+
+ if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
+ namespaceUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
+
+ return Create(namespaceUsageInfo);
+
+ case SymbolKind.NamedType:
+ var typeUsageInfo = TypeOrNamespaceUsageInfo.None;
+ if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
+ typeUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
+
+ return Create(typeUsageInfo);
+
+ case SymbolKind.Method:
+ case SymbolKind.Property:
+ case SymbolKind.Field:
+ case SymbolKind.Event:
+ case SymbolKind.Parameter:
+ case SymbolKind.Local:
+ var valueUsageInfo = ValueUsageInfo.Read;
+ if (semanticFacts.IsWrittenTo(semanticModel, node, cancellationToken))
+ valueUsageInfo |= ValueUsageInfo.Write;
+
+ return Create(valueUsageInfo);
+ }
+ }
+
+ return SymbolUsageInfo.None;
+ }
+ }
+
+ bool IsInNameOfOperation(SyntaxNode node)
+ {
+ // Walk up out of the member access expression. This way if we have something like
+ // nameof(C.Goo()), we ensure that operation.Parent is the INameOfOperation.
+
+ while (syntaxFacts.IsMemberAccessExpression(node?.Parent))
+ node = node.Parent;
+
+ if (node is null)
+ return false;
+
+ var operation = semanticModel.GetOperation(node, cancellationToken);
+
+ // Note: sizeof/typeof also return 'name', but are handled in GetSymbolUsageInfo.
+ if (operation?.Parent is INameOfOperation)
+ return true;
+
+ return false;
+ }
+ }
+
+ private static bool IsNodeOrAnyAncestorLeftSideOfDot(SyntaxNode node, ISyntaxFacts syntaxFacts)
+ {
+ if (syntaxFacts.IsLeftSideOfDot(node))
+ {
+ return true;
+ }
+
+ if (syntaxFacts.IsRightOfQualifiedName(node) ||
+ syntaxFacts.IsNameOfSimpleMemberAccessExpression(node) ||
+ syntaxFacts.IsNameOfMemberBindingExpression(node))
+ {
+ return syntaxFacts.IsLeftSideOfDot(node.Parent);
+ }
+
+ return false;
+ }
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs
index 6e2e935413c3a..4c8cdde6c1990 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs
@@ -281,31 +281,34 @@ static IEnumerable EnumerateAnnotatedSpans(SyntaxNode node, SyntaxAnno
var (firstToken, lastToken) = nodeOrToken.AsNode(out var childNode)
? (childNode.GetFirstToken(includeZeroWidth: true), childNode.GetLastToken(includeZeroWidth: true))
: (nodeOrToken.AsToken(), nodeOrToken.AsToken());
- yield return GetSpan(firstToken, lastToken);
+ yield return GetSpanIncludingPreviousAndNextTokens(firstToken, lastToken);
}
}
}
- internal static TextSpan GetSpan(SyntaxToken firstToken, SyntaxToken lastToken)
+ ///
+ /// Attempt to get a span that encompassed these tokens, but is expanded to go from the normal start of the
+ /// token that precedes them to the normal end of the token that follows. If there is no token that precedes
+ /// or follows, then we expand to consume at least the full span of the and
+ /// so that we at least will try to format any trivia on them.
+ ///
+ internal static TextSpan GetSpanIncludingPreviousAndNextTokens(SyntaxToken firstToken, SyntaxToken lastToken)
{
var previousToken = firstToken.GetPreviousToken();
- var nextToken = lastToken.GetNextToken();
-
- if (previousToken.RawKind != 0)
- {
- firstToken = previousToken;
- }
+ var start = previousToken.RawKind != 0
+ ? previousToken.SpanStart
+ : firstToken.FullSpan.Start;
- if (nextToken.RawKind != 0)
- {
- lastToken = nextToken;
- }
+ var nextToken = lastToken.GetNextToken();
+ var end = nextToken.RawKind != 0
+ ? nextToken.Span.End
+ : lastToken.FullSpan.End;
- return TextSpan.FromBounds(firstToken.SpanStart, lastToken.Span.End);
+ return TextSpan.FromBounds(start, end);
}
internal static TextSpan GetElasticSpan(SyntaxToken token)
- => GetSpan(token, token);
+ => GetSpanIncludingPreviousAndNextTokens(token, token);
private static IEnumerable AggregateSpans(IEnumerable spans)
{
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
index 220d7208706c5..d4bfb48e6c1fa 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
@@ -34,7 +34,7 @@ namespace Microsoft.CodeAnalysis.LanguageService;
///
/// -
/// 'GetXxxOfYYY' where 'XXX' matches the name of a property on a 'YYY' syntax construct that both C# and VB have. For
-/// example 'GetExpressionOfMemberAccessExpression' corresponding to MemberAccessExpressionsyntax.Expression in both C# and
+/// example 'GetExpressionOfMemberAccessExpression' corresponding to MemberAccessExpressionSyntax.Expression in both C# and
/// VB. These functions should throw if passed a node that the corresponding 'IsYYY' did not return for.
/// For nodes that only have a single child, these functions can stay here. For nodes with multiple children, these should migrate
/// to and be built off of 'GetPartsOfXXX'.
@@ -537,6 +537,7 @@ void GetPartsOfTupleExpression(SyntaxNode node,
void GetPartsOfImplicitObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode argumentList, out SyntaxNode? initializer);
void GetPartsOfParameter(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode? @default);
void GetPartsOfParenthesizedExpression(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode expression, out SyntaxToken closeParen);
+ void GetPartsOfPostfixUnaryExpression(SyntaxNode node, out SyntaxNode operand, out SyntaxToken operatorToken);
void GetPartsOfPrefixUnaryExpression(SyntaxNode node, out SyntaxToken operatorToken, out SyntaxNode operand);
void GetPartsOfQualifiedName(SyntaxNode node, out SyntaxNode left, out SyntaxToken dotToken, out SyntaxNode right);
void GetPartsOfUsingAliasDirective(SyntaxNode node, out SyntaxToken globalKeyword, out SyntaxToken alias, out SyntaxNode name);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
index f6a2d095ea2ce..fbb47a46606de 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
@@ -579,6 +579,12 @@ public static SyntaxNode GetNameOfMemberAccessExpression(this ISyntaxFacts synta
return name;
}
+ public static SyntaxNode GetOperandOfPostfixUnaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
+ {
+ syntaxFacts.GetPartsOfPostfixUnaryExpression(node, out var operand, out _);
+ return operand;
+ }
+
public static SyntaxNode GetOperandOfPrefixUnaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
{
syntaxFacts.GetPartsOfPrefixUnaryExpression(node, out _, out var operand);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IAsyncEnumerableExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IAsyncEnumerableExtensions.cs
index 3df805accec96..31cf47d93254c 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IAsyncEnumerableExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IAsyncEnumerableExtensions.cs
@@ -62,7 +62,7 @@ public static IAsyncEnumerable MergeAsync(this ImmutableArray stream, ChannelWriter writer, CancellationToken cancellationToken)
{
- await foreach (var value in stream)
+ await foreach (var value in stream.ConfigureAwait(false))
await writer.WriteAsync(value, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs
index deb8e86fa466f..4fd2ef3ea2e65 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Compilation/CompilationExtensions.cs
@@ -294,6 +294,12 @@ public static ImmutableArray GetReferencedAssemblySymbols(this
public static INamedTypeSymbol? InterpolatedStringHandlerAttributeType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(InterpolatedStringHandlerAttribute).FullName!);
+ public static INamedTypeSymbol? DateOnlyType(this Compilation compilation)
+ => compilation.GetTypeByMetadataName("System.DateOnly");
+
+ public static INamedTypeSymbol? TimeOnlyType(this Compilation compilation)
+ => compilation.GetTypeByMetadataName("System.TimeOnly");
+
///
/// Gets a type by its metadata name to use for code analysis within a . This method
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
index 05044d486c833..5f3bf68ff6138 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
@@ -1866,6 +1866,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService
closeParen = parenthesizedExpression.CloseParenToken
End Sub
+ Public Sub GetPartsOfPostfixUnaryExpression(node As SyntaxNode, ByRef operand As SyntaxNode, ByRef operatorToken As SyntaxToken) Implements ISyntaxFacts.GetPartsOfPostfixUnaryExpression
+ Throw New InvalidOperationException(DoesNotExistInVBErrorMessage)
+ End Sub
+
Public Sub GetPartsOfPrefixUnaryExpression(node As SyntaxNode, ByRef operatorToken As SyntaxToken, ByRef operand As SyntaxNode) Implements ISyntaxFacts.GetPartsOfPrefixUnaryExpression
Dim unaryExpression = DirectCast(node, UnaryExpressionSyntax)
operatorToken = unaryExpression.OperatorToken
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems
index 60ac10a5da8bc..57b34ba5239ef 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems
@@ -9,6 +9,7 @@
Microsoft.CodeAnalysis.CSharp.Shared
+
@@ -62,6 +63,8 @@
+
+
diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/FixAllSpanMappingService/CSharpFixAllSpanMappingService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeFixesAndRefactorings/CSharpFixAllSpanMappingService.cs
similarity index 90%
rename from src/Workspaces/CSharp/Portable/LanguageServices/FixAllSpanMappingService/CSharpFixAllSpanMappingService.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeFixesAndRefactorings/CSharpFixAllSpanMappingService.cs
index 33ecdd714ce22..60338353c5690 100644
--- a/src/Workspaces/CSharp/Portable/LanguageServices/FixAllSpanMappingService/CSharpFixAllSpanMappingService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeFixesAndRefactorings/CSharpFixAllSpanMappingService.cs
@@ -17,14 +17,10 @@
namespace Microsoft.CodeAnalysis.CSharp.CodeFixesAndRefactorings;
[ExportLanguageService(typeof(IFixAllSpanMappingService), LanguageNames.CSharp), Shared]
-internal sealed class CSharpFixAllSpanMappingService : AbstractFixAllSpanMappingService
+[method: ImportingConstructor]
+[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+internal sealed class CSharpFixAllSpanMappingService() : AbstractFixAllSpanMappingService
{
- [ImportingConstructor]
- [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
- public CSharpFixAllSpanMappingService()
- {
- }
-
protected override async Task>> GetFixAllSpansIfWithinGlobalStatementAsync(
Document document, TextSpan span, CancellationToken cancellationToken)
{
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs
index a97d4b64fd1aa..835dadfc6fa31 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs
@@ -81,8 +81,7 @@ public static MemberDeclarationSyntax GenerateNamedTypeDeclaration(
// the getter and setter to get generated instead. Since the list of members is going to include
// the method symbols for the getter and setter, we don't want to generate them twice.
- var members = GetMembers(namedType).Where(s => s.Kind != SymbolKind.Property || PropertyGenerator.CanBeGenerated((IPropertySymbol)s))
- .ToImmutableArray();
+ var members = GetMembers(namedType).WhereAsArray(s => s.Kind != SymbolKind.Property || PropertyGenerator.CanBeGenerated((IPropertySymbol)s));
if (namedType.IsRecord)
{
declaration = GenerateRecordMembers(service, info, (RecordDeclarationSyntax)declaration, members, cancellationToken);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Editing/CSharpImportAdder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Editing/CSharpImportAdder.cs
index 7a71c9ba2faf3..f612474882770 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Editing/CSharpImportAdder.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Editing/CSharpImportAdder.cs
@@ -148,7 +148,7 @@ await ProducerConsumer.RunParallelAsync(
consumeItems: static async (items, args, cancellationToken) =>
{
var (_, _, conflicts) = args;
- await foreach (var conflict in items)
+ await foreach (var conflict in items.ConfigureAwait(false))
conflicts.Add(conflict);
},
args: (self: this, containsAnonymousMethods, conflicts),
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs
index 8f2dfb7c24712..4740457936469 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs
@@ -19,15 +19,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions;
internal partial class ITypeSymbolExtensions
{
- private sealed class TypeSyntaxGeneratorVisitor : SymbolVisitor
+ private sealed class TypeSyntaxGeneratorVisitor(bool nameOnly) : SymbolVisitor
{
- private readonly bool _nameOnly;
-
private static readonly TypeSyntaxGeneratorVisitor NameOnlyInstance = new(nameOnly: true);
private static readonly TypeSyntaxGeneratorVisitor NotNameOnlyInstance = new(nameOnly: false);
- private TypeSyntaxGeneratorVisitor(bool nameOnly)
- => _nameOnly = nameOnly;
+ private static readonly QualifiedNameSyntax SystemObjectType =
+ QualifiedName(
+ AliasQualifiedName(
+ CreateGlobalIdentifier(),
+ IdentifierName("System")),
+ IdentifierName("Object"));
+
+ private readonly bool _nameOnly = nameOnly;
public static TypeSyntaxGeneratorVisitor Create(bool nameOnly = false)
=> nameOnly ? NameOnlyInstance : NotNameOnlyInstance;
@@ -186,16 +190,12 @@ public TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol)
}
if (symbol.Name == string.Empty || symbol.IsAnonymousType)
- {
- return CreateSystemObject();
- }
+ return SystemObjectType;
if (symbol.TypeParameters.Length == 0)
{
if (symbol.TypeKind == TypeKind.Error && symbol.Name == "var")
- {
- return CreateSystemObject();
- }
+ return SystemObjectType;
return symbol.Name.ToIdentifierName();
}
@@ -210,13 +210,7 @@ public TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol)
}
public static QualifiedNameSyntax CreateSystemObject()
- {
- return QualifiedName(
- AliasQualifiedName(
- CreateGlobalIdentifier(),
- IdentifierName("System")),
- IdentifierName("Object"));
- }
+ => SystemObjectType;
private static IdentifierNameSyntax CreateGlobalIdentifier()
=> IdentifierName(GlobalKeyword);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs
index b9610a98721e0..7875d6cdcb1a6 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs
@@ -39,17 +39,14 @@ private static TypeSyntax GenerateTypeSyntax(
var type = symbol as ITypeSymbol;
var containsAnonymousType = type != null && type.ContainsAnonymousType();
+ // something with an anonymous type can only be represented with 'var', regardless
+ // of what the user's preferences might be.
if (containsAnonymousType && allowVar)
- {
- // something with an anonymous type can only be represented with 'var', regardless
- // of what the user's preferences might be.
return IdentifierName("var");
- }
var syntax = containsAnonymousType
? TypeSyntaxGeneratorVisitor.CreateSystemObject()
- : symbol.Accept(TypeSyntaxGeneratorVisitor.Create(nameSyntax))!
- .WithAdditionalAnnotations(Simplifier.Annotation);
+ : symbol.Accept(TypeSyntaxGeneratorVisitor.Create(nameSyntax))!.WithAdditionalAnnotations(Simplifier.Annotation);
if (!allowVar)
syntax = syntax.WithAdditionalAnnotations(DoNotAllowVarAnnotation.Annotation);
diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Formatting/CSharpSyntaxFormattingService.cs
similarity index 99%
rename from src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Formatting/CSharpSyntaxFormattingService.cs
index 1af732a124a4b..05bbaf4e19eca 100644
--- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Formatting/CSharpSyntaxFormattingService.cs
@@ -280,12 +280,16 @@ or SyntaxKind.EndOfDirectiveToken
private ImmutableArray GetFormattingRules(ParsedDocument document, int position, SyntaxToken tokenBeforeCaret)
{
+#if CSHARP_WORKSPACE
var formattingRuleFactory = _services.SolutionServices.GetRequiredService();
+#endif
return
[
+#if CSHARP_WORKSPACE
formattingRuleFactory.CreateRule(document, position),
+#endif
.. GetTypingRules(tokenBeforeCaret),
- .. Formatter.GetDefaultFormattingRules(_services),
+ .. this.GetDefaultFormattingRules(),
];
}
diff --git a/src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Formatting/TypingFormattingRule.cs
similarity index 100%
rename from src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Formatting/TypingFormattingRule.cs
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs
index c9b073156028b..f38e5dc30742f 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs
@@ -68,7 +68,7 @@ public override async Task RemoveUnnecessaryImportsAsync(
}
}
- var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(SyntaxFormatting, cancellationToken).ConfigureAwait(false);
+ var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
var formattedRoot = SyntaxFormatting.GetFormattingResult(newRoot, spansToFormat, formattingOptions, rules: default, cancellationToken).GetFormattedRoot(cancellationToken);
return document.WithSyntaxRoot(formattedRoot);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeCleanup/CodeCleanupHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeCleanup/CodeCleanupHelpers.cs
new file mode 100644
index 0000000000000..a735cab94e664
--- /dev/null
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeCleanup/CodeCleanupHelpers.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.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+
+namespace Microsoft.CodeAnalysis.CodeCleanup;
+
+internal static class CodeCleanupHelpers
+{
+ public static async Task CleanupSyntaxAsync(
+ Document document, CodeCleanupOptions options, CancellationToken cancellationToken)
+ {
+ Contract.ThrowIfFalse(document.SupportsSyntaxTree);
+
+ // format any node with explicit formatter annotation
+ var syntaxFormatting = document.GetRequiredLanguageService();
+ var document1 = await syntaxFormatting.FormatAsync(document, Formatter.Annotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false);
+
+ // format any elastic whitespace
+ var document2 = await syntaxFormatting.FormatAsync(document1, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false);
+
+ return document2;
+ }
+}
diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/FixAllContextHelper.cs
similarity index 95%
rename from src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/FixAllContextHelper.cs
index 83a5f68d69146..fe4b3aef08f45 100644
--- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/FixAllContextHelper.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -46,6 +47,7 @@ public static async Task() is { } spanMappingService)
@@ -54,6 +56,9 @@ public static async Task p.Language == project.Language)
- .ToImmutableArray();
+ .WhereAsArray(p => p.Language == project.Language);
// Update the progress dialog with the count of projects to actually fix. We'll update the progress
// bar as we get all the documents in AddDocumentDiagnosticsAsync.
@@ -85,7 +89,7 @@ public static async Task.GetInstance(out var builder);
- await foreach (var diagnostics in results)
+ await foreach (var diagnostics in results.ConfigureAwait(false))
builder.AddRange(diagnostics);
return builder.ToImmutableAndClear();
@@ -105,6 +109,7 @@ public static async Task>> GetSpanDiagnosticsAsync(
FixAllContext fixAllContext,
IEnumerable>> documentsAndSpans)
@@ -121,6 +126,7 @@ static async Task>> Get
return builder.ToImmutableMultiDictionaryAndFree();
}
+#endif
}
private static async Task>> GetDocumentDiagnosticsToFixAsync(
diff --git a/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixesAndRefactorings/AbstractFixAllSpanMappingService.cs
similarity index 100%
rename from src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixesAndRefactorings/AbstractFixAllSpanMappingService.cs
diff --git a/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/IFixAllSpanMappingService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixesAndRefactorings/IFixAllSpanMappingService.cs
similarity index 100%
rename from src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/IFixAllSpanMappingService.cs
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixesAndRefactorings/IFixAllSpanMappingService.cs
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/ImportAdderService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/ImportAdderService.cs
index 9b9f8f0a575f1..786943d48779a 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/ImportAdderService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/ImportAdderService.cs
@@ -242,7 +242,9 @@ private async Task AddImportDirectivesFromSymbolAnnotationsAsync(
model,
cancellationToken).ConfigureAwait(false);
- var importsToAdd = importToSyntax.Where(kvp => safeImportsToAdd.Contains(kvp.Key)).Select(kvp => kvp.Value).ToImmutableArray();
+ var importsToAdd = importToSyntax.SelectAsArray(
+ predicate: kvp => safeImportsToAdd.Contains(kvp.Key),
+ selector: kvp => kvp.Value);
if (importsToAdd.Length == 0)
return document;
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs
index 7cd8fbc03720f..6b0ae9b15bfe1 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
@@ -131,4 +132,18 @@ public static Solution WithUpToDateSourceGeneratorDocuments(this Solution soluti
new SourceGeneratorExecutionVersionMap(projectIdToSourceGenerationVersion.ToImmutable()));
}
#endif
+
+ public static TextDocument? GetTextDocumentForLocation(this Solution solution, Location location)
+ {
+ switch (location.Kind)
+ {
+ case LocationKind.SourceFile:
+ return solution.GetDocument(location.SourceTree);
+ case LocationKind.ExternalFile:
+ var documentId = solution.GetDocumentIdsWithFilePath(location.GetLineSpan().Path).FirstOrDefault();
+ return solution.GetTextDocument(documentId);
+ default:
+ return null;
+ }
+ }
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/FormatterShared.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/FormatterShared.cs
new file mode 100644
index 0000000000000..4ca43d54847d0
--- /dev/null
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/FormatterShared.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Formatting.Rules;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+
+using static Microsoft.CodeAnalysis.Formatting.FormattingExtensions;
+
+namespace Microsoft.CodeAnalysis.Formatting;
+
+internal static class FormatterShared
+{
+ extension(ISyntaxFormatting syntaxFormatting)
+ {
+ public Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, CancellationToken cancellationToken)
+ => syntaxFormatting.FormatAsync(document, annotation, options, rules: default, cancellationToken);
+
+ public async Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
+ {
+ var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ return document.WithSyntaxRoot(syntaxFormatting.Format(root, annotation, options, rules, cancellationToken));
+ }
+
+ public SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
+ => syntaxFormatting.Format(node, GetAnnotatedSpans(node, annotation), options, rules, cancellationToken);
+
+ public SyntaxNode Format(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
+ => syntaxFormatting.GetFormattingResult(node, spans, options, rules, cancellationToken).GetFormattedRoot(cancellationToken);
+
+ public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken)
+ => syntaxFormatting.GetFormattingResult(node, spans, options, rules, cancellationToken);
+ }
+}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/SyntaxFormattingOptionsProviders.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/SyntaxFormattingOptionsProviders.cs
index da985d4374383..49ac745e58bc2 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/SyntaxFormattingOptionsProviders.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Formatting/SyntaxFormattingOptionsProviders.cs
@@ -14,12 +14,10 @@ internal static class SyntaxFormattingOptionsProviders
public static SyntaxFormattingOptions GetSyntaxFormattingOptions(this IOptionsReader options, Host.LanguageServices languageServices)
=> languageServices.GetRequiredService().GetFormattingOptions(options);
- public static ValueTask GetSyntaxFormattingOptionsAsync(this Document document, CancellationToken cancellationToken)
- => GetSyntaxFormattingOptionsAsync(document, document.GetRequiredLanguageService(), cancellationToken);
-
- public static async ValueTask GetSyntaxFormattingOptionsAsync(this Document document, ISyntaxFormatting formatting, CancellationToken cancellationToken)
+ public static async ValueTask GetSyntaxFormattingOptionsAsync(this Document document, CancellationToken cancellationToken)
{
var configOptions = await document.GetHostAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false);
+ var formatting = document.GetRequiredLanguageService();
return formatting.GetFormattingOptions(configOptions);
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.cs
index 613b33ec07caa..cc44e036d68a0 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.cs
@@ -82,8 +82,7 @@ public ImmutableArray InferTypes(
{
var result = CreateTypeInferrer(semanticModel, cancellationToken)
.InferTypes(position)
- .Select(t => t.InferredType)
- .ToImmutableArray();
+ .SelectAsArray(t => t.InferredType);
return InferTypeBasedOnNameIfEmpty(semanticModel, result, nameOpt);
}
@@ -94,8 +93,7 @@ public ImmutableArray InferTypes(
{
var result = CreateTypeInferrer(semanticModel, cancellationToken)
.InferTypes(expression)
- .Select(info => info.InferredType)
- .ToImmutableArray();
+ .SelectAsArray(info => info.InferredType);
return InferTypeBasedOnNameIfEmpty(semanticModel, result, nameOpt);
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems
index bcd7a64cba81f..4d20ed4ea2e56 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems
@@ -10,6 +10,9 @@
+
+
+
@@ -25,6 +28,7 @@
+
@@ -181,6 +185,7 @@
+
diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/FixAllSpanMappingService/VisualBasicFixAllSpanMappingService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/CodeFixesAndRefactorings/VisualBasicFixAllSpanMappingService.vb
similarity index 94%
rename from src/Workspaces/VisualBasic/Portable/LanguageServices/FixAllSpanMappingService/VisualBasicFixAllSpanMappingService.vb
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/CodeFixesAndRefactorings/VisualBasicFixAllSpanMappingService.vb
index 1fe502cf74f4d..04e2137f964bf 100644
--- a/src/Workspaces/VisualBasic/Portable/LanguageServices/FixAllSpanMappingService/VisualBasicFixAllSpanMappingService.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/CodeFixesAndRefactorings/VisualBasicFixAllSpanMappingService.vb
@@ -11,7 +11,7 @@ Imports Microsoft.CodeAnalysis.Text
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixesAndRefactorings
- Friend Class VisualBasicFixAllSpanMappingService
+ Friend NotInheritable Class VisualBasicFixAllSpanMappingService
Inherits AbstractFixAllSpanMappingService
diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Formatting/VisualBasicSyntaxFormattingService.vb
similarity index 96%
rename from src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb
rename to src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Formatting/VisualBasicSyntaxFormattingService.vb
index 2f1c254754b9c..ad27e567b5eab 100644
--- a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Formatting/VisualBasicSyntaxFormattingService.vb
@@ -12,7 +12,7 @@ Imports Microsoft.CodeAnalysis.Text
Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting
- Friend Class VisualBasicSyntaxFormattingService
+ Friend NotInheritable Class VisualBasicSyntaxFormattingService
Inherits VisualBasicSyntaxFormatting
Implements ISyntaxFormattingService
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems
index f1ea2654a14ca..459fdf9be2a00 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems
@@ -9,6 +9,7 @@
Microsoft.CodeAnalysis.VisualBasic.Shared
+
@@ -64,6 +65,7 @@
+