未验证 提交 80e9227e 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #50073 from dotnet/merges/release/dev16.9-to-master

Merge release/dev16.9 to master
......@@ -13016,11 +13016,13 @@ class C
var analyzerConfigFile = dir.CreateFile(".globalconfig");
var analyzerConfig = analyzerConfigFile.WriteAllText(@"
is_global = true
global_level = 100
option1 = abc");
var analyzerConfigFile2 = dir.CreateFile(".globalconfig2");
var analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
is_global = true
global_level = 100
option1 = def");
var output = VerifyOutput(dir, src, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path + "," + analyzerConfig2.Path }, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false);
......@@ -13033,11 +13035,13 @@ class C
analyzerConfig = analyzerConfigFile.WriteAllText(@"
is_global = true
global_level = 100
[/file.cs]
option1 = abc");
analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
is_global = true
global_level = 100
[/file.cs]
option1 = def");
......
......@@ -2175,6 +2175,258 @@ public void GlobalConfigIssuesWarningWithInvalidSectionNames(string sectionName,
public void GlobalConfigIssuesWarningWithInvalidSectionNames_PlatformSpecific(string sectionName, bool isValidWindows, bool isValidOther)
=> GlobalConfigIssuesWarningWithInvalidSectionNames(sectionName, ExecutionConditionUtil.IsWindows ? isValidWindows : isValidOther);
[Theory]
[InlineData("/.globalconfig", true)]
[InlineData("/.GLOBALCONFIG", true)]
[InlineData("/.glObalConfiG", true)]
[InlineData("/path/to/.globalconfig", true)]
[InlineData("/my.globalconfig", false)]
[InlineData("/globalconfig", false)]
[InlineData("/path/to/globalconfig", false)]
[InlineData("/path/to/my.globalconfig", false)]
[InlineData("/.editorconfig", false)]
[InlineData("/.globalconfİg", false)]
public void FileNameCausesConfigToBeReportedAsGlobal(string fileName, bool shouldBeTreatedAsGlobal)
{
var config = Parse("", fileName);
Assert.Equal(shouldBeTreatedAsGlobal, config.IsGlobal);
}
[Fact]
public void GlobalLevelCanBeReadFromAnyConfig()
{
var config = Parse("global_level = 5", "/.editorconfig");
Assert.Equal(5, config.GlobalLevel);
}
[Fact]
public void GlobalLevelDefaultsTo100ForUserGlobalConfigs()
{
var config = Parse("", "/" + AnalyzerConfig.UserGlobalConfigName);
Assert.True(config.IsGlobal);
Assert.Equal(100, config.GlobalLevel);
}
[Fact]
public void GlobalLevelCanBeOverriddenForUserGlobalConfigs()
{
var config = Parse("global_level = 5", "/" + AnalyzerConfig.UserGlobalConfigName);
Assert.True(config.IsGlobal);
Assert.Equal(5, config.GlobalLevel);
}
[Fact]
public void GlobalLevelDefaultsToZeroForNonUserGlobalConfigs()
{
var config = Parse("is_global = true", "/.nugetconfig");
Assert.True(config.IsGlobal);
Assert.Equal(0, config.GlobalLevel);
}
[Fact]
public void GlobalLevelIsNotPresentInConfigSet()
{
var config = Parse("global_level = 123", "/.globalconfig");
var set = AnalyzerConfigSet.Create(ImmutableArray.Create(config));
var globalOptions = set.GlobalConfigOptions;
Assert.Empty(globalOptions.AnalyzerOptions);
Assert.Empty(globalOptions.TreeOptions);
Assert.Empty(globalOptions.Diagnostics);
}
[Fact]
public void GlobalLevelInSectionIsPresentInConfigSet()
{
var config = Parse(@"
[/path]
global_level = 123", "/.globalconfig");
var set = AnalyzerConfigSet.Create(ImmutableArray.Create(config));
var globalOptions = set.GlobalConfigOptions;
Assert.Empty(globalOptions.AnalyzerOptions);
Assert.Empty(globalOptions.TreeOptions);
Assert.Empty(globalOptions.Diagnostics);
var sectionOptions = set.GetOptionsForSourcePath("/path");
Assert.Single(sectionOptions.AnalyzerOptions);
Assert.Equal("123", sectionOptions.AnalyzerOptions["global_level"]);
Assert.Empty(sectionOptions.TreeOptions);
Assert.Empty(sectionOptions.Diagnostics);
}
[Theory]
[InlineData(1, 2)]
[InlineData(2, 1)]
[InlineData(-2, -1)]
[InlineData(2, -1)]
public void GlobalLevelAllowsOverrideOfGlobalKeys(int level1, int level2)
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse($@"
is_global = true
global_level = {level1}
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse($@"
is_global = true
global_level = {level2}
option1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Single(globalConfig.GlobalSection.Properties.Keys, "option1");
string expectedValue = level1 > level2 ? "value1" : "value2";
Assert.Single(globalConfig.GlobalSection.Properties.Values, expectedValue);
configs.Free();
}
[Fact]
public void GlobalLevelAllowsOverrideOfSectionKeys()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
global_level = 1
[/path]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"
is_global = true
global_level = 2
[/path]
option1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Single(globalConfig.NamedSections);
Assert.Equal("/path", globalConfig.NamedSections[0].Name);
Assert.Single(globalConfig.NamedSections[0].Properties.Keys, "option1");
Assert.Single(globalConfig.NamedSections[0].Properties.Values, "value2");
configs.Free();
}
[Theory]
[InlineData(1, 2, 3, "value3")]
[InlineData(2, 1, 3, "value3")]
[InlineData(3, 2, 1, "value1")]
[InlineData(1, 2, 1, "value2")]
[InlineData(1, 1, 2, "value3")]
[InlineData(2, 1, 1, "value1")]
public void GlobalLevelAllowsOverrideOfDuplicateGlobalKeys(int level1, int level2, int level3, string expectedValue)
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse($@"
is_global = true
global_level = {level1}
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse($@"
is_global = true
global_level = {level2}
option1 = value2",
"/.globalconfig2"));
configs.Add(Parse($@"
is_global = true
global_level = {level3}
option1 = value3",
"/.globalconfig3"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Single(globalConfig.GlobalSection.Properties.Keys, "option1");
Assert.Single(globalConfig.GlobalSection.Properties.Values, expectedValue);
configs.Free();
}
[Fact]
public void GlobalLevelReportsConflictsOnlyAtTheHighestLevel()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse($@"
is_global = true
global_level = 1
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse($@"
is_global = true
global_level = 1
option1 = value2",
"/.globalconfig2"));
configs.Add(Parse($@"
is_global = true
global_level = 3
option1 = value3",
"/.globalconfig3"));
configs.Add(Parse($@"
is_global = true
global_level = 3
option1 = value4",
"/.globalconfig4"));
configs.Add(Parse($@"
is_global = true
global_level = 2
option1 = value5",
"/.globalconfig5"));
configs.Add(Parse($@"
is_global = true
global_level = 2
option1 = value6",
"/.globalconfig6"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
// we don't report config1, 2, 5, or 6, because they didn't conflict: 3 + 4 overrode them, but then themselves were conflicting
diagnostics.Verify(
Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "Global Section", "/.globalconfig3, /.globalconfig4").WithLocation(1, 1)
);
configs.Free();
}
[Fact]
public void InvalidGlobalLevelIsIgnored()
{
var userGlobalConfig = Parse($@"
is_global = true
global_level = abc
", "/.globalconfig");
var nonUserGlobalConfig = Parse($@"
is_global = true
global_level = abc
", "/.editorconfig");
Assert.Equal(100, userGlobalConfig.GlobalLevel);
Assert.Equal(0, nonUserGlobalConfig.GlobalLevel);
}
#endregion
}
}
......@@ -28,6 +28,16 @@ public sealed partial class AnalyzerConfig
/// </summary>
internal const string GlobalKey = "is_global";
/// <summary>
/// Key that indicates the precedence of this config when <see cref="IsGlobal"/> is true
/// </summary>
internal const string GlobalLevelKey = "global_level";
/// <summary>
/// Filename that indicates this file is a user provided global config
/// </summary>
internal const string UserGlobalConfigName = ".globalconfig";
/// <summary>
/// A set of keys that are reserved for special interpretation for the editorconfig specification.
/// All values corresponding to reserved keys in a (key,value) property pair are always lowercased
......@@ -86,7 +96,45 @@ public sealed partial class AnalyzerConfig
/// <summary>
/// Gets whether this editorconfig is a global editorconfig.
/// </summary>
internal bool IsGlobal => GlobalSection.Properties.ContainsKey(GlobalKey);
internal bool IsGlobal => _hasGlobalFileName || GlobalSection.Properties.ContainsKey(GlobalKey);
/// <summary>
/// Get the global level of this config, used to resolve conflicting keys
/// </summary>
/// <remarks>
/// A user can explicitly set the global level via the <see cref="GlobalLevelKey"/>.
/// When no global level is explicitly set, we use a heuristic:
/// <list type="bullet">
/// <item><description>
/// Any file matching the <see cref="UserGlobalConfigName"/> is determined to be a user supplied global config and gets a level of 100
/// </description></item>
/// <item><description>
/// Any other file gets a default level of 0
/// </description></item>
/// </list>
///
/// This value is unused when <see cref="IsGlobal"/> is <c>false</c>.
/// </remarks>
internal int GlobalLevel
{
get
{
if (GlobalSection.Properties.TryGetValue(GlobalLevelKey, out string? val) && int.TryParse(val, out int level))
{
return level;
}
else if (_hasGlobalFileName)
{
return 100;
}
else
{
return 0;
}
}
}
private readonly bool _hasGlobalFileName;
private AnalyzerConfig(
Section globalSection,
......@@ -96,6 +144,7 @@ public sealed partial class AnalyzerConfig
GlobalSection = globalSection;
NamedSections = namedSections;
PathToFile = pathToFile;
_hasGlobalFileName = Path.GetFileName(pathToFile).Equals(UserGlobalConfigName, StringComparison.OrdinalIgnoreCase);
// Find the containing directory and normalize the path separators
string directory = Path.GetDirectoryName(pathToFile) ?? pathToFile;
......
......@@ -481,8 +481,8 @@ internal static GlobalAnalyzerConfig MergeGlobalConfigs(ArrayBuilder<AnalyzerCon
/// </summary>
internal struct GlobalAnalyzerConfigBuilder
{
private ImmutableDictionary<string, ImmutableDictionary<string, (string value, string configPath)>.Builder>.Builder? _values;
private ImmutableDictionary<string, ImmutableDictionary<string, ArrayBuilder<string>>.Builder>.Builder? _duplicates;
private ImmutableDictionary<string, ImmutableDictionary<string, (string value, string configPath, int globalLevel)>.Builder>.Builder? _values;
private ImmutableDictionary<string, ImmutableDictionary<string, (int globalLevel, ArrayBuilder<string> configPaths)>.Builder>.Builder? _duplicates;
internal const string GlobalConfigPath = "<Global Config>";
internal const string GlobalSectionName = "Global Section";
......@@ -491,16 +491,16 @@ internal void MergeIntoGlobalConfig(AnalyzerConfig config, DiagnosticBag diagnos
{
if (_values is null)
{
_values = ImmutableDictionary.CreateBuilder<string, ImmutableDictionary<string, (string, string)>.Builder>(Section.NameEqualityComparer);
_duplicates = ImmutableDictionary.CreateBuilder<string, ImmutableDictionary<string, ArrayBuilder<string>>.Builder>(Section.NameEqualityComparer);
_values = ImmutableDictionary.CreateBuilder<string, ImmutableDictionary<string, (string, string, int)>.Builder>(Section.NameEqualityComparer);
_duplicates = ImmutableDictionary.CreateBuilder<string, ImmutableDictionary<string, (int, ArrayBuilder<string>)>.Builder>(Section.NameEqualityComparer);
}
MergeSection(config.PathToFile, config.GlobalSection, isGlobalSection: true);
MergeSection(config.PathToFile, config.GlobalSection, config.GlobalLevel, isGlobalSection: true);
foreach (var section in config.NamedSections)
{
if (IsAbsoluteEditorConfigPath(section.Name))
{
MergeSection(config.PathToFile, section, isGlobalSection: false);
MergeSection(config.PathToFile, section, config.GlobalLevel, isGlobalSection: false);
}
else
{
......@@ -525,7 +525,7 @@ internal GlobalAnalyzerConfig Build(DiagnosticBag diagnostics)
{
bool isGlobalSection = string.IsNullOrWhiteSpace(section);
string sectionName = isGlobalSection ? GlobalSectionName : section;
foreach ((var keyName, var configPaths) in keys)
foreach ((var keyName, (_, var configPaths)) in keys)
{
diagnostics.Add(Diagnostic.Create(
MultipleGlobalAnalyzerKeysDescriptor,
......@@ -562,52 +562,71 @@ private Section GetSection(string sectionName)
return new Section(sectionName, result);
}
private void MergeSection(string configPath, Section section, bool isGlobalSection)
private void MergeSection(string configPath, Section section, int globalLevel, bool isGlobalSection)
{
Debug.Assert(_values is object);
Debug.Assert(_duplicates is object);
if (!_values.TryGetValue(section.Name, out var sectionDict))
{
sectionDict = ImmutableDictionary.CreateBuilder<string, (string, string)>(Section.PropertiesKeyComparer);
sectionDict = ImmutableDictionary.CreateBuilder<string, (string, string, int)>(Section.PropertiesKeyComparer);
_values.Add(section.Name, sectionDict);
}
_duplicates.TryGetValue(section.Name, out var duplicateDict);
foreach ((var key, var value) in section.Properties)
{
if (isGlobalSection && Section.PropertiesKeyComparer.Equals(key, GlobalKey))
if (isGlobalSection && (Section.PropertiesKeyComparer.Equals(key, GlobalKey) || Section.PropertiesKeyComparer.Equals(key, GlobalLevelKey)))
{
continue;
}
bool keyInSection = sectionDict.ContainsKey(key);
bool keyDuplicated = duplicateDict?.ContainsKey(key) ?? false;
bool keyInSection = sectionDict.TryGetValue(key, out var sectionValue);
// if this key is neither already present, or already duplicate, we can add it
(int globalLevel, ArrayBuilder<string> configPaths) duplicateValue = default;
bool keyDuplicated = !keyInSection && duplicateDict?.TryGetValue(key, out duplicateValue) == true;
// if this key is neither already present, or already duplicate, we can add it
if (!keyInSection && !keyDuplicated)
{
sectionDict.Add(key, (value, configPath));
sectionDict.Add(key, (value, configPath, globalLevel));
}
else
{
if (duplicateDict is null)
int currentGlobalLevel = keyInSection ? sectionValue.globalLevel : duplicateValue.globalLevel;
// if this key overrides one we knew about previously, replace it
if (currentGlobalLevel < globalLevel)
{
duplicateDict = ImmutableDictionary.CreateBuilder<string, ArrayBuilder<string>>(Section.PropertiesKeyComparer);
_duplicates.Add(section.Name, duplicateDict);
sectionDict[key] = (value, configPath, globalLevel);
if (keyDuplicated)
{
duplicateDict!.Remove(key);
}
}
// this key conflicts with a previous one
else if (currentGlobalLevel == globalLevel)
{
if (duplicateDict is null)
{
duplicateDict = ImmutableDictionary.CreateBuilder<string, (int, ArrayBuilder<string>)>(Section.PropertiesKeyComparer);
_duplicates.Add(section.Name, duplicateDict);
}
// record that this key is now a duplicate
ArrayBuilder<string> configList = keyDuplicated ? duplicateDict[key] : ArrayBuilder<string>.GetInstance();
configList.Add(configPath);
duplicateDict[key] = configList;
// record that this key is now a duplicate
ArrayBuilder<string> configList = duplicateValue.configPaths ?? ArrayBuilder<string>.GetInstance();
configList.Add(configPath);
duplicateDict[key] = (globalLevel, configList);
// if we'd previously added this key, remove it and remember the extra duplicate location
if (keyInSection)
{
var originalConfigPath = sectionDict[key].configPath;
sectionDict.Remove(key);
duplicateDict[key].Insert(0, originalConfigPath);
// if we'd previously added this key, remove it and remember the extra duplicate location
if (keyInSection)
{
var originalEntry = sectionValue;
Debug.Assert(originalEntry.globalLevel == globalLevel);
sectionDict.Remove(key);
configList.Insert(0, originalEntry.configPath);
}
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册