File: Features\Options\SolutionAnalyzerConfigOptionsUpdater.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Collections;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Options;
 
/// <summary>
/// Keeps <see cref="Solution.FallbackAnalyzerOptions"/> up-to-date with global option values maintained by <see cref="IGlobalOptionService"/>.
/// </summary>
[Export]
[ExportEventListener(WellKnownEventListeners.Workspace,
    [WorkspaceKind.Host, WorkspaceKind.Interactive, WorkspaceKind.SemanticSearch, WorkspaceKind.MetadataAsSource, WorkspaceKind.MiscellaneousFiles, WorkspaceKind.Debugger, WorkspaceKind.Preview]), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class SolutionAnalyzerConfigOptionsUpdater(IGlobalOptionService globalOptions) : IEventListener<object>, IEventListenerStoppable
{
    public void StartListening(Workspace workspace, object serviceOpt)
        => globalOptions.AddOptionChangedHandler(workspace, GlobalOptionsChanged);
 
    public void StopListening(Workspace workspace)
        => globalOptions.RemoveOptionChangedHandler(workspace, GlobalOptionsChanged);
 
    private void GlobalOptionsChanged(object sender, object target, OptionChangedEventArgs args)
    {
        Debug.Assert(target is Workspace);
 
        try
        {
            // only editorconfig options are stored in Solution.FallbackAnalyzerOptions:
            if (!args.ChangedOptions.Any(static o => o.key.Option.Definition.IsEditorConfigOption))
            {
                return;
            }
 
            _ = ((Workspace)target).SetCurrentSolution(UpdateOptions, changeKind: WorkspaceChangeKind.SolutionChanged);
 
            Solution UpdateOptions(Solution oldSolution)
            {
                var oldFallbackOptions = oldSolution.FallbackAnalyzerOptions;
                var newFallbackOptions = oldFallbackOptions;
 
                foreach (var (language, languageOptions) in oldFallbackOptions)
                {
                    ImmutableDictionary<string, string>.Builder? lazyBuilder = null;
 
                    foreach (var (key, value) in args.ChangedOptions)
                    {
                        if (!key.Option.Definition.IsEditorConfigOption)
                        {
                            continue;
                        }
 
                        if (key.Language != null && key.Language != language)
                        {
                            continue;
                        }
 
                        if (lazyBuilder == null)
                        {
                            lazyBuilder = ImmutableDictionary.CreateBuilder<string, string>(AnalyzerConfigOptions.KeyComparer);
 
                            // copy existing option values:
                            foreach (var oldKey in languageOptions.Keys)
                            {
                                if (languageOptions.TryGetValue(oldKey, out var oldValue))
                                {
                                    lazyBuilder.Add(oldKey, oldValue);
                                }
                            }
                        }
 
                        // update changed values:
                        var configName = key.Option.Definition.ConfigName;
                        if (value is NamingStylePreferences preferences)
                        {
                            NamingStylePreferencesEditorConfigSerializer.WriteNamingStylePreferencesToEditorConfig(
                                preferences.SymbolSpecifications,
                                preferences.NamingStyles,
                                preferences.NamingRules,
                                language,
                                entryWriter: (name, value) => lazyBuilder[name] = value,
                                triviaWriter: null,
                                setPrioritiesToPreserveOrder: true);
                        }
                        else
                        {
                            lazyBuilder[configName] = key.Option.Definition.Serializer.Serialize(value);
                        }
                    }
 
                    if (lazyBuilder != null)
                    {
                        newFallbackOptions = newFallbackOptions.SetItem(
                            language,
                            StructuredAnalyzerConfigOptions.Create(new DictionaryAnalyzerConfigOptions(lazyBuilder.ToImmutable())));
                    }
                }
 
                return oldSolution.WithFallbackAnalyzerOptions(newFallbackOptions);
            }
        }
        catch (Exception e) when (FatalError.ReportAndPropagate(e, ErrorSeverity.Diagnostic))
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
}