diff --git a/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs b/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs new file mode 100644 index 0000000000000..8e801c0ddd4e7 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.TodoComments; +using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.CodeAnalysis.VisualBasic.Formatting; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting; + +[UseExportProvider] +public class RazorLineFormattingOptionsTests +{ + private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeatures; + + private class TestRazorDocumentServiceProvider : IDocumentServiceProvider + { + public TService? GetService() where TService : class, IDocumentService + => typeof(TService) == typeof(DocumentPropertiesService) ? (TService?)(object)new PropertiesService() : null; + + internal sealed class PropertiesService : DocumentPropertiesService + { + public override string? DiagnosticsLspClientName => "RazorCSharp"; + } + } + + [Fact] + public async Task FormatAsync() + { + var hostServices = s_composition.GetHostServices(); + + using var workspace = new AdhocWorkspace(hostServices); + + var globalOptions = ((IMefHostExportProvider)hostServices).GetExportedValue(); + globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.UseTabs), true); + globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.TabSize), 10); + + var project = workspace.AddProject("Test", LanguageNames.CSharp); + + var source = @" +class C + { +void F () {} + } +"; + + var documentInfo = DocumentInfo.Create( + DocumentId.CreateNewId(project.Id), + name: "file.razor.g.cs", + folders: Array.Empty(), + sourceCodeKind: SourceCodeKind.Regular, + loader: TextLoader.From(TextAndVersion.Create(SourceText.From(source), VersionStamp.Create(), "file.razor.g.cs")), + filePath: "file.razor.g.cs", + isGenerated: false, + designTimeOnly: true, + documentServiceProvider: new TestRazorDocumentServiceProvider()); + + var document = workspace.AddDocument(documentInfo); + +#pragma warning disable RS0030 // Do not used banned APIs + var formattedDocument = await Formatter.FormatAsync(document, spans: null, options: null, CancellationToken.None); +#pragma warning restore RS0030 // Do not used banned APIs + + var formattedText = await formattedDocument.GetTextAsync(); + + // document options override solution options: + AssertEx.Equal(@" +class C +{ +" + "\t" + @"void F() { } +} +", formattedText.ToString()); + } +} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 4b92cd5cc0efd..7635a0cf836f8 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -107,8 +107,6 @@ public TestWorkspace( _backgroundParser.Start(); _metadataAsSourceFileService = ExportProvider.GetExportedValues().FirstOrDefault(); - - RegisterDocumentOptionProviders(ExportProvider.GetExports()); } internal static TestComposition GetComposition(TestComposition? composition) diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs index eb6a3f9376d8d..59a78d60da334 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs @@ -18,7 +18,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.EditorConfig; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using static Microsoft.CodeAnalysis.CodeActions.CodeAction; diff --git a/src/Features/LanguageServer/Protocol/Features/Options/RazorLineFormattingOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/RazorLineFormattingOptionsStorage.cs index 3d52ba5c63ae5..0c1df2d99dc44 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/RazorLineFormattingOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/RazorLineFormattingOptionsStorage.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Formatting; /// /// Formatting options for Razor design-time documents. /// -internal class RazorLineFormattingOptionsStorage +internal static class RazorLineFormattingOptionsStorage { internal static readonly Option2 UseTabs = new( "RazorDesignTimeDocumentFormattingOptions", "UseTabs", LineFormattingOptions.Default.UseTabs); diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs index caa140bd3db5b..55ccbc21b45a8 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs @@ -84,11 +84,6 @@ public void SetOptions(OptionSet optionSet) throw new NotImplementedException(); } - public void RegisterDocumentOptionsProvider(IDocumentOptionsProvider documentOptionsProvider) - { - throw new NotImplementedException(); - } - public Task GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 5590cb6405044..cfb053d1c31cc 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -74,9 +74,7 @@ public async Task CreateAndAddToWorkspaceAsync( ? filePath : null; - // After the call to EnsureDocumentOptionProvidersInitializedAsync, everything can be off the UI thread. - // Thus, we have a ConfigureAwait(false) on the call and switch explicitly after. - await _visualStudioWorkspaceImpl.EnsureDocumentOptionProvidersInitializedAsync(cancellationToken).ConfigureAwait(false); + // Following can be off the UI thread. await TaskScheduler.Default; // From this point on, we start mutating the solution. So make us non cancellable. diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index ade67dd99e3a0..5f64ab6e50436 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -129,8 +129,6 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac internal FileWatchedPortableExecutableReferenceFactory FileWatchedReferenceFactory { get; } private readonly Lazy _projectCodeModelFactory; - private readonly IEnumerable> _documentOptionsProviderFactories; - private bool _documentOptionsProvidersInitialized = false; private readonly Lazy _lazyExternalErrorDiagnosticUpdateSource; private readonly IAsynchronousOperationListener _workspaceListener; @@ -145,7 +143,6 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro _textBufferFactoryService = exportProvider.GetExportedValue(); _projectionBufferFactoryService = exportProvider.GetExportedValue(); _projectCodeModelFactory = exportProvider.GetExport(); - _documentOptionsProviderFactories = exportProvider.GetExports(); // We fetch this lazily because VisualStudioProjectFactory depends on VisualStudioWorkspaceImpl -- we have a circularity. Since this // exists right now as a compat shim, we'll just do this. @@ -2039,23 +2036,6 @@ await ApplyBatchChangeToWorkspaceAsync(solutionChanges => }); } - internal async Task EnsureDocumentOptionProvidersInitializedAsync(CancellationToken cancellationToken) - { - // HACK: switch to the UI thread, ensure we initialize our options provider which depends on a - // UI-affinitized experimentation service - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - _foregroundObject.AssertIsForeground(); - - if (_documentOptionsProvidersInitialized) - { - return; - } - - _documentOptionsProvidersInitialized = true; - RegisterDocumentOptionProviders(_documentOptionsProviderFactories); - } - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54137", AllowLocks = false)] internal void SetMaxLanguageVersion(ProjectId projectId, string? maxLanguageVersion) { diff --git a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs index 1dba460027b2a..4c3de113c72fa 100644 --- a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs @@ -2,9 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Options { @@ -15,28 +20,79 @@ namespace Microsoft.CodeAnalysis.Options /// public sealed class DocumentOptionSet : OptionSet { - private readonly OptionSet _backingOptionSet; + private readonly OptionSet _underlyingOptions; + private readonly StructuredAnalyzerConfigOptions? _configOptions; + private ImmutableDictionary _values; private readonly string _language; - internal DocumentOptionSet(OptionSet backingOptionSet, string language) + internal DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language) + : this(configOptions, underlyingOptions, language, ImmutableDictionary.Empty) + { + } + + private DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language, ImmutableDictionary values) { - _backingOptionSet = backingOptionSet; _language = language; + _configOptions = configOptions; + _underlyingOptions = underlyingOptions; + _values = values; } internal string Language => _language; + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)] private protected override object? GetOptionCore(OptionKey optionKey) - => _backingOptionSet.GetOption(optionKey); + { + // If we already know the document specific value, we're done + if (_values.TryGetValue(optionKey, out var value)) + { + return value; + } + + if (TryGetAnalyzerConfigOption(optionKey, out value)) + { + // Cache and return + return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value); + } + + // We don't have a document specific value, so forward + return _underlyingOptions.GetOption(optionKey); + } + + private bool TryGetAnalyzerConfigOption(OptionKey option, out object? value) + { + if (_configOptions == null) + { + value = null; + return false; + } + + var editorConfigPersistence = (IEditorConfigStorageLocation?)option.Option.StorageLocations.SingleOrDefault(static location => location is IEditorConfigStorageLocation); + if (editorConfigPersistence == null) + { + value = null; + return false; + } + + try + { + return editorConfigPersistence.TryGetOption(_configOptions, option.Option.Type, out value); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + value = null; + return false; + } + } public T GetOption(PerLanguageOption option) - => _backingOptionSet.GetOption(option, _language); + => GetOption(option, _language); internal T GetOption(PerLanguageOption2 option) - => _backingOptionSet.GetOption(option, _language); + => GetOption(option, _language); public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) - => new DocumentOptionSet(_backingOptionSet.WithChangedOption(optionAndLanguage, value), _language); + => new DocumentOptionSet(_configOptions, _underlyingOptions, _language, _values.SetItem(optionAndLanguage, value)); /// /// Creates a new that contains the changed value. @@ -53,10 +109,10 @@ internal DocumentOptionSet WithChangedOption(PerLanguageOption2 option, T private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language) { Debug.Assert((language ?? _language) == _language, $"Use of a {nameof(DocumentOptionSet)} is not expected to differ from the language it was constructed with."); - return _backingOptionSet.AsAnalyzerConfigOptions(optionService, language ?? _language); + return base.CreateAnalyzerConfigOptions(optionService, language ?? _language); } internal override IEnumerable GetChangedOptions(OptionSet optionSet) - => _backingOptionSet.GetChangedOptions(optionSet); + => GetChangedOptions(optionSet); } } diff --git a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs deleted file mode 100644 index 84af82e9c2268..0000000000000 --- a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Diagnostics; -using static Microsoft.CodeAnalysis.ProjectState; - -namespace Microsoft.CodeAnalysis.Options.EditorConfig -{ - internal static class EditorConfigDocumentOptionsProviderFactory - { - public static IDocumentOptionsProvider Create() - => new EditorConfigDocumentOptionsProvider(); - - private sealed class EditorConfigDocumentOptionsProvider : IDocumentOptionsProvider - { - public async Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken) - { - var provider = (ProjectAnalyzerConfigOptionsProvider)document.Project.State.AnalyzerOptions.AnalyzerConfigOptionsProvider; - var options = await provider.GetOptionsAsync(document.DocumentState, cancellationToken).ConfigureAwait(false); - return new DocumentOptions(options); - } - - private sealed class DocumentOptions : IDocumentOptions - { - private readonly StructuredAnalyzerConfigOptions? _options; - - public DocumentOptions(StructuredAnalyzerConfigOptions? options) - => _options = options; - - public bool TryGetDocumentOption(OptionKey option, out object? value) - { - if (_options == null) - { - value = null; - return false; - } - - var editorConfigPersistence = (IEditorConfigStorageLocation?)option.Option.StorageLocations.SingleOrDefault(static location => location is IEditorConfigStorageLocation); - if (editorConfigPersistence == null) - { - value = null; - return false; - } - - try - { - return editorConfigPersistence.TryGetOption(_options, option.Option.Type, out value); - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - value = null; - return false; - } - } - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Options/IDocumentOptions.cs b/src/Workspaces/Core/Portable/Options/IDocumentOptions.cs deleted file mode 100644 index f588607f5630f..0000000000000 --- a/src/Workspaces/Core/Portable/Options/IDocumentOptions.cs +++ /dev/null @@ -1,20 +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.Options -{ - /// - /// Returned from a - /// - internal interface IDocumentOptions - { - /// - /// Attempts to fetch the value for the given option. - /// - /// - /// The value returned. May be null even if the function returns true as "null" may be valid value for some options. - /// True if this provider had a specific value for this option. False to indicate other providers should be queried. - bool TryGetDocumentOption(OptionKey option, out object? value); - } -} diff --git a/src/Workspaces/Core/Portable/Options/IDocumentOptionsProvider.cs b/src/Workspaces/Core/Portable/Options/IDocumentOptionsProvider.cs deleted file mode 100644 index 848ed4a711a9e..0000000000000 --- a/src/Workspaces/Core/Portable/Options/IDocumentOptionsProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.Options -{ - /// - /// Implemented to provide options that apply to specific documents, like from .editorconfig files. - /// - /// - /// This is passed to to activate it - /// for a workspace. This instance then lives around for the lifetime of the workspace. - /// - internal interface IDocumentOptionsProvider - { - /// - /// Fetches a for the given document. Any asynchronous work (looking for config files, etc.) - /// should be done here. Can return a null-valued task to mean there is no options being provided for this document. - /// - Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken); - } -} diff --git a/src/Workspaces/Core/Portable/Options/IDocumentOptionsProviderFactory.cs b/src/Workspaces/Core/Portable/Options/IDocumentOptionsProviderFactory.cs deleted file mode 100644 index f4409171b43b3..0000000000000 --- a/src/Workspaces/Core/Portable/Options/IDocumentOptionsProviderFactory.cs +++ /dev/null @@ -1,14 +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.Options -{ - /// - /// A MEF-exported factory which produces s for s. - /// - internal interface IDocumentOptionsProviderFactory - { - IDocumentOptionsProvider? TryCreate(Workspace workspace); - } -} diff --git a/src/Workspaces/Core/Portable/Options/IOptionService.cs b/src/Workspaces/Core/Portable/Options/IOptionService.cs index 406208e9ef36c..c3ac1c6ba1445 100644 --- a/src/Workspaces/Core/Portable/Options/IOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/IOptionService.cs @@ -77,17 +77,6 @@ internal interface IOptionService : IWorkspaceService event EventHandler OptionChanged; - /// - /// Registers a provider that can modify the result of . Providers registered earlier are queried first - /// for options, and the first provider to give a value wins. - /// - void RegisterDocumentOptionsProvider(IDocumentOptionsProvider documentOptionsProvider); - - /// - /// Returns the that applies to a specific document, given that document and the global options. - /// - Task GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken); - /// /// Registers a workspace with the option service. /// diff --git a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs index 00f647d565845..8b93b77a5122c 100644 --- a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs @@ -7,8 +7,11 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; @@ -41,16 +44,13 @@ internal sealed class OptionService : IWorkspaceOptionService private readonly TaskQueue _taskQueue; /// - /// Gate guarding and . + /// Gate guarding . /// private readonly object _gate = new(); private ImmutableArray> _eventHandlers = ImmutableArray>.Empty; - private ImmutableArray _documentOptionsProviders = - ImmutableArray.Empty; - public OptionService( IGlobalOptionService globalOptionService, HostWorkspaceServices workspaceServices) @@ -126,96 +126,6 @@ public event EventHandler OptionChanged public void SetOptions(OptionSet optionSet) => _globalOptionService.SetOptions(optionSet); public void RegisterWorkspace(Workspace workspace) => _globalOptionService.RegisterWorkspace(workspace); public void UnregisterWorkspace(Workspace workspace) => _globalOptionService.UnregisterWorkspace(workspace); - - public void RegisterDocumentOptionsProvider(IDocumentOptionsProvider documentOptionsProvider) - { - if (documentOptionsProvider == null) - { - throw new ArgumentNullException(nameof(documentOptionsProvider)); - } - - lock (_gate) - { - _documentOptionsProviders = _documentOptionsProviders.Add(documentOptionsProvider); - } - } - - public async Task GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken) - { - ImmutableArray documentOptionsProviders; - - lock (_gate) - { - documentOptionsProviders = _documentOptionsProviders; - } - - var realizedDocumentOptions = new List(); - - foreach (var provider in documentOptionsProviders) - { - cancellationToken.ThrowIfCancellationRequested(); - - var documentOption = await provider.GetOptionsForDocumentAsync(document, cancellationToken).ConfigureAwait(false); - - if (documentOption != null) - { - realizedDocumentOptions.Add(documentOption); - } - } - - return new DocumentSpecificOptionSet(realizedDocumentOptions, optionSet); - } - - private class DocumentSpecificOptionSet : OptionSet - { - private readonly OptionSet _underlyingOptions; - private readonly List _documentOptions; - private ImmutableDictionary _values; - - public DocumentSpecificOptionSet(List documentOptions, OptionSet underlyingOptions) - : this(documentOptions, underlyingOptions, ImmutableDictionary.Empty) - { - } - - public DocumentSpecificOptionSet(List documentOptions, OptionSet underlyingOptions, ImmutableDictionary values) - { - _documentOptions = documentOptions; - _underlyingOptions = underlyingOptions; - _values = values; - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)] - private protected override object? GetOptionCore(OptionKey optionKey) - { - // If we already know the document specific value, we're done - if (_values.TryGetValue(optionKey, out var value)) - { - return value; - } - - foreach (var documentOptionSource in _documentOptions) - { - if (documentOptionSource.TryGetDocumentOption(optionKey, out value)) - { - // Cache and return - return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value); - } - } - - // We don't have a document specific value, so forward - return _underlyingOptions.GetOption(optionKey); - } - - public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) - => new DocumentSpecificOptionSet(_documentOptions, _underlyingOptions, _values.SetItem(optionAndLanguage, value)); - - internal override IEnumerable GetChangedOptions(OptionSet optionSet) - { - // GetChangedOptions only needs to be supported for OptionSets that need to be compared during application, - // but that's already enforced it must be a full SerializableOptionSet. - throw new NotSupportedException(); - } - } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 27f7c17b2f5fc..a74f2087c6dd1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -505,9 +505,10 @@ private void InitializeCachedOptions(OptionSet solutionOptions) { var newAsyncLazy = new AsyncLazy(async c => { - var optionsService = Project.Solution.Workspace.Services.GetRequiredService(); - var documentOptionSet = await optionsService.GetUpdatedOptionSetForDocumentAsync(this, solutionOptions, c).ConfigureAwait(false); - return new DocumentOptionSet(documentOptionSet, Project.Language); + var provider = (ProjectState.ProjectAnalyzerConfigOptionsProvider)Project.State.AnalyzerOptions.AnalyzerConfigOptionsProvider; + var options = await provider.GetOptionsAsync(DocumentState, c).ConfigureAwait(false); + + return new DocumentOptionSet(options, solutionOptions, Project.Language); }, cacheResult: true); Interlocked.CompareExchange(ref _cachedOptions, newAsyncLazy, comparand: null); diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 63bcc3a31c59e..488e6a3358715 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.EditorConfig; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -92,8 +91,6 @@ protected Workspace(HostServices host, string? workspaceKind) _optionService, ImmutableDictionary.Empty, changedOptionKeysSerializable: ImmutableHashSet.Empty); _latestSolution = CreateSolution(info, emptyOptions, analyzerReferences: SpecializedCollections.EmptyReadOnlyList()); - - _optionService.RegisterDocumentOptionsProvider(EditorConfigDocumentOptionsProviderFactory.Create()); } internal void LogTestMessage(string message) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index 9f87b6a29a146..f5a3e3cfc6698 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -776,18 +776,5 @@ protected virtual Project AdjustReloadedProject(Project oldProject, Project relo return newSolution.GetRequiredProject(oldProject.Id); } - - internal void RegisterDocumentOptionProviders(IEnumerable> documentOptionsProviderFactories) - { - foreach (var providerFactory in ExtensionOrderer.Order(documentOptionsProviderFactories)) - { - var optionsProvider = providerFactory.Value.TryCreate(this); - - if (optionsProvider != null) - { - Services.GetRequiredService().RegisterDocumentOptionsProvider(optionsProvider); - } - } - } } } diff --git a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs index 71f708bb0aa06..f636e48dd2b09 100644 --- a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs +++ b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs @@ -43,43 +43,6 @@ public Task FormatAsync(Document document, IEnumerable? span => Task.FromResult(document.WithText(SourceText.From($"Formatted with options: {lineFormattingOptions.ToString().Replace("\r", "\\r").Replace("\n", "\\n")}"))); } - [Export(typeof(IDocumentOptionsProviderFactory)), Shared, PartNotDiscoverable] - internal class TestDocumentOptionsProviderFactory : IDocumentOptionsProviderFactory - { - public readonly Dictionary Options = new(); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentOptionsProviderFactory() - { - } - - public IDocumentOptionsProvider? TryCreate(Workspace workspace) - => new Provider(new DocumentOptions(Options)); - - private class Provider : IDocumentOptionsProvider - { - private readonly DocumentOptions _options; - - public Provider(DocumentOptions options) - => _options = options; - - public Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken) - => Task.FromResult(_options); - } - - public class DocumentOptions : IDocumentOptions - { - private readonly Dictionary _options; - - public DocumentOptions(Dictionary options) - => _options = options; - - public bool TryGetDocumentOption(OptionKey option, out object? value) - => _options.TryGetValue(option, out value); - } - } - [Fact] public async Task FormatAsync_ForeignLanguageWithFormattingSupport() { @@ -101,16 +64,10 @@ public async Task FormatAsync_ForeignLanguageWithFormattingSupport() [CombinatorialData] public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool passExplicitOptions) { - var hostServices = s_composition.AddParts(new[] { typeof(NoCompilationLanguageServiceFactory), typeof(TestFormattingService), typeof(TestDocumentOptionsProviderFactory) }).GetHostServices(); + var hostServices = s_composition.AddParts(new[] { typeof(NoCompilationLanguageServiceFactory), typeof(TestFormattingService) }).GetHostServices(); using var workspace = new AdhocWorkspace(hostServices); - // register custom document options provider (Razor scenario) - var documentOptionsFactory = (TestDocumentOptionsProviderFactory)((IMefHostExportProvider)hostServices).GetExportedValue(); - documentOptionsFactory.Options.Add(new OptionKey(FormattingOptions.IndentationSize, NoCompilationConstants.LanguageName), 10); - var provider = documentOptionsFactory.TryCreate(workspace)!; - workspace.Services.GetRequiredService().RegisterDocumentOptionsProvider(provider); - var project = workspace.AddProject("Dummy", NoCompilationConstants.LanguageName); var document = workspace.AddDocument(project.Id, "File.dummy", SourceText.From("dummy")); @@ -123,7 +80,7 @@ public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool document = document.Project.Solution.WithOptions(solutionOptions).GetRequiredDocument(document.Id); var documentOptions = await document.GetOptionsAsync(); - Assert.Equal(10, documentOptions.GetOption(FormattingOptions.IndentationSize)); + Assert.Equal(7, documentOptions.GetOption(FormattingOptions.IndentationSize)); var options = passExplicitOptions ? new OptionValueSet(ImmutableDictionary.Empty. Add(new OptionKey(FormattingOptions.UseTabs, NoCompilationConstants.LanguageName), true). @@ -145,7 +102,7 @@ public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool else { // document options override solution options: - AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = False, TabSize = 1, IndentationSize = 10, NewLine = \n }", formattedText.ToString()); + AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = False, TabSize = 1, IndentationSize = 7, NewLine = \n }", formattedText.ToString()); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 6187276342eb7..6b401c5561d3d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -56,8 +56,6 @@ internal sealed partial class RemoteWorkspace : Workspace internal RemoteWorkspace(HostServices hostServices, string? workspaceKind) : base(hostServices, workspaceKind) { - var exportProvider = (IMefHostExportProvider)Services.HostServices; - RegisterDocumentOptionProviders(exportProvider.GetExports()); } protected override void Dispose(bool finalize)