diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 8a872e2f5c01..6862676b94da 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -16,7 +16,11 @@ using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Formatting; +<<<<<<< HEAD using Microsoft.CodeAnalysis.Host; +======= +using Microsoft.CodeAnalysis.Indentation; +>>>>>>> parent of a1576d606f3 (Merge pull request #73618 from CyrusNajmabadi/sourceGenBalanced) using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -573,7 +577,513 @@ End Class var doc1Z = cs.GetDocument(document1.Id); var hasX = (await doc1Z.GetTextAsync()).ToString().Contains("X"); - if (hasX) + await workspaceWaiter.ExpeditedWaitAsync(); + } + + [Fact] + public async Task TestEmptySolutionUpdateDoesNotFireEvents() + { + using var workspace = CreateWorkspace(); + var project = new EditorTestHostProject(workspace); + workspace.AddTestProject(project); + + // wait for all previous operations to complete + await WaitForWorkspaceOperationsToComplete(workspace); + + var solution = workspace.CurrentSolution; + var workspaceChanged = false; + + workspace.WorkspaceChanged += (s, e) => workspaceChanged = true; + + // make an 'empty' update by claiming something changed, but its the same as before + workspace.OnParseOptionsChanged(project.Id, project.ParseOptions); + + // wait for any new outstanding operations to complete (there shouldn't be any) + await WaitForWorkspaceOperationsToComplete(workspace); + + // same solution instance == nothing changed + Assert.Equal(solution, workspace.CurrentSolution); + + // no event was fired because nothing was changed + Assert.False(workspaceChanged); + } + + [Fact] + public void TestAddProject() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + Assert.Equal(0, solution.Projects.Count()); + + var project = new EditorTestHostProject(workspace); + + workspace.AddTestProject(project); + solution = workspace.CurrentSolution; + + Assert.Equal(1, solution.Projects.Count()); + } + + [Fact] + public void TestRemoveExistingProject1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project = new EditorTestHostProject(workspace); + + workspace.AddTestProject(project); + workspace.OnProjectRemoved(project.Id); + solution = workspace.CurrentSolution; + + Assert.Equal(0, solution.Projects.Count()); + } + + [Fact] + public void TestRemoveExistingProject2() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project = new EditorTestHostProject(workspace); + + workspace.AddTestProject(project); + solution = workspace.CurrentSolution; + workspace.OnProjectRemoved(project.Id); + solution = workspace.CurrentSolution; + + Assert.Equal(0, solution.Projects.Count()); + } + + [Fact] + public void TestRemoveNonAddedProject1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project = new EditorTestHostProject(workspace); + + Assert.Throws(() => workspace.OnProjectRemoved(project.Id)); + } + + [Fact] + public void TestRemoveNonAddedProject2() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + + Assert.Throws(() => workspace.OnProjectRemoved(project2.Id)); + } + + [Fact] + public async Task TestChangeOptions1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument( + """ + #if GOO + class C { } + #else + class D { } + #endif + """); + + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + + await VerifyRootTypeNameAsync(workspace, "D"); + + workspace.OnParseOptionsChanged(document.Id.ProjectId, + new CSharpParseOptions(preprocessorSymbols: new[] { "GOO" })); + + await VerifyRootTypeNameAsync(workspace, "C"); + } + + [Fact] + public async Task TestChangeOptions2() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument( + """ + #if GOO + class C { } + #else + class D { } + #endif + """); + + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + workspace.OpenDocument(document.Id); + + await VerifyRootTypeNameAsync(workspace, "D"); + + workspace.OnParseOptionsChanged(document.Id.ProjectId, + new CSharpParseOptions(preprocessorSymbols: new[] { "GOO" })); + + await VerifyRootTypeNameAsync(workspace, "C"); + + workspace.CloseDocument(document.Id); + } + + [Fact] + public async Task TestAddedSubmissionParseTreeHasEmptyFilePath() + { + using var workspace = CreateWorkspace(); + var document1 = new EditorTestHostDocument("var x = 1;", displayName: "Sub1", sourceCodeKind: SourceCodeKind.Script); + var project1 = new EditorTestHostProject(workspace, document1, name: "Submission"); + + var document2 = new EditorTestHostDocument("var x = 2;", displayName: "Sub2", sourceCodeKind: SourceCodeKind.Script, filePath: "a.csx"); + var project2 = new EditorTestHostProject(workspace, document2, name: "Script"); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + workspace.TryApplyChanges(workspace.CurrentSolution); + + // Check that a parse tree for a submission has an empty file path. + var tree1 = await workspace.CurrentSolution + .GetProjectState(project1.Id) + .DocumentStates.GetState(document1.Id) + .GetSyntaxTreeAsync(CancellationToken.None); + Assert.Equal("", tree1.FilePath); + + // Check that a parse tree for a script does not have an empty file path. + var tree2 = await workspace.CurrentSolution + .GetProjectState(project2.Id) + .DocumentStates.GetState(document2.Id) + .GetSyntaxTreeAsync(CancellationToken.None); + Assert.Equal("a.csx", tree2.FilePath); + } + + private static async Task VerifyRootTypeNameAsync(EditorTestWorkspace workspaceSnapshotBuilder, string typeName) + { + var currentSnapshot = workspaceSnapshotBuilder.CurrentSolution; + var type = await GetRootTypeDeclarationAsync(currentSnapshot); + + Assert.Equal(type.Identifier.ValueText, typeName); + } + + private static async Task GetRootTypeDeclarationAsync(Solution currentSnapshot) + { + var tree = await currentSnapshot.Projects.First().Documents.First().GetSyntaxTreeAsync(); + var root = (CompilationUnitSyntax)tree.GetRoot(); + var type = (TypeDeclarationSyntax)root.Members[0]; + return type; + } + + [Fact] + public void TestAddP2PReferenceFails() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + + Assert.Throws(() => workspace.OnProjectReferenceAdded(project1.Id, new ProjectReference(project2.Id))); + } + + [Fact] + public void TestAddP2PReference1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var reference = new ProjectReference(project2.Id); + workspace.OnProjectReferenceAdded(project1.Id, reference); + + var snapshot = workspace.CurrentSolution; + var id1 = snapshot.Projects.First(p => p.Name == project1.Name).Id; + var id2 = snapshot.Projects.First(p => p.Name == project2.Name).Id; + + Assert.True(snapshot.GetProject(id1).ProjectReferences.Contains(reference), "ProjectReferences did not contain project2"); + } + + [Fact] + public void TestAddP2PReferenceTwice() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + workspace.OnProjectReferenceAdded(project1.Id, new ProjectReference(project2.Id)); + + Assert.Throws(() => workspace.OnProjectReferenceAdded(project1.Id, new ProjectReference(project2.Id))); + } + + [Fact] + public void TestRemoveP2PReference1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + workspace.OnProjectReferenceAdded(project1.Id, new ProjectReference(project2.Id)); + workspace.OnProjectReferenceRemoved(project1.Id, new ProjectReference(project2.Id)); + + var snapshot = workspace.CurrentSolution; + var id1 = snapshot.Projects.First(p => p.Name == project1.Name).Id; + var id2 = snapshot.Projects.First(p => p.Name == project2.Name).Id; + + Assert.Equal(0, snapshot.GetProject(id1).ProjectReferences.Count()); + } + + [Fact] + public void TestAddP2PReferenceCircularity() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var project1 = new EditorTestHostProject(workspace, name: "project1"); + var project2 = new EditorTestHostProject(workspace, name: "project2"); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + workspace.OnProjectReferenceAdded(project1.Id, new ProjectReference(project2.Id)); + + Assert.Throws(() => workspace.OnProjectReferenceAdded(project2.Id, new ProjectReference(project1.Id))); + } + + [Fact] + public void TestRemoveProjectWithOpenedDocuments() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument(string.Empty); + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + workspace.OpenDocument(document.Id); + + workspace.OnProjectRemoved(project1.Id); + Assert.False(workspace.IsDocumentOpen(document.Id)); + Assert.Empty(workspace.CurrentSolution.Projects); + } + + [Fact] + public void TestRemoveProjectWithClosedDocuments() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument(string.Empty); + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + workspace.OpenDocument(document.Id); + workspace.CloseDocument(document.Id); + workspace.OnProjectRemoved(project1.Id); + } + + [Fact] + public void TestRemoveOpenedDocument() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument(string.Empty); + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + workspace.OpenDocument(document.Id); + + workspace.OnDocumentRemoved(document.Id); + + Assert.Empty(workspace.CurrentSolution.Projects.Single().Documents); + + workspace.OnProjectRemoved(project1.Id); + } + + [Fact] + public async Task TestGetCompilation() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document = new EditorTestHostDocument(@"class C { }"); + var project1 = new EditorTestHostProject(workspace, document, name: "project1"); + + workspace.AddTestProject(project1); + await VerifyRootTypeNameAsync(workspace, "C"); + + var snapshot = workspace.CurrentSolution; + var id1 = snapshot.Projects.First(p => p.Name == project1.Name).Id; + + var compilation = await snapshot.GetProject(id1).GetCompilationAsync(); + var classC = compilation.SourceModule.GlobalNamespace.GetMembers("C").Single(); + } + + [Fact] + public async Task TestGetCompilationOnDependentProject() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document1 = new EditorTestHostDocument(@"public class C { }"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + + var document2 = new EditorTestHostDocument(@"class D : C { }"); + var project2 = new EditorTestHostProject(workspace, document2, name: "project2", projectReferences: new[] { project1 }); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var snapshot = workspace.CurrentSolution; + var id1 = snapshot.Projects.First(p => p.Name == project1.Name).Id; + var id2 = snapshot.Projects.First(p => p.Name == project2.Name).Id; + + var compilation2 = await snapshot.GetProject(id2).GetCompilationAsync(); + var classD = compilation2.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classC = classD.BaseType; + } + + [Fact] + public async Task TestGetCompilationOnCrossLanguageDependentProject() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var document1 = new EditorTestHostDocument(@"public class C { }"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + + var document2 = new EditorTestHostDocument(""" + Public Class D + Inherits C + End Class + """); + var project2 = new EditorTestHostProject(workspace, document2, language: LanguageNames.VisualBasic, name: "project2", projectReferences: new[] { project1 }); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var snapshot = workspace.CurrentSolution; + var id1 = snapshot.Projects.First(p => p.Name == project1.Name).Id; + var id2 = snapshot.Projects.First(p => p.Name == project2.Name).Id; + + var compilation2 = await snapshot.GetProject(id2).GetCompilationAsync(); + var classD = compilation2.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classC = classD.BaseType; + } + + [Fact] + public async Task TestGetCompilationOnCrossLanguageDependentProjectChanged() + { + using var workspace = CreateWorkspace(); + var solutionX = workspace.CurrentSolution; + + var document1 = new EditorTestHostDocument(@"public class C { }"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + + var document2 = new EditorTestHostDocument(""" + Public Class D + Inherits C + End Class + """); + var project2 = new EditorTestHostProject(workspace, document2, language: LanguageNames.VisualBasic, name: "project2", projectReferences: new[] { project1 }); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var solutionY = workspace.CurrentSolution; + var id1 = solutionY.Projects.First(p => p.Name == project1.Name).Id; + var id2 = solutionY.Projects.First(p => p.Name == project2.Name).Id; + + var compilation2 = await solutionY.GetProject(id2).GetCompilationAsync(); + var errors = compilation2.GetDiagnostics(); + var classD = compilation2.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classC = classD.BaseType; + Assert.NotEqual(TypeKind.Error, classC.TypeKind); + + // change the class name in document1 + workspace.OpenDocument(document1.Id); + var buffer1 = document1.GetTextBuffer(); + + // change C to X + buffer1.Replace(new Span(13, 1), "X"); + + // this solution should have the change + var solutionZ = workspace.CurrentSolution; + var docZ = solutionZ.GetDocument(document1.Id); + var docZText = await docZ.GetTextAsync(); + Assert.Equal("public class X { }", docZText.ToString()); + + var compilation2Z = await solutionZ.GetProject(id2).GetCompilationAsync(); + var classDz = compilation2Z.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classCz = classDz.BaseType; + + Assert.Equal(TypeKind.Error, classCz.TypeKind); + } + + [WpfFact] + public async Task TestDependentSemanticVersionChangesWhenNotOriginallyAccessed() + { + using var workspace = CreateWorkspace(disablePartialSolutions: false); + var solutionX = workspace.CurrentSolution; + + var document1 = new EditorTestHostDocument(@"public class C { }"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + + var document2 = new EditorTestHostDocument(""" + Public Class D + Inherits C + End Class + """); + var project2 = new EditorTestHostProject(workspace, document2, language: LanguageNames.VisualBasic, name: "project2", projectReferences: new[] { project1 }); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var solutionY = workspace.CurrentSolution; + var id1 = solutionY.Projects.First(p => p.Name == project1.Name).Id; + var id2 = solutionY.Projects.First(p => p.Name == project2.Name).Id; + + var compilation2y = await solutionY.GetProject(id2).GetCompilationAsync(); + var errors = compilation2y.GetDiagnostics(); + var classDy = compilation2y.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classCy = classDy.BaseType; + Assert.NotEqual(TypeKind.Error, classCy.TypeKind); + + // open both documents so background compiler works on their compilations + workspace.OpenDocument(document1.Id); + workspace.OpenDocument(document2.Id); + + // change C to X + var buffer1 = document1.GetTextBuffer(); + buffer1.Replace(new Span(13, 1), "X"); + + for (var iter = 0; iter < 10; iter++) { var newVersion = await cs.GetProject(project1.Id).GetDependentSemanticVersionAsync(); var newVersionX = await doc1Z.Project.GetDependentSemanticVersionAsync(); @@ -657,16 +1167,50 @@ End Class } } - // Should never find this since we're using partial semantics. - Assert.False(foundTheError, "Did find error"); - + [WpfFact] + public async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress() { - // the current solution should eventually have the change - var cs = workspace.CurrentSolution; - var doc1Z = cs.GetDocument(document1.Id); - var hasX = (await doc1Z.GetTextAsync()).ToString().Contains("X"); + var composition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestDocumentTrackingService)); - if (hasX) + using var workspace = CreateWorkspace(disablePartialSolutions: false, composition: composition); + var trackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); + var solutionX = workspace.CurrentSolution; + + var document1 = new EditorTestHostDocument(@"public class C { }"); + var project1 = new EditorTestHostProject(workspace, document1, name: "project1"); + + var document2 = new EditorTestHostDocument(""" + Public Class D + Inherits C + End Class + """); + var project2 = new EditorTestHostProject(workspace, document2, language: LanguageNames.VisualBasic, name: "project2", projectReferences: new[] { project1 }); + + workspace.AddTestProject(project1); + workspace.AddTestProject(project2); + + var solutionY = workspace.CurrentSolution; + var id1 = solutionY.Projects.First(p => p.Name == project1.Name).Id; + var id2 = solutionY.Projects.First(p => p.Name == project2.Name).Id; + + var compilation2y = await solutionY.GetProject(id2).GetCompilationAsync(); + var errors = compilation2y.GetDiagnostics(); + var classDy = compilation2y.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classCy = classDy.BaseType; + Assert.NotEqual(TypeKind.Error, classCy.TypeKind); + + // Make the second document active. As there is no automatic background compiler, no changes will be seen as long as we keep asking for frozen-partial semantics. + trackingService.SetActiveDocument(document2.Id); + + workspace.OpenDocument(document1.Id); + workspace.OpenDocument(document2.Id); + + // change C to X + var buffer1 = document1.GetTextBuffer(); + buffer1.Replace(new Span(13, 1), "X"); + + var foundTheError = false; + for (var iter = 0; iter < 5 && !foundTheError; iter++) { var doc2Z = cs.GetDocument(document2.Id); var compilation2Z = await doc2Z.Project.GetCompilationAsync(); @@ -676,6 +1220,30 @@ End Class if (classCz.TypeKind == TypeKind.Error) foundTheError = true; } + + // Should never find this since we're using partial semantics. + Assert.False(foundTheError, "Did find error"); + + { + // the current solution should eventually have the change + var cs = workspace.CurrentSolution; + var doc1Z = cs.GetDocument(document1.Id); + var hasX = (await doc1Z.GetTextAsync()).ToString().Contains("X"); + + if (hasX) + { + var doc2Z = cs.GetDocument(document2.Id); + var compilation2Z = await doc2Z.Project.GetCompilationAsync(); + var classDz = compilation2Z.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); + var classCz = classDz.BaseType; + + if (classCz.TypeKind == TypeKind.Error) + foundTheError = true; + } + } + + // 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 diff --git a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb index 8ebc28eb5e83..96808a907ff2 100644 --- a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb +++ b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb @@ -72,6 +72,7 @@ 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 f0aa89b965ad..7cc7b5cb3537 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -6,8 +6,10 @@ 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 @@ -55,24 +57,18 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename host As RenameTestHost, Optional renameOptions As SymbolRenameOptions = Nothing, Optional expectFailure As Boolean = False, - Optional sourceGenerator As ISourceGenerator = Nothing, - Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As RenameEngineResult + Optional sourceGenerator As ISourceGenerator = Nothing) As RenameEngineResult Dim composition = EditorTestCompositions.EditorFeatures.AddParts( GetType(NoCompilationContentTypeLanguageService), GetType(NoCompilationContentTypeDefinitions), - GetType(WorkspaceTestLogger), - GetType(TestWorkspaceConfigurationService)) + GetType(WorkspaceTestLogger)) 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 19f53caf2372..e6b354035207 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -10,8 +10,10 @@ 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 @@ -20,10 +22,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(TestWorkspaceConfigurationService)) + GetType(MockPreviewDialogService)) 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 19ecabcdd2cd..c86ff38b884b 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -6,7 +6,6 @@ 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 @@ -548,8 +547,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, - Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As Task + Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None + ) As Tasks.Task Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() @@ -558,9 +557,6 @@ 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 eafd9c2a0a47..da956f0c9f2b 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; 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,7 +16,6 @@ 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; @@ -83,11 +81,6 @@ 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 514450460e7f..773b736d0cb2 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", true); + "dotnet_source_generator_execution_balanced_feature_flag", false); } diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 74145221a205..75edd90c0dd4 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -5,8 +5,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities -Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Host +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Text @@ -123,9 +122,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer End Using End Function - - Friend Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly( - preference As SourceGeneratorExecutionPreference) As Task + + Public Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly() As Task Dim workspaceXml = @@ -133,13 +131,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer - Using workspace = EditorTestWorkspace.Create( - workspaceXml, - composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) - - Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) - configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=preference) - + Using workspace = EditorTestWorkspace.Create(workspaceXml) Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) @@ -155,31 +147,19 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - ' 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 + Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) End Using End Function - - Friend Async Function AddingAGeneratedDocumentUpdatesListCorrectly( - preference As SourceGeneratorExecutionPreference) As Task + + Public Async Function AddingAGeneratedDocumentUpdatesListCorrectly() As Task Dim 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) - + Using workspace = EditorTestWorkspace.Create(workspaceXml) Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) @@ -199,12 +179,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - ' 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 + Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) ' Add a second item and see if it updates correctly again workspace.OnAdditionalDocumentAdded( @@ -213,12 +188,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer "Test2.txt")) Await WaitForGeneratorsAndItemSourcesAsync(workspace) - - 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 + Assert.Equal(2, generatorFilesItemSource.Items.Cast(Of SourceGeneratedFileItem)().Count()) 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 c4b6d2b96349..a454f7215ff2 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -535,8 +535,6 @@ 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( @@ -668,21 +666,25 @@ 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 c19eed238124..9915049dc04b 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs @@ -4,16 +4,24 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -namespace Roslyn.Test.Utilities; - -[Export, Shared, PartNotDiscoverable] -[ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class TestWorkspaceConfigurationService() : IWorkspaceConfigurationService +namespace Roslyn.Test.Utilities { - public WorkspaceConfigurationOptions Options { get; set; } + [Export] + [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] + [Shared] + [PartNotDiscoverable] + internal sealed class TestWorkspaceConfigurationService : IWorkspaceConfigurationService + { + public WorkspaceConfigurationOptions Options { get; set; } + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestWorkspaceConfigurationService() + { + } + } }