|
// 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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider;
internal abstract class SettingsProviderBase<TData, TOptionsUpdater, TOption, TValue> : ISettingsProvider<TData>
where TOptionsUpdater : ISettingUpdater<TOption, TValue>
{
private readonly List<TData> _snapshot = [];
private static readonly object s_gate = new();
private ISettingsEditorViewModel? _viewModel;
protected readonly string FileName;
protected readonly TOptionsUpdater SettingsUpdater;
protected readonly Workspace Workspace;
public readonly IGlobalOptionService GlobalOptions;
protected abstract void UpdateOptions(TieredAnalyzerConfigOptions options, ImmutableArray<Project> projectsInScope);
protected SettingsProviderBase(string fileName, TOptionsUpdater settingsUpdater, Workspace workspace, IGlobalOptionService globalOptions)
{
FileName = fileName;
SettingsUpdater = settingsUpdater;
Workspace = workspace;
GlobalOptions = globalOptions;
}
protected void Update()
{
var givenFolder = new DirectoryInfo(FileName).Parent;
if (givenFolder is null)
{
return;
}
var solution = Workspace.CurrentSolution;
var projects = solution.GetProjectsUnderEditorConfigFile(FileName);
var project = projects.FirstOrDefault();
if (project is null)
{
// no .NET projects in the solution
return;
}
var configFileDirectoryOptions = project.State.GetAnalyzerOptionsForPath(givenFolder.FullName, CancellationToken.None);
var projectDirectoryOptions = project.GetAnalyzerConfigOptions();
// TODO: Support for multiple languages https://github.com/dotnet/roslyn/issues/65859
var options = new TieredAnalyzerConfigOptions(
new CombinedAnalyzerConfigOptions(configFileDirectoryOptions, projectDirectoryOptions),
GlobalOptions,
language: LanguageNames.CSharp,
editorConfigFileName: FileName);
UpdateOptions(options, projects);
}
public async Task<SourceText> GetChangedEditorConfigAsync(SourceText sourceText)
{
if (!await SettingsUpdater.HasAnyChangesAsync().ConfigureAwait(false))
{
return sourceText;
}
var text = await SettingsUpdater.GetChangedEditorConfigAsync(sourceText, default).ConfigureAwait(false);
return text is not null ? text : sourceText;
}
public ImmutableArray<TData> GetCurrentDataSnapshot()
{
lock (s_gate)
{
return [.. _snapshot];
}
}
protected void AddRange(IEnumerable<TData> items)
{
lock (s_gate)
{
_snapshot.AddRange(items);
}
_viewModel?.NotifyOfUpdate();
}
public void RegisterViewModel(ISettingsEditorViewModel viewModel)
=> _viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
private sealed class CombinedAnalyzerConfigOptions(AnalyzerConfigData fileDirectoryConfigData, AnalyzerConfigData? projectDirectoryConfigData) : StructuredAnalyzerConfigOptions
{
private readonly AnalyzerConfigData _fileDirectoryConfigData = fileDirectoryConfigData;
private readonly AnalyzerConfigData? _projectDirectoryConfigData = projectDirectoryConfigData;
public override NamingStylePreferences GetNamingStylePreferences()
{
var preferences = _fileDirectoryConfigData.ConfigOptionsWithoutFallback.GetNamingStylePreferences();
if (preferences.IsEmpty && _projectDirectoryConfigData.HasValue)
{
preferences = _projectDirectoryConfigData.Value.ConfigOptionsWithoutFallback.GetNamingStylePreferences();
}
return preferences;
}
public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
if (_fileDirectoryConfigData.ConfigOptionsWithoutFallback.TryGetValue(key, out value))
{
return true;
}
if (!_projectDirectoryConfigData.HasValue)
{
value = null;
return false;
}
if (_projectDirectoryConfigData.Value.ConfigOptionsWithoutFallback.TryGetValue(key, out value))
{
return true;
}
var diagnosticKey = "dotnet_diagnostic.(?<key>.*).severity";
var match = Regex.Match(key, diagnosticKey);
if (match.Success && match.Groups["key"].Value is string isolatedKey &&
_projectDirectoryConfigData.Value.TreeOptions.TryGetValue(isolatedKey, out var severity))
{
value = severity.ToEditorConfigString();
return true;
}
value = null;
return false;
}
public override IEnumerable<string> Keys
{
get
{
foreach (var key in _fileDirectoryConfigData.ConfigOptionsWithoutFallback.Keys)
yield return key;
if (!_projectDirectoryConfigData.HasValue)
yield break;
foreach (var key in _projectDirectoryConfigData.Value.ConfigOptionsWithoutFallback.Keys)
{
if (!_fileDirectoryConfigData.ConfigOptionsWithoutFallback.TryGetValue(key, out _))
yield return key;
}
foreach (var (key, severity) in _projectDirectoryConfigData.Value.TreeOptions)
{
var diagnosticKey = "dotnet_diagnostic." + key + ".severity";
if (!_fileDirectoryConfigData.ConfigOptionsWithoutFallback.TryGetValue(diagnosticKey, out _) &&
!_projectDirectoryConfigData.Value.ConfigOptionsWithoutFallback.TryGetValue(diagnosticKey, out _))
{
yield return diagnosticKey;
}
}
}
}
}
}
|