diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index a04d587c91a78..dab3f3ac8d0cd 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -468,10 +469,15 @@ End Class var classC = classD.BaseType; } - [Fact] - public async Task TestGetCompilationOnCrossLanguageDependentProjectChanged() + [Theory, CombinatorialData] + internal async Task TestGetCompilationOnCrossLanguageDependentProjectChanged( + SourceGeneratorExecutionPreference preference) { - using var workspace = CreateWorkspace(); + using var workspace = CreateWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceConfigurationService))); + + var configService = workspace.ExportProvider.GetExportedValue(); + configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: preference); + var solutionX = workspace.CurrentSolution; var document1 = new EditorTestHostDocument(@"public class C { }"); @@ -514,7 +520,12 @@ End Class var classDz = compilation2Z.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); var classCz = classDz.BaseType; - Assert.Equal(TypeKind.Error, classCz.TypeKind); + // In balanced mode the skeleton won't be regenerated. So the downstream project won't see the change to + // remove the class. + if (preference is SourceGeneratorExecutionPreference.Automatic) + Assert.Equal(TypeKind.Error, classCz.TypeKind); + else + Assert.Equal(TypeKind.Class, classCz.TypeKind); } [WpfFact] @@ -575,12 +586,19 @@ End Class } } - [WpfFact] - public async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress() + [WpfTheory, CombinatorialData] + internal async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress( + SourceGeneratorExecutionPreference preference) { - var composition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestDocumentTrackingService)); + var composition = EditorTestCompositions.EditorFeatures.AddParts( + typeof(TestDocumentTrackingService), + typeof(TestWorkspaceConfigurationService)); using var workspace = CreateWorkspace(disablePartialSolutions: false, composition: composition); + + var configService = workspace.ExportProvider.GetExportedValue(); + configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: preference); + var trackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); var solutionX = workspace.CurrentSolution; @@ -662,8 +680,12 @@ End Class } } - // Should find now that we're going a normal compilation. - Assert.True(foundTheError, "Did not find error"); + // In balanced mode the skeleton won't be regenerated. So the downstream project won't see the change to + // remove the class. So it will not find the error symbol. + if (preference is SourceGeneratorExecutionPreference.Automatic) + Assert.True(foundTheError); + else + Assert.False(foundTheError); } [Fact] diff --git a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb index 96808a907ff22..8ebc28eb5e830 100644 --- a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb +++ b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb @@ -72,7 +72,6 @@ public partial class GeneratedClass : IInterface { } , host:=host, renameTo:="A", sourceGenerator:=New GeneratorThatImplementsInterfaceMethod()) - End Using End Sub diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 7cc7b5cb35379..f0aa89b965adf 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -6,10 +6,8 @@ Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeCleanup Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Host -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Remote.Testing Imports Microsoft.CodeAnalysis.Rename Imports Microsoft.CodeAnalysis.Rename.ConflictEngine @@ -57,18 +55,24 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename host As RenameTestHost, Optional renameOptions As SymbolRenameOptions = Nothing, Optional expectFailure As Boolean = False, - Optional sourceGenerator As ISourceGenerator = Nothing) As RenameEngineResult + Optional sourceGenerator As ISourceGenerator = Nothing, + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As RenameEngineResult Dim composition = EditorTestCompositions.EditorFeatures.AddParts( GetType(NoCompilationContentTypeLanguageService), GetType(NoCompilationContentTypeDefinitions), - GetType(WorkspaceTestLogger)) + GetType(WorkspaceTestLogger), + GetType(TestWorkspaceConfigurationService)) If host = RenameTestHost.OutOfProcess_SingleCall OrElse host = RenameTestHost.OutOfProcess_SplitCall Then composition = composition.WithTestHostParts(TestHost.OutOfProcess) End If Dim workspace = TestWorkspace.CreateWorkspace(workspaceXml, composition:=composition) + + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=executionPreference) + workspace.Services.SolutionServices.SetWorkspaceTestOutput(helper) If sourceGenerator IsNot Nothing Then diff --git a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb index e6b3540352073..19f53caf23725 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -10,10 +10,8 @@ Imports Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.RenameTracking Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Shared.TestHooks -Imports Microsoft.CodeAnalysis.Rename Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Text @@ -22,10 +20,10 @@ Imports Microsoft.VisualStudio.Text.Tagging Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Friend Module RenameTestHelpers - Private ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf.AddParts( GetType(MockDocumentNavigationServiceFactory), - GetType(MockPreviewDialogService)) + GetType(MockPreviewDialogService), + GetType(TestWorkspaceConfigurationService)) Private Function GetSessionInfo(workspace As EditorTestWorkspace) As (document As Document, textSpan As TextSpan) Dim hostdoc = workspace.DocumentWithCursor diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index c86ff38b884bf..19ecabcdd2cd3 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.Implementation.InlineRename Imports Microsoft.CodeAnalysis.Editor.InlineRename Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities +Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.InlineRename Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.[Shared].TestHooks @@ -547,8 +548,8 @@ class D : B Optional renameFile As Boolean = False, Optional resolvableConflictText As String = Nothing, Optional unresolvableConflictText As String = Nothing, - Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None - ) As Tasks.Task + Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None, + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As Task Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() @@ -557,6 +558,9 @@ class D : B globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, renameInComments) globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, renameFile) + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=executionPreference) + Dim cursorDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) Dim cursorPosition = cursorDocument.CursorPosition.Value diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index cdd36c6cb0524..349adc7ecaeca 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -10,6 +10,7 @@ using System.Runtime.Loader; using System.Text.Json; using Microsoft.CodeAnalysis.Contracts.Telemetry; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.BrokeredServices; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Logging; using Microsoft.CodeAnalysis.LanguageServer.Services; using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions; +using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Roslyn.Utilities; @@ -82,6 +84,11 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation using var exportProvider = await ExportProviderBuilder.CreateExportProviderAsync(extensionManager, serverConfiguration.DevKitDependencyPath, loggerFactory); + // LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to + // 'automatic' for now. + var globalOptionService = exportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + // The log file directory passed to us by VSCode might not exist yet, though its parent directory is guaranteed to exist. Directory.CreateDirectory(serverConfiguration.ExtensionLogDirectory); diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs index 773b736d0cb20..514450460e7f3 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs @@ -50,5 +50,5 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi SourceGeneratorExecutionPreferenceUtilities.GetEditorConfigString)); public static readonly Option2 SourceGeneratorExecutionBalancedFeatureFlag = new( - "dotnet_source_generator_execution_balanced_feature_flag", false); + "dotnet_source_generator_execution_balanced_feature_flag", true); } diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 75edd90c0dd42..74145221a2051 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -5,7 +5,8 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Editor.UnitTests +Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Text @@ -122,8 +123,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer End Using End Function - - Public Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly() As Task + + Friend Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly( + preference As SourceGeneratorExecutionPreference) As Task Dim workspaceXml = @@ -131,7 +133,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer - Using workspace = EditorTestWorkspace.Create(workspaceXml) + Using workspace = EditorTestWorkspace.Create( + workspaceXml, + composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) + + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=preference) + Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) @@ -147,19 +155,31 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + ' In balanced-mode the SG file won't go away until a save/build happens. + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + Else + Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + End If End Using End Function - - Public Async Function AddingAGeneratedDocumentUpdatesListCorrectly() As Task + + Friend Async Function AddingAGeneratedDocumentUpdatesListCorrectly( + preference As SourceGeneratorExecutionPreference) As Task Dim workspaceXml = - Using workspace = EditorTestWorkspace.Create(workspaceXml) + Using workspace = EditorTestWorkspace.Create( + workspaceXml, + composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) + + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=preference) + Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) @@ -179,7 +199,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + ' In balanced-mode the SG file won't be created until a save/build happens. + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + Else + Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + End If ' Add a second item and see if it updates correctly again workspace.OnAdditionalDocumentAdded( @@ -188,7 +213,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer "Test2.txt")) Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.Equal(2, generatorFilesItemSource.Items.Cast(Of SourceGeneratedFileItem)().Count()) + + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.Equal(2, generatorFilesItemSource.Items.Cast(Of SourceGeneratedFileItem)().Count()) + Else + Assert.Equal(1, generatorFilesItemSource.Items.Cast(Of NoSourceGeneratedFilesPlaceholderItem)().Count()) + End If End Using End Function diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 4f387f594bed4..17d034fbf0951 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -536,6 +536,8 @@ private async Task OnBatchScopeDisposedMaybeAsync(bool useAsync) var additionalDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); var analyzerConfigDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); + var hasAnalyzerChanges = _analyzersAddedInBatch.Count > 0 || _analyzersRemovedInBatch.Count > 0; + await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges => { _sourceFiles.UpdateSolutionForBatch( @@ -667,25 +669,21 @@ await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsyn }).ConfigureAwait(false); foreach (var (documentId, textContainer) in documentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } foreach (var (documentId, textContainer) in additionalDocumentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAdditionalDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } foreach (var (documentId, textContainer) in analyzerConfigDocumentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } // Give the host the opportunity to check if those files are open if (documentFileNamesAdded.Count > 0) - { await _projectSystemProjectFactory.RaiseOnDocumentsAddedMaybeAsync(useAsync, documentFileNamesAdded.ToImmutable()).ConfigureAwait(false); - } + + // If we added or removed analyzers, then re-run all generators to bring them up to date. + if (hasAnalyzerChanges) + _projectSystemProjectFactory.Workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); } } diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs index 9915049dc04bb..c19eed238124a 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs @@ -4,24 +4,16 @@ using System; using System.Composition; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -namespace Roslyn.Test.Utilities -{ - [Export] - [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] - [Shared] - [PartNotDiscoverable] - internal sealed class TestWorkspaceConfigurationService : IWorkspaceConfigurationService - { - public WorkspaceConfigurationOptions Options { get; set; } +namespace Roslyn.Test.Utilities; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestWorkspaceConfigurationService() - { - } - } +[Export, Shared, PartNotDiscoverable] +[ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class TestWorkspaceConfigurationService() : IWorkspaceConfigurationService +{ + public WorkspaceConfigurationOptions Options { get; set; } }