未验证 提交 d98da604 编写于 作者: S Sam Harwell 提交者: GitHub

Merge pull request #41871 from sharwell/as-analyzer-options

Add OptionSet.AsAnalyzerConfigOptions
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal sealed partial class AnalyzerConfigOptionSet
{
private sealed class AnalyzerConfigOptionsImpl : AnalyzerConfigOptions
{
private readonly AnalyzerConfigOptions _options;
private readonly AnalyzerConfigOptions _fallbackOptions;
public AnalyzerConfigOptionsImpl(AnalyzerConfigOptions options, AnalyzerConfigOptions fallbackOptions)
{
_options = options;
_fallbackOptions = fallbackOptions;
}
public override bool TryGetValue(string key, out string value)
{
if (_options.TryGetValue(key, out value))
{
return true;
}
return _fallbackOptions.TryGetValue(key, out value);
}
}
}
}
......@@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
/// <summary>
/// This class proxies requests for option values first to the <see cref="AnalyzerConfigOptions" /> then to a backup <see cref="OptionSet" /> if provided.
/// </summary>
internal sealed class AnalyzerConfigOptionSet : OptionSet
internal sealed partial class AnalyzerConfigOptionSet : OptionSet
{
private readonly AnalyzerConfigOptions _analyzerConfigOptions;
private readonly OptionSet? _optionSet;
......@@ -39,6 +39,16 @@ public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object?
throw new NotImplementedException();
}
private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language)
{
if (_optionSet is null)
{
return _analyzerConfigOptions;
}
return new AnalyzerConfigOptionsImpl(_analyzerConfigOptions, _optionSet.AsAnalyzerConfigOptions(optionService, language));
}
internal override IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet)
{
throw new NotImplementedException();
......
......@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
......@@ -55,6 +56,11 @@ public IEnumerable<IOption> GetRegisteredOptions()
throw new NotImplementedException();
}
public bool TryMapEditorConfigKeyToOption(string key, string language, [NotNullWhen(true)] out IEditorConfigStorageLocation2 storageLocation, out OptionKey optionKey)
{
throw new NotImplementedException();
}
public ImmutableHashSet<IOption> GetRegisteredSerializableOptions(ImmutableHashSet<string> languages)
{
throw new NotImplementedException();
......
......@@ -303,7 +303,7 @@ private void FormatDocumentCreatedFromTemplate(IVsHierarchy hierarchy, uint item
var formattedText = addedDocument.GetTextSynchronously(cancellationToken).WithChanges(formattedTextChanges);
// Ensure the line endings are normalized. The formatter doesn't touch everything if it doesn't need to.
var targetLineEnding = documentOptions.GetOption(FormattingOptions.NewLine);
var targetLineEnding = documentOptions.GetOption(FormattingOptions.NewLine)!;
var originalText = formattedText;
foreach (var originalLine in originalText.Lines)
......
......@@ -2,7 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Options
{
......@@ -22,17 +27,19 @@ internal DocumentOptionSet(OptionSet backingOptionSet, string language)
_language = language;
}
[return: MaybeNull]
public override object GetOption(OptionKey optionKey)
{
return _backingOptionSet.GetOption(optionKey);
}
[return: MaybeNull]
public T GetOption<T>(PerLanguageOption<T> option)
{
return _backingOptionSet.GetOption(option, _language);
}
public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object value)
public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value)
{
return new DocumentOptionSet(_backingOptionSet.WithChangedOption(optionAndLanguage, value), _language);
}
......@@ -45,6 +52,12 @@ public DocumentOptionSet WithChangedOption<T>(PerLanguageOption<T> option, T val
return (DocumentOptionSet)WithChangedOption(option, _language, value);
}
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);
}
internal override IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet)
{
return _backingOptionSet.GetChangedOptions(optionSet);
......
......@@ -10,6 +10,8 @@ namespace Microsoft.CodeAnalysis.Options
{
internal interface IEditorConfigStorageLocation2 : IEditorConfigStorageLocation
{
string KeyName { get; }
/// <summary>
/// Gets the editorconfig string representation for this storage location.
/// </summary>
......
......@@ -10,6 +10,7 @@
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -21,6 +22,9 @@ namespace Microsoft.CodeAnalysis.Options
[Export(typeof(IGlobalOptionService)), Shared]
internal class GlobalOptionService : IGlobalOptionService
{
private static readonly ImmutableDictionary<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)> s_emptyEditorConfigKeysToOptions
= ImmutableDictionary.Create<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)>(AnalyzerConfigOptions.KeyComparer);
private readonly Lazy<ImmutableHashSet<IOption>> _lazyAllOptions;
private readonly ImmutableArray<Lazy<IOptionPersister>> _optionSerializers;
private readonly ImmutableDictionary<string, Lazy<ImmutableHashSet<IOption>>> _serializableOptionsByLanguage;
......@@ -28,6 +32,10 @@ internal class GlobalOptionService : IGlobalOptionService
private readonly object _gate = new object();
private ImmutableDictionary<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)> _neutralEditorConfigKeysToOptions = s_emptyEditorConfigKeysToOptions;
private ImmutableDictionary<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)> _csharpEditorConfigKeysToOptions = s_emptyEditorConfigKeysToOptions;
private ImmutableDictionary<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)> _visualBasicEditorConfigKeysToOptions = s_emptyEditorConfigKeysToOptions;
private ImmutableDictionary<OptionKey, object?> _currentValues;
private ImmutableArray<Workspace> _registeredWorkspaces;
......@@ -99,6 +107,70 @@ public IEnumerable<IOption> GetRegisteredOptions()
return _lazyAllOptions.Value;
}
public bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey)
{
ImmutableDictionary<string, (IOption? option, IEditorConfigStorageLocation2? storageLocation)> temporaryOptions = s_emptyEditorConfigKeysToOptions;
ref var editorConfigToOptionsStorage = ref temporaryOptions;
switch (language)
{
case LanguageNames.CSharp:
// Suppression required due to https://github.com/dotnet/roslyn/issues/42018
editorConfigToOptionsStorage = ref _csharpEditorConfigKeysToOptions!;
break;
case LanguageNames.VisualBasic:
// Suppression required due to https://github.com/dotnet/roslyn/issues/42018
editorConfigToOptionsStorage = ref _visualBasicEditorConfigKeysToOptions!;
break;
case null:
case "":
// Suppression required due to https://github.com/dotnet/roslyn/issues/42018
editorConfigToOptionsStorage = ref _neutralEditorConfigKeysToOptions!;
break;
}
var (option, storage) = ImmutableInterlocked.GetOrAdd(
ref editorConfigToOptionsStorage,
key,
(key, arg) => MapToOptionIgnorePerLanguage(arg.self, key, arg.language),
(self: this, language));
if (option is object)
{
RoslynDebug.AssertNotNull(storage);
storageLocation = storage;
optionKey = option.IsPerLanguage ? new OptionKey(option, language) : new OptionKey(option);
return true;
}
storageLocation = null;
optionKey = default;
return false;
// Local function
static (IOption? option, IEditorConfigStorageLocation2? storageLocation) MapToOptionIgnorePerLanguage(GlobalOptionService service, string key, string? language)
{
// Use GetRegisteredSerializableOptions instead of GetRegisteredOptions to avoid loading assemblies for
// inactive languages.
foreach (var option in service.GetRegisteredSerializableOptions(ImmutableHashSet.Create(language ?? "")))
{
foreach (var storage in option.StorageLocations)
{
if (!(storage is IEditorConfigStorageLocation2 editorConfigStorage))
continue;
if (!AnalyzerConfigOptions.KeyComparer.Equals(key, editorConfigStorage.KeyName))
continue;
return (option, editorConfigStorage);
}
}
return (null, null);
}
}
public ImmutableHashSet<IOption> GetRegisteredSerializableOptions(ImmutableHashSet<string> languages)
{
if (languages.IsEmpty)
......
......@@ -53,6 +53,18 @@ internal interface IGlobalOptionService
/// </summary>
IEnumerable<IOption> GetRegisteredOptions();
/// <summary>
/// Map an <strong>.editorconfig</strong> key to a corresponding <see cref="IEditorConfigStorageLocation2"/> and
/// <see cref="OptionKey"/> that can be used to read and write the value stored in an <see cref="OptionSet"/>.
/// </summary>
/// <param name="key">The <strong>.editorconfig</strong> key.</param>
/// <param name="language">The language to use for the <paramref name="optionKey"/>, if the matching option has
/// <see cref="IOption.IsPerLanguage"/> set.</param>
/// <param name="storageLocation">The <see cref="IEditorConfigStorageLocation2"/> for the key.</param>
/// <param name="optionKey">The <see cref="OptionKey"/> for the key and language.</param>
/// <returns><see langword="true"/> if a matching option was found; otherwise, <see langword="false"/>.</returns>
bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey);
/// <summary>
/// Returns the set of all registered serializable options applicable for the given <paramref name="languages"/>.
/// </summary>
......
......@@ -61,6 +61,9 @@ internal interface IOptionService : IWorkspaceService
/// </summary>
IEnumerable<IOption> GetRegisteredOptions();
/// <inheritdoc cref="IGlobalOptionService.TryMapEditorConfigKeyToOption"/>
bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey);
/// <summary>
/// Returns the set of all registered serializable options applicable for the given <paramref name="languages"/>.
/// </summary>
......
......@@ -123,6 +123,7 @@ private ImmutableArray<EventHandler<OptionChangedEventArgs>> GetEventHandlers()
[return: MaybeNull] public T GetOption<T>(Option<T> option) => _globalOptionService.GetOption(option);
[return: MaybeNull] public T GetOption<T>(PerLanguageOption<T> option, string? languageName) => _globalOptionService.GetOption(option, languageName);
public IEnumerable<IOption> GetRegisteredOptions() => _globalOptionService.GetRegisteredOptions();
public bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey) => _globalOptionService.TryMapEditorConfigKeyToOption(key, language, out storageLocation, out optionKey);
public ImmutableHashSet<IOption> GetRegisteredSerializableOptions(ImmutableHashSet<string> languages) => _globalOptionService.GetRegisteredSerializableOptions(languages);
public void SetOptions(OptionSet optionSet) => _globalOptionService.SetOptions(optionSet);
public void RegisterWorkspace(Workspace workspace) => _globalOptionService.RegisterWorkspace(workspace);
......
// 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.
#nullable enable
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Options
{
public abstract partial class OptionSet
{
private sealed class AnalyzerConfigOptionsImpl : AnalyzerConfigOptions
{
private readonly OptionSet _optionSet;
private readonly IOptionService _optionService;
private readonly string? _language;
public AnalyzerConfigOptionsImpl(OptionSet optionSet, IOptionService optionService, string? language)
{
_optionSet = optionSet;
_optionService = optionService;
_language = language;
}
public override bool TryGetValue(string key, out string? value)
{
if (!_optionService.TryMapEditorConfigKeyToOption(key, _language, out var storageLocation, out var optionKey))
{
value = null;
return false;
}
var typedValue = _optionSet.GetOption(optionKey);
value = storageLocation.GetEditorConfigString(typedValue, _optionSet);
return true;
}
}
}
}
......@@ -4,13 +4,25 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Options
{
public abstract class OptionSet
public abstract partial class OptionSet
{
private const string NoLanguageSentinel = "\0";
private static readonly ImmutableDictionary<string, AnalyzerConfigOptions> s_emptyAnalyzerConfigOptions =
ImmutableDictionary.Create<string, AnalyzerConfigOptions>(StringComparer.Ordinal);
/// <summary>
/// Map from language name to the <see cref="AnalyzerConfigOptions"/> wrapper.
/// </summary>
private ImmutableDictionary<string, AnalyzerConfigOptions> _lazyAnalyzerConfigOptions = s_emptyAnalyzerConfigOptions;
/// <summary>
/// Gets the value of the option.
/// </summary>
......@@ -55,6 +67,20 @@ public OptionSet WithChangedOption<T>(PerLanguageOption<T> option, string? langu
return WithChangedOption(new OptionKey(option, language), value);
}
internal AnalyzerConfigOptions AsAnalyzerConfigOptions(IOptionService optionService, string? language)
{
return ImmutableInterlocked.GetOrAdd(
ref _lazyAnalyzerConfigOptions,
language ?? NoLanguageSentinel,
(string language, (OptionSet self, IOptionService optionService) arg) => arg.self.CreateAnalyzerConfigOptions(arg.optionService, (object)language == NoLanguageSentinel ? null : language),
(this, optionService));
}
internal abstract IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet);
private protected virtual AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language)
{
return new AnalyzerConfigOptionsImpl(this, optionService, language);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册