diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs index b4c2cbb29f6af..54d58fd117e17 100644 --- a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs @@ -102,7 +102,6 @@ internal sealed class TrackingSession private readonly Workspace _workspace; private readonly CancellationTokenSource _cancellationSource = new(); private readonly IActiveStatementSpanFactory _spanProvider; - private readonly ICompileTimeSolutionProvider _compileTimeSolutionProvider; private readonly WorkspaceEventRegistration _documentOpenedHandlerDisposer; private readonly WorkspaceEventRegistration _documentClosedHandlerDisposer; @@ -121,7 +120,6 @@ public TrackingSession(Workspace workspace, IActiveStatementSpanFactory spanProv { _workspace = workspace; _spanProvider = spanProvider; - _compileTimeSolutionProvider = workspace.Services.GetRequiredService(); _documentOpenedHandlerDisposer = _workspace.RegisterDocumentOpenedHandler(DocumentOpened); _documentClosedHandlerDisposer = _workspace.RegisterDocumentClosedHandler(DocumentClosed); @@ -158,26 +156,23 @@ private void DocumentClosed(DocumentEventArgs e) private void DocumentOpened(DocumentEventArgs e) => _ = TrackActiveSpansAsync(e.Document); - private async Task TrackActiveSpansAsync(Document designTimeDocument) + private async Task TrackActiveSpansAsync(Document document) { try { var cancellationToken = _cancellationSource.Token; - if (!designTimeDocument.DocumentState.SupportsEditAndContinue()) + if (!document.DocumentState.SupportsEditAndContinue()) { return; } - var compileTimeSolution = _compileTimeSolutionProvider.GetCompileTimeSolution(designTimeDocument.Project.Solution); - var compileTimeDocument = await compileTimeSolution.GetDocumentAsync(designTimeDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - - if (compileTimeDocument == null || !TryGetSnapshot(compileTimeDocument, out var snapshot)) + if (!TryGetSnapshot(document, out var snapshot)) { return; } - _ = await GetAdjustedTrackingSpansAsync(compileTimeDocument, snapshot, cancellationToken).ConfigureAwait(false); + _ = await GetAdjustedTrackingSpansAsync(document, snapshot, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs index 6a122bace49f2..225ab405f36d7 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -48,8 +48,8 @@ public NoSessionException() private bool _disabled; private RemoteDebuggingSessionProxy? _debuggingSession; - private Solution? _pendingUpdatedDesignTimeSolution; - private Solution? _committedDesignTimeSolution; + private Solution? _pendingUpdatedSolution; + private Solution? _committedSolution; public event Action? SolutionCommitted; @@ -72,12 +72,9 @@ public void SetFileLoggingDirectory(string? logDirectory) private SolutionServices Services => workspaceProvider.Value.Workspace.Services.SolutionServices; - private Solution GetCurrentDesignTimeSolution() + private Solution GetCurrentSolution() => workspaceProvider.Value.Workspace.CurrentSolution; - private Solution GetCurrentCompileTimeSolution(Solution currentDesignTimeSolution) - => Services.GetRequiredService().GetCompileTimeSolution(currentDesignTimeSolution); - private RemoteDebuggingSessionProxy GetDebuggingSession() => _debuggingSession ?? throw new NoSessionException(); @@ -114,16 +111,15 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) // so that we don't miss any pertinent workspace update events. sourceTextProvider.Activate(); - var currentSolution = GetCurrentDesignTimeSolution(); - _committedDesignTimeSolution = currentSolution; - var solution = GetCurrentCompileTimeSolution(currentSolution); + var currentSolution = GetCurrentSolution(); + _committedSolution = currentSolution; sourceTextProvider.SetBaseline(currentSolution); var proxy = new RemoteEditAndContinueServiceProxy(Services); _debuggingSession = await proxy.StartDebuggingSessionAsync( - solution, + currentSolution, new ManagedHotReloadServiceBridge(debuggerService.Value), sourceTextProvider, reportDiagnostics: true, @@ -155,7 +151,7 @@ private async ValueTask BreakStateOrCapabilitiesChangedAsync(bool? inBreakState, try { var session = GetDebuggingSession(); - var solution = (inBreakState == true) ? GetCurrentCompileTimeSolution(GetCurrentDesignTimeSolution()) : null; + var solution = (inBreakState == true) ? GetCurrentSolution() : null; await session.BreakStateOrCapabilitiesChangedAsync(inBreakState, cancellationToken).ConfigureAwait(false); @@ -191,18 +187,18 @@ public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) return; } - var committedDesignTimeSolution = Interlocked.Exchange(ref _pendingUpdatedDesignTimeSolution, null); - Contract.ThrowIfNull(committedDesignTimeSolution); + var committedSolution = Interlocked.Exchange(ref _pendingUpdatedSolution, null); + Contract.ThrowIfNull(committedSolution); try { - SolutionCommitted?.Invoke(committedDesignTimeSolution); + SolutionCommitted?.Invoke(committedSolution); } catch (Exception e) when (FatalError.ReportAndCatch(e)) { } - _committedDesignTimeSolution = committedDesignTimeSolution; + _committedSolution = committedSolution; try { @@ -222,7 +218,7 @@ public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) return; } - Contract.ThrowIfNull(Interlocked.Exchange(ref _pendingUpdatedDesignTimeSolution, null)); + Contract.ThrowIfNull(Interlocked.Exchange(ref _pendingUpdatedSolution, null)); try { @@ -258,8 +254,8 @@ public async ValueTask EndSessionAsync(CancellationToken cancellationToken) sourceTextProvider.Deactivate(); _debuggingSession = null; - _committedDesignTimeSolution = null; - _pendingUpdatedDesignTimeSolution = null; + _committedSolution = null; + _pendingUpdatedSolution = null; } private ActiveStatementSpanProvider GetActiveStatementSpanProvider(Solution solution) @@ -290,9 +286,9 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio return false; } - Contract.ThrowIfNull(_committedDesignTimeSolution); - var oldSolution = _committedDesignTimeSolution; - var newSolution = GetCurrentDesignTimeSolution(); + Contract.ThrowIfNull(_committedSolution); + var oldSolution = _committedSolution; + var newSolution = GetCurrentSolution(); return (sourceFilePath != null) ? await EditSession.HasChangesAsync(oldSolution, newSolution, sourceFilePath, cancellationToken).ConfigureAwait(false) @@ -326,8 +322,7 @@ public async ValueTask GetUpdatesAsync(ImmutableArray (info.ProjectInstanceId.ProjectFilePath, info.ProjectInstanceId.TargetFramework, info.RestartAutomatically)); @@ -338,13 +333,13 @@ public async ValueTask GetUpdatesAsync(ImmutableArray UseCohosting = new("razor_use_cohosting", defaultValue: false); -} diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs deleted file mode 100644 index 458c483fa1485..0000000000000 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Provides a compile-time view of the current workspace solution. -/// Workaround for Razor projects which generate both design-time and compile-time source files. -/// TODO: remove https://github.com/dotnet/roslyn/issues/51678 -/// -internal sealed class CompileTimeSolutionProvider : ICompileTimeSolutionProvider -{ - [ExportWorkspaceServiceFactory(typeof(ICompileTimeSolutionProvider), [WorkspaceKind.Host]), Shared] - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class Factory(IGlobalOptionService globalOptions) : IWorkspaceServiceFactory - { - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new CompileTimeSolutionProvider(workspaceServices.Workspace, globalOptions); - } - - private const string RazorEncConfigFileName = "RazorSourceGenerator.razorencconfig"; - private const string RazorSourceGeneratorTypeName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator"; - private static readonly ImmutableArray s_razorSourceGeneratorAssemblyNames = - [ - "Microsoft.NET.Sdk.Razor.SourceGenerators", - "Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators", - "Microsoft.CodeAnalysis.Razor.Compiler", - ]; - private static readonly ImmutableArray s_razorSourceGeneratorFileNamePrefixes = s_razorSourceGeneratorAssemblyNames - .SelectAsArray(static assemblyName => Path.Combine(assemblyName, RazorSourceGeneratorTypeName)); - - private readonly bool _useCohosting; - - private readonly object _gate = new(); - - /// - /// Cached compile-time solution corresponding to an existing design-time solution. - /// -#if NET - private readonly ConditionalWeakTable _designTimeToCompileTimeSolution = []; -#else - private ConditionalWeakTable _designTimeToCompileTimeSolution = new(); -#endif - - private Solution? _lastCompileTimeSolution; - - public CompileTimeSolutionProvider(Workspace workspace, IGlobalOptionService globalOptions) - { - _useCohosting = globalOptions.GetOption(LegacyRazorOptions.UseCohosting); - _ = workspace.RegisterWorkspaceChangedHandler((e) => - { - if (e.Kind is WorkspaceChangeKind.SolutionCleared or WorkspaceChangeKind.SolutionRemoved) - { - lock (_gate) - { -#if NET - _designTimeToCompileTimeSolution.Clear(); -#else - _designTimeToCompileTimeSolution = new(); -#endif - _lastCompileTimeSolution = null; - } - } - }); - } - - private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) - => documentState.FilePath != null && documentState.FilePath.EndsWith(RazorEncConfigFileName, StringComparison.OrdinalIgnoreCase); - - public Solution GetCompileTimeSolution(Solution designTimeSolution) - { - if (_useCohosting) - { - // when cohosting is on, the design time and runtime solutions are the same - return designTimeSolution; - } - - lock (_gate) - { - _designTimeToCompileTimeSolution.TryGetValue(designTimeSolution, out var cachedCompileTimeSolution); - - // Design time solution hasn't changed since we calculated the last compile-time solution: - if (cachedCompileTimeSolution != null) - return cachedCompileTimeSolution; - - var staleSolution = _lastCompileTimeSolution; - var compileTimeSolution = designTimeSolution; - - foreach (var projectState in compileTimeSolution.SolutionState.SortedProjectStates) - { - using var _1 = ArrayBuilder.GetInstance(out var configIdsToRemove); - using var _2 = ArrayBuilder.GetInstance(out var documentIdsToRemove); - - foreach (var (_, configState) in projectState.AnalyzerConfigDocumentStates.States) - { - if (IsRazorAnalyzerConfig(configState)) - { - configIdsToRemove.Add(configState.Id); - } - } - - // only remove design-time only documents when source-generated ones replace them - if (configIdsToRemove.Count > 0) - { - foreach (var (_, documentState) in projectState.DocumentStates.States) - { - if (documentState.Attributes.DesignTimeOnly || IsRazorDesignTimeDocument(documentState)) - { - documentIdsToRemove.Add(documentState.Id); - } - } - - compileTimeSolution = compileTimeSolution - .RemoveAnalyzerConfigDocuments(configIdsToRemove.ToImmutable()) - .RemoveDocuments(documentIdsToRemove.ToImmutable()); - - if (staleSolution is not null) - { - var existingStaleProject = staleSolution.GetProject(projectState.Id); - if (existingStaleProject is not null) - compileTimeSolution = compileTimeSolution.WithCachedSourceGeneratorState(projectState.Id, existingStaleProject); - } - } - } - - compileTimeSolution = _designTimeToCompileTimeSolution.GetValue(designTimeSolution, _ => compileTimeSolution); - _lastCompileTimeSolution = compileTimeSolution; - - return compileTimeSolution; - } - } - - // Copied from - // https://github.com/dotnet/sdk/blob/main/src/RazorSdk/SourceGenerators/RazorSourceGenerator.Helpers.cs#L32 - private static string GetIdentifierFromPath(string filePath) - { - var builder = new StringBuilder(filePath.Length); - - for (var i = 0; i < filePath.Length; i++) - { - switch (filePath[i]) - { - case ':' or '\\' or '/': - case char ch when !char.IsLetterOrDigit(ch): - builder.Append('_'); - break; - default: - builder.Append(filePath[i]); - break; - } - } - - return builder.ToString(); - } - - private static bool IsRazorDesignTimeDocument(DocumentState documentState) - => documentState.FilePath?.EndsWith(".razor.g.cs") == true || documentState.FilePath?.EndsWith(".cshtml.g.cs") == true; - - internal static async Task TryGetCompileTimeDocumentAsync( - Document designTimeDocument, - Solution compileTimeSolution, - CancellationToken cancellationToken, - string? generatedDocumentPathPrefix = null) - { - var compileTimeDocument = await compileTimeSolution.GetDocumentAsync(designTimeDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - if (compileTimeDocument != null) - { - return compileTimeDocument; - } - - if (!IsRazorDesignTimeDocument(designTimeDocument.DocumentState)) - { - return null; - } - - var designTimeProjectDirectoryName = PathUtilities.GetDirectoryName(designTimeDocument.Project.FilePath)!; - - var generatedDocumentPaths = BuildGeneratedDocumentPaths(designTimeProjectDirectoryName, designTimeDocument.FilePath!, generatedDocumentPathPrefix); - - var sourceGeneratedDocuments = await compileTimeSolution.GetRequiredProject(designTimeDocument.Project.Id).GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - return sourceGeneratedDocuments.SingleOrDefault(d => d.FilePath != null && generatedDocumentPaths.Contains(d.FilePath)); - } - - /// - /// Note that in .NET 6 Preview 7 the source generator changed to passing in the relative doc path without a leading \ to GetIdentifierFromPath - /// which caused the source generated file name to no longer be prefixed by an _. Additionally, the file extension was changed to .g.cs - /// - private static OneOrMany BuildGeneratedDocumentPaths(string designTimeProjectDirectoryName, string designTimeDocumentFilePath, string? generatedDocumentPathPrefix) - { - var relativeDocumentPath = GetRelativeDocumentPath(designTimeProjectDirectoryName, designTimeDocumentFilePath); - - if (generatedDocumentPathPrefix is not null) - { - return OneOrMany.Create(GetGeneratedDocumentPath(generatedDocumentPathPrefix, relativeDocumentPath)); - } - - return OneOrMany.Create(s_razorSourceGeneratorFileNamePrefixes.SelectAsArray( - static (prefix, relativeDocumentPath) => GetGeneratedDocumentPath(prefix, relativeDocumentPath), relativeDocumentPath)); - - static string GetGeneratedDocumentPath(string prefix, string relativeDocumentPath) - { - return Path.Combine(prefix, GetIdentifierFromPath(relativeDocumentPath)) + ".g.cs"; - } - } - - private static string GetRelativeDocumentPath(string projectDirectory, string designTimeDocumentFilePath) - => PathUtilities.GetRelativePath(projectDirectory, designTimeDocumentFilePath)[..^".g.cs".Length]; -} diff --git a/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs deleted file mode 100644 index 9f039b776002c..0000000000000 --- a/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Provides a compile-time view of the current workspace solution. -/// Workaround for Razor projects which generate both design-time and compile-time source files. -/// TODO: remove https://github.com/dotnet/roslyn/issues/51678 -/// -internal interface ICompileTimeSolutionProvider : IWorkspaceService -{ - Solution GetCompileTimeSolution(Solution designTimeSolution); -} diff --git a/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs b/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs deleted file mode 100644 index 3445b1f1c688b..0000000000000 --- a/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; -using Roslyn.Test.Utilities.TestGenerators; -using Xunit; - -namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests; - -[UseExportProvider] -public sealed class CompileTimeSolutionProviderTests -{ - [Theory] - [InlineData("razor")] - [InlineData("cshtml")] - public async Task TryGetCompileTimeDocumentAsync(string kind) - { - var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); - var projectId = ProjectId.CreateNewId(); - - var projectFilePath = Path.Combine(TempRoot.Root, "a.csproj"); - var additionalFilePath = Path.Combine(TempRoot.Root, "a", $"X.{kind}"); - var designTimeFilePath = Path.Combine(TempRoot.Root, "a", $"X.{kind}.g.cs"); - - var generator = new TestSourceGenerator() { ExecuteImpl = context => context.AddSource($"a_X_{kind}.g.cs", "") }; - var sourceGeneratedPathPrefix = Path.Combine(TempRoot.Root, typeof(TestSourceGenerator).Assembly.GetName().Name!, typeof(TestSourceGenerator).FullName); - var analyzerConfigId = DocumentId.CreateNewId(projectId); - var documentId = DocumentId.CreateNewId(projectId); - var additionalDocumentId = DocumentId.CreateNewId(projectId); - var designTimeDocumentId = DocumentId.CreateNewId(projectId); - - var designTimeSolution = workspace.CurrentSolution. - AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj", "proj", LanguageNames.CSharp, filePath: projectFilePath)). - WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo( - assemblyPath: Path.Combine(TempRoot.Root, "proj"), - generatedFilesOutputDirectory: null)). - WithProjectMetadataReferences(projectId, TargetFrameworkUtil.GetReferences(TargetFramework.NetStandard20)). - AddAnalyzerReference(projectId, new TestGeneratorReference(generator)). - AddAdditionalDocument(additionalDocumentId, "additional", SourceText.From(""), filePath: additionalFilePath). - AddAnalyzerConfigDocument(analyzerConfigId, "config", SourceText.From(""), filePath: "RazorSourceGenerator.razorencconfig"). - AddDocument(documentId, "a.cs", ""). - AddDocument(DocumentInfo.Create( - designTimeDocumentId, - name: "a", - loader: null, - filePath: designTimeFilePath, - isGenerated: true).WithDesignTimeOnly(true)); - - var designTimeDocument = designTimeSolution.GetRequiredDocument(designTimeDocumentId); - - var provider = workspace.Services.GetRequiredService(); - var compileTimeSolution = provider.GetCompileTimeSolution(designTimeSolution); - - Assert.False(compileTimeSolution.ContainsAnalyzerConfigDocument(analyzerConfigId)); - Assert.False(compileTimeSolution.ContainsDocument(designTimeDocumentId)); - Assert.True(compileTimeSolution.ContainsDocument(documentId)); - - var sourceGeneratedDoc = (await compileTimeSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single(); - - var compileTimeDocument = await CompileTimeSolutionProvider.TryGetCompileTimeDocumentAsync(designTimeDocument, compileTimeSolution, CancellationToken.None, sourceGeneratedPathPrefix); - Assert.Same(sourceGeneratedDoc, compileTimeDocument); - } - - [Fact] - public async Task GeneratorOutputCachedBetweenAcrossCompileTimeSolutions() - { - var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); - var projectId = ProjectId.CreateNewId(); - - var generatorInvocations = 0; - - var generator = new PipelineCallbackGenerator(context => - { - // We'll replicate a simple example of how the razor generator handles disabling here so the test - // functions similar to the real world - var isDisabled = context.AnalyzerConfigOptionsProvider.Select( - (o, ct) => o.GlobalOptions.TryGetValue("build_property.SuppressRazorSourceGenerator", out var value) && bool.Parse(value)); - - var sources = context.AdditionalTextsProvider.Combine(isDisabled).Select((pair, ct) => - { - var (additionalText, isDisabledFlag) = pair; - - if (isDisabledFlag) - return null; - - Interlocked.Increment(ref generatorInvocations); - return "// " + additionalText.GetText(ct)!.ToString(); - }); - - context.RegisterSourceOutput(sources, (context, s) => - { - if (s != null) - context.AddSource("hint", SourceText.From(s)); - }); - }); - - var analyzerConfigId = DocumentId.CreateNewId(projectId); - var additionalDocumentId = DocumentId.CreateNewId(projectId); - workspace.SetCurrentSolution(s => s - .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj", "proj", LanguageNames.CSharp)) - .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo( - assemblyPath: Path.Combine(TempRoot.Root, "proj"), - generatedFilesOutputDirectory: null)) - .AddAnalyzerReference(projectId, new TestGeneratorReference(generator)) - .AddAdditionalDocument(additionalDocumentId, "additional", SourceText.From(""), filePath: "additional.razor") - .AddAnalyzerConfigDocument(analyzerConfigId, "config", SourceText.From("is_global = true\r\nbuild_property.SuppressRazorSourceGenerator = true"), filePath: "Z:\\RazorSourceGenerator.razorencconfig"), - WorkspaceChangeKind.SolutionAdded); - - // Fetch a compilation first for the base solution; we're doing this because currently if we try to move the - // cached generator state to a snapshot that has no CompilationTracker at all, we won't update the state. - _ = await workspace.CurrentSolution.GetRequiredProject(projectId).GetCompilationAsync(); - - var provider = workspace.Services.GetRequiredService(); - var compileTimeSolution1 = provider.GetCompileTimeSolution(workspace.CurrentSolution); - - _ = await compileTimeSolution1.GetRequiredProject(projectId).GetCompilationAsync(); - - Assert.Equal(1, generatorInvocations); - - // Now do something that shouldn't force the generator to rerun; we must change this through the workspace since the - // service itself uses versions that won't change otherwise - var documentId = DocumentId.CreateNewId(projectId); - workspace.SetCurrentSolution( - s => s.AddDocument(documentId, "Test.cs", "// source file"), - WorkspaceChangeKind.DocumentAdded, - projectId, - documentId); - - var compileTimeSolution2 = provider.GetCompileTimeSolution(workspace.CurrentSolution); - Assert.NotSame(compileTimeSolution1, compileTimeSolution2); - - _ = await compileTimeSolution2.GetRequiredProject(projectId).GetCompilationAsync(); - - Assert.Equal(1, generatorInvocations); - } -} diff --git a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs index 9151ccc849200..f1738681dc17a 100644 --- a/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs +++ b/src/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs @@ -3,14 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue; @@ -20,59 +17,29 @@ private sealed class OpenDocumentSource(Document document) : AbstractDocumentDia { public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { - var designTimeDocument = Document; - var designTimeSolution = designTimeDocument.Project.Solution; - var services = designTimeSolution.Services; + var solution = Document.Project.Solution; + var services = solution.Services; // Do not report EnC diagnostics for a non-host workspace, or if Hot Reload/EnC session is not active. - if (designTimeSolution.WorkspaceKind != WorkspaceKind.Host || + if (solution.WorkspaceKind != WorkspaceKind.Host || services.GetService()?.SessionTracker is not { IsSessionActive: true } sessionStateTracker) { return []; } - var applyDiagnostics = sessionStateTracker.ApplyChangesDiagnostics.WhereAsArray(static (data, id) => data.DocumentId == id, designTimeDocument.Id); - - // Only create and synchronize compile-time solution if we need it. - var compileTimeSolution = services.GetRequiredService().GetCompileTimeSolution(designTimeSolution); - - var compileTimeDocument = await CompileTimeSolutionProvider.TryGetCompileTimeDocumentAsync(designTimeDocument, compileTimeSolution, cancellationToken).ConfigureAwait(false); - if (compileTimeDocument == null) - { - return applyDiagnostics; - } - - // EnC services should never be called on a design-time solution. + var applyDiagnostics = sessionStateTracker.ApplyChangesDiagnostics.WhereAsArray(static (data, id) => data.DocumentId == id, Document.Id); var proxy = new RemoteEditAndContinueServiceProxy(services); var spanLocator = services.GetService(); var activeStatementSpanProvider = spanLocator != null - ? new ActiveStatementSpanProvider((documentId, filePath, cancellationToken) => spanLocator.GetSpansAsync(compileTimeSolution, documentId, filePath, cancellationToken)) + ? new ActiveStatementSpanProvider((documentId, filePath, cancellationToken) => spanLocator.GetSpansAsync(solution, documentId, filePath, cancellationToken)) : static async (_, _, _) => ImmutableArray.Empty; - var rudeEditDiagnostics = await proxy.GetDocumentDiagnosticsAsync(compileTimeDocument, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); - - // TODO: remove - // We pretend the diagnostic is in the original document, but use the mapped line span. - // Razor will ignore the column (which will be off because #line directives can't currently map columns) and only use the line number. - rudeEditDiagnostics = rudeEditDiagnostics.SelectAsArray(data => (designTimeDocument != compileTimeDocument) ? RemapLocation(designTimeDocument, data) : data); + var rudeEditDiagnostics = await proxy.GetDocumentDiagnosticsAsync(Document, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); return applyDiagnostics.AddRange(rudeEditDiagnostics); } - - private static DiagnosticData RemapLocation(Document designTimeDocument, DiagnosticData data) - { - Debug.Assert(data.DataLocation != null); - Debug.Assert(designTimeDocument.FilePath != null); - - // If the location in the generated document is in a scope of user-visible #line mapping use the mapped span, - // otherwise (if it's hidden) display the diagnostic at the start of the file. - var span = data.DataLocation.UnmappedFileSpan != data.DataLocation.MappedFileSpan ? data.DataLocation.MappedFileSpan.Span : default; - var location = new DiagnosticDataLocation(new FileLinePositionSpan(designTimeDocument.FilePath, span)); - - return data.WithLocations(location, additionalLocations: []); - } } public static IDiagnosticSource CreateOpenDocumentSource(Document document) diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index ff484050603e9..cdbbc91e155c4 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -449,6 +449,5 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_validate_compilation_tracker_states", new FeatureFlagStorage(@"Roslyn.ValidateCompilationTrackerStates")}, {"dotnet_source_generator_execution", new RoamingProfileStorage("TextEditor.Roslyn.Specific.SourceGeneratorExecution")}, {"xaml_enable_lsp_intellisense", new FeatureFlagStorage(@"Xaml.EnableLspIntelliSense")}, - {"razor_use_cohosting", new FeatureFlagStorage("Razor.LSP.UseRazorCohostServer")}, }; } diff --git a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs index df628047d59d6..2e0c5db7db8a5 100644 --- a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -47,19 +47,19 @@ private sealed class PdbMatchingSourceTextProvider : IPdbMatchingSourceTextProvi private bool _disabled; private DebuggingSessionId? _debuggingSession; - private Solution? _committedDesignTimeSolution; - private Solution? _pendingUpdatedDesignTimeSolution; + private Solution? _committedSolution; + private Solution? _pendingUpdatedSolution; private void Disable() { _disabled = true; _debuggingSession = null; - _committedDesignTimeSolution = null; - _pendingUpdatedDesignTimeSolution = null; + _committedSolution = null; + _pendingUpdatedSolution = null; solutionSnapshotRegistry.Clear(); } - private async ValueTask GetCurrentDesignTimeSolutionAsync(CancellationToken cancellationToken) + private async ValueTask GetCurrentSolutionAsync(CancellationToken cancellationToken) { // First, calls to the client to get the current snapshot id. // The client service calls the LSP client, which sends message to the LSP server, which in turn calls back to RegisterSolutionSnapshot. @@ -69,9 +69,6 @@ private async ValueTask GetCurrentDesignTimeSolutionAsync(Cancellation return solutionSnapshotRegistry.GetRegisteredSolutionSnapshot(id); } - private static Solution GetCurrentCompileTimeSolution(Solution currentDesignTimeSolution) - => currentDesignTimeSolution.Services.GetRequiredService().GetCompileTimeSolution(currentDesignTimeSolution); - public async ValueTask StartSessionAsync(CancellationToken cancellationToken) { if (_disabled) @@ -81,13 +78,12 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) try { - var currentDesignTimeSolution = await GetCurrentDesignTimeSolutionAsync(cancellationToken).ConfigureAwait(false); - _committedDesignTimeSolution = currentDesignTimeSolution; - var compileTimeSolution = GetCurrentCompileTimeSolution(currentDesignTimeSolution); + var currentSolution = await GetCurrentSolutionAsync(cancellationToken).ConfigureAwait(false); + _committedSolution = currentSolution; // TODO: use remote proxy once we transition to pull diagnostics _debuggingSession = encService.StartDebuggingSession( - compileTimeSolution, + currentSolution, _debuggerService, PdbMatchingSourceTextProvider.Instance, reportDiagnostics: true); @@ -136,10 +132,10 @@ public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) try { Contract.ThrowIfNull(_debuggingSession); - var committedDesignTimeSolution = Interlocked.Exchange(ref _pendingUpdatedDesignTimeSolution, null); - Contract.ThrowIfNull(committedDesignTimeSolution); + var committedSolution = Interlocked.Exchange(ref _pendingUpdatedSolution, null); + Contract.ThrowIfNull(committedSolution); - _committedDesignTimeSolution = committedDesignTimeSolution; + _committedSolution = committedSolution; encService.CommitSolutionUpdate(_debuggingSession.Value); } @@ -163,7 +159,7 @@ public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) try { Contract.ThrowIfNull(_debuggingSession); - Contract.ThrowIfNull(Interlocked.Exchange(ref _pendingUpdatedDesignTimeSolution, null)); + Contract.ThrowIfNull(Interlocked.Exchange(ref _pendingUpdatedSolution, null)); encService.DiscardSolutionUpdate(_debuggingSession.Value); } @@ -187,8 +183,8 @@ public async ValueTask EndSessionAsync(CancellationToken cancellationToken) encService.EndDebuggingSession(_debuggingSession.Value); _debuggingSession = null; - _committedDesignTimeSolution = null; - _pendingUpdatedDesignTimeSolution = null; + _committedSolution = null; + _pendingUpdatedSolution = null; } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -218,10 +214,10 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio return false; } - Contract.ThrowIfNull(_committedDesignTimeSolution); - var oldSolution = _committedDesignTimeSolution; + Contract.ThrowIfNull(_committedSolution); + var oldSolution = _committedSolution; - var newSolution = await GetCurrentDesignTimeSolutionAsync(cancellationToken).ConfigureAwait(false); + var newSolution = await GetCurrentSolutionAsync(cancellationToken).ConfigureAwait(false); return (sourceFilePath != null) ? await EditSession.HasChangesAsync(oldSolution, newSolution, sourceFilePath, cancellationToken).ConfigureAwait(false) @@ -252,8 +248,7 @@ public async ValueTask GetUpdatesAsync(ImmutableArray (info.ProjectInstanceId.ProjectFilePath, info.ProjectInstanceId.TargetFramework, info.RestartAutomatically)); EmitSolutionUpdateResults.Data results; @@ -270,7 +265,7 @@ public async ValueTask GetUpdatesAsync(ImmutableArray