提交 d5b1768c 编写于 作者: J Jonathon Marolf 提交者: GitHub

Merge pull request #16510 from jmarolf/bugfix/naming-styles-editorconfig-override

Append editorconfig namingstyle options to the top of the workspace o…
......@@ -20,7 +20,7 @@ public DocumentOptions(ICodingConventionsSnapshot codingConventionSnapshot, IErr
_errorLogger = errorLogger;
}
public bool TryGetDocumentOption(Document document, OptionKey option, out object value)
public bool TryGetDocumentOption(Document document, OptionKey option, OptionSet underlyingOptions, out object value)
{
var editorConfigPersistence = option.Option.StorageLocations.OfType<EditorConfigStorageLocation>().SingleOrDefault();
if (editorConfigPersistence == null)
......@@ -32,7 +32,8 @@ public bool TryGetDocumentOption(Document document, OptionKey option, out object
var allRawConventions = _codingConventionSnapshot.AllRawConventions;
try
{
return editorConfigPersistence.TryParseReadonlyDictionary(allRawConventions, option.Option.Type, out value);
var underlyingOption = underlyingOptions.GetOption(option);
return editorConfigPersistence.TryGetOption(underlyingOption, allRawConventions, option.Option.Type, out value);
}
catch (Exception ex)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
{
internal static class NamingStylePreferencesExtensions
{
public static NamingStylePreferences PrependNamingStylePreferences(this NamingStylePreferences original, NamingStylePreferences newPreferences)
{
var symbolSpecifications = original.SymbolSpecifications.InsertRange(0, newPreferences.SymbolSpecifications);
var namingStyles = original.NamingStyles.InsertRange(0, newPreferences.NamingStyles);
var namingRules = original.NamingRules.InsertRange(0, newPreferences.NamingRules);
return new NamingStylePreferences(symbolSpecifications, namingStyles, namingRules);
}
}
}
......@@ -41,21 +41,32 @@ internal sealed class EditorConfigStorageLocation : OptionStorageLocation
}
};
private Func<IReadOnlyDictionary<string, object>, Type, (object result, bool succeeded)> _tryParseDictionary;
private Func<object, IReadOnlyDictionary<string, object>, Type, (object result, bool succeeded)> _tryParseDictionary;
private static Func<IReadOnlyDictionary<string, object>, Type, (object result, bool succeeded)> _cachedTryParseDictionary = (dictionary, type) =>
private static Func<object, IReadOnlyDictionary<string, object>, Type, (object result, bool succeeded)> _cachedTryParseDictionary = (underlyingOption, dictionary, type) =>
{
if (type == typeof(NamingStylePreferences))
{
var result = EditorConfigNamingStyleParser.GetNamingStylesFromDictionary(dictionary);
if (!result.NamingRules.Any() &&
!result.NamingStyles.Any() &&
!result.SymbolSpecifications.Any())
var editorconfigNamingStylePreferences = EditorConfigNamingStyleParser.GetNamingStylesFromDictionary(dictionary);
if (!editorconfigNamingStylePreferences.NamingRules.Any() &&
!editorconfigNamingStylePreferences.NamingStyles.Any() &&
!editorconfigNamingStylePreferences.SymbolSpecifications.Any())
{
// We were not able to parse any rules from editorconfig, tell the caller that the parse failed
return (result: editorconfigNamingStylePreferences, succeeded: false);
}
var workspaceNamingStylePreferences = underlyingOption as NamingStylePreferences;
if (workspaceNamingStylePreferences != null)
{
return (result: result, succeeded: false);
// We parsed naming styles from editorconfig, append them to our existing styles
var combinedNamingStylePreferences = workspaceNamingStylePreferences.PrependNamingStylePreferences(editorconfigNamingStylePreferences);
return (result: (object)combinedNamingStylePreferences, succeeded: true);
}
return (result: result, succeeded: true);
// no existing naming styles were passed so just return the set of styles that were parsed from editorconfig
return (result: editorconfigNamingStylePreferences, succeeded: true);
}
else
{
......@@ -63,7 +74,7 @@ internal sealed class EditorConfigStorageLocation : OptionStorageLocation
}
};
public bool TryParseReadonlyDictionary(IReadOnlyDictionary<string, object> allRawConventions, Type type, out object result)
public bool TryGetOption(object underlyingOption, IReadOnlyDictionary<string, object> allRawConventions, Type type, out object result)
{
if (_parseValue != null && KeyName != null)
{
......@@ -75,7 +86,7 @@ public bool TryParseReadonlyDictionary(IReadOnlyDictionary<string, object> allRa
}
else if (_tryParseDictionary != null)
{
var tuple = _tryParseDictionary(allRawConventions, type);
var tuple = _tryParseDictionary(underlyingOption, allRawConventions, type);
result = tuple.result;
return tuple.succeeded;
}
......@@ -104,10 +115,10 @@ public EditorConfigStorageLocation()
_tryParseDictionary = _cachedTryParseDictionary;
}
public EditorConfigStorageLocation(Func<IReadOnlyDictionary<string, object>, (object result, bool succeeded)> tryParseDictionary)
public EditorConfigStorageLocation(Func<object, IReadOnlyDictionary<string, object>, (object result, bool succeeded)> tryParseDictionary)
{
// If we're explicitly given a parsing function we can throw away the type when parsing
_tryParseDictionary = (dictionary, type) => tryParseDictionary(dictionary);
_tryParseDictionary = (underlyingOption, dictionary, type) => tryParseDictionary(underlyingOption, dictionary);
}
}
}
......@@ -11,6 +11,6 @@ namespace Microsoft.CodeAnalysis.Options
/// </summary>
interface IDocumentOptions
{
bool TryGetDocumentOption(Document document, OptionKey option, out object value);
bool TryGetDocumentOption(Document document, OptionKey option, OptionSet underlyingOptions, out object value);
}
}
......@@ -188,7 +188,7 @@ public override object GetOption(OptionKey optionKey)
foreach (var documentOptionSource in _documentOptions)
{
if (documentOptionSource.TryGetDocumentOption(_document, optionKey, out value))
if (documentOptionSource.TryGetDocumentOption(_document, optionKey, _underlyingOptions, out value))
{
// Cache and return
lock (_gate)
......
......@@ -383,6 +383,7 @@
<Compile Include="FindSymbols\SyntaxTree\SyntaxTreeIndex.cs" />
<Compile Include="FindSymbols\SyntaxTree\SyntaxTreeIndex.DeclarationInfo.cs" />
<Compile Include="FindSymbols\SyntaxTree\SyntaxTreeIndex.IdentifierInfo.cs" />
<Compile Include="NamingStyles\Serialization\NamingStylePreferencesExtensions.cs" />
<Compile Include="PatternMatching\PatternMatch.cs" />
<Compile Include="PatternMatching\PatternMatcher.cs" />
<Compile Include="PatternMatching\PatternMatcher.Segment.cs" />
......
......@@ -2,29 +2,87 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.NamingStyles;
using Microsoft.CodeAnalysis.Options;
using Xunit;
using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.EditorConfigNamingStyleParser;
namespace Microsoft.CodeAnalysis.UnitTests.EditorConfig.StorageLocation
{
public class EditorConfigStorageLocationTests
{
[Fact]
public static void TestEmptyDictionaryReturnFalse()
public static void TestEmptyDictionaryReturnNoNamingStylePreferencesObjectReturnsFalse()
{
var editorConfigStorageLocation = new EditorConfigStorageLocation();
var result = editorConfigStorageLocation.TryParseReadonlyDictionary(new Dictionary<string, object>(), typeof(NamingStylePreferences), out var @object);
var result = editorConfigStorageLocation.TryGetOption(new object(), new Dictionary<string, object>(), typeof(NamingStylePreferences), out var @object);
Assert.False(result, "Expected TryParseReadonlyDictionary to return 'false' for empty dictionary");
}
[Fact]
public static void TestEmptyDictionaryDefaultNamingStylePreferencesObjectReturnsFalse()
{
var editorConfigStorageLocation = new EditorConfigStorageLocation();
var existingNamingStylePreferences = new NamingStylePreferences(
ImmutableArray.Create<SymbolSpecification>(),
ImmutableArray.Create<NamingStyle>(),
ImmutableArray.Create<SerializableNamingRule>());
var result = editorConfigStorageLocation.TryGetOption(
existingNamingStylePreferences,
new Dictionary<string, object>(),
typeof(NamingStylePreferences),
out var @object);
Assert.False(result, "Expected TryParseReadonlyDictionary to return 'false' for empty dictionary");
}
[Fact]
public static void TestNonEmptyDictionaryReturnsTrue()
{
var initialDictionary = new Dictionary<string, object>()
{
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.severity"] = "warning",
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.symbols"] = "method_and_property_symbols",
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.style"] = "pascal_case_style",
["dotnet_naming_symbols.method_and_property_symbols.applicable_kinds"] = "method,property",
["dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities"] = "*",
["dotnet_naming_style.pascal_case_style.capitalization"] = "pascal_case"
};
var existingNamingStylePreferences = ParseDictionary(initialDictionary);
var editorConfigStorageLocation = new EditorConfigStorageLocation();
var newDictionary = new Dictionary<string, object>()
{
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.severity"] = "error",
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.symbols"] = "method_and_property_symbols",
["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.style"] = "pascal_case_style",
["dotnet_naming_symbols.method_and_property_symbols.applicable_kinds"] = "method,property",
["dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities"] = "*",
["dotnet_naming_style.pascal_case_style.capitalization"] = "pascal_case"
};
var result = editorConfigStorageLocation.TryGetOption(
existingNamingStylePreferences,
newDictionary,
typeof(NamingStylePreferences),
out var combinedNamingStyles);
Assert.True(result, "Expected non-empty dictionary to return true");
var isNamingStylePreferencesObject = combinedNamingStyles is NamingStylePreferences;
Assert.True(isNamingStylePreferencesObject, $"Expected returned object to be of type '{nameof(NamingStylePreferences)}'");
Assert.Equal(DiagnosticSeverity.Error, ((NamingStylePreferences)combinedNamingStyles).Rules.NamingRules[0].EnforcementLevel);
}
[Fact]
public static void TestObjectTypeThrowsNotSupportedException()
{
var editorConfigStorageLocation = new EditorConfigStorageLocation();
Assert.Throws<NotSupportedException>(() =>
{
editorConfigStorageLocation.TryParseReadonlyDictionary(new Dictionary<string, object>(), typeof(object), out var @object);
editorConfigStorageLocation.TryGetOption(new object(), new Dictionary<string, object>(), typeof(object), out var @object);
});
}
}
......
......@@ -68,10 +68,10 @@
<ItemGroup>
<Compile Include="CodeStyle\EditorConfigCodeStyleParserTests.cs" />
<Compile Include="DependentTypeFinderTests.cs" />
<Compile Include="EditorConfigStorageLocation\EditorConfigStorageLocationTests.cs" />
<Compile Include="Differencing\LongestCommonSubsequenceTests.cs" />
<Compile Include="Editting\SyntaxEditorTests.cs" />
<Compile Include="Execution\Extensions.cs" />
<Compile Include="EditorConfigStorageLocation\EditorConfigStorageLocationTests.cs" />
<Compile Include="Execution\SnapshotSerializationTestBase.cs" />
<Compile Include="Execution\SnapshotSerializationTests.cs" />
<Compile Include="ExtensionOrdererTests.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册